Table of Contents

Custom elements

Blox has an open architecture: when the built-in libraries do not have the shape you need, you build your own block by descending from TTMSFNCBloxBlock and drawing a path, then register it so it appears in the Selector and ToolBar alongside the built-in shapes. This chapter shows how to draw a custom block, give it link points, and register it (and how to re-categorise existing blocks).

Drawing a custom block

Override GetBlockPath and add geometry to the supplied path. Coordinates are in the block's original rectangle space (0–100 on each axis), so the shape scales automatically with the block. AddPolygon takes a TTMSFNCBloxPointArray built from TMSFNCBloxPoint(X, Y); AddRectangle and AddEllipse cover the simple cases.

type
  THexagonBlock = class(TTMSFNCBloxBlock)
  protected
    procedure GetBlockPath(APath: TTMSFNCBloxPath; ADrawer: TTMSFNCBloxBlockDrawer); override;
  public
    procedure UpdateLinkPoints; virtual;
  end;

procedure THexagonBlock.GetBlockPath(APath: TTMSFNCBloxPath;
  ADrawer: TTMSFNCBloxBlockDrawer);
var
  LPoly: TTMSFNCBloxPointArray;
begin
  { Coordinates are in the 0..100 original-rectangle space, so the shape scales. }
  SetLength(LPoly, 7);
  LPoly[0] := TMSFNCBloxPoint(25, 0);
  LPoly[1] := TMSFNCBloxPoint(75, 0);
  LPoly[2] := TMSFNCBloxPoint(100, 50);
  LPoly[3] := TMSFNCBloxPoint(75, 100);
  LPoly[4] := TMSFNCBloxPoint(25, 100);
  LPoly[5] := TMSFNCBloxPoint(0, 50);
  LPoly[6] := TMSFNCBloxPoint(25, 0);
  APath.AddPolygon(LPoly);
end;

procedure THexagonBlock.UpdateLinkPoints;
begin
  with OriginalRect do
  begin
    LinkPoints.Clear;
    LinkPoints.AddLink(Left, (Bottom - Top) / 2, aoLeft);
    LinkPoints.AddLink(Right, (Bottom - Top) / 2, aoRight);
  end;
end;
Tip

Override UpdateLinkPoints to give your block link points at meaningful positions (see Working with blocks for the coordinate convention).

Registering elements

Custom blocks become available to the selector and toolbar through the OnRegisterElements event. Call RegisterElement(AClass, AId, ACaption, ACategory) from the FMX.TMSFNCBloxUIRegistration unit (or the VCL./WEB. equivalent). The selector and toolbar pick up the new category automatically.

procedure TForm1.BloxControl1RegisterElements(Sender: TObject);
begin
  { RegisterElement(class, id, caption, category). An empty id uses the class name. }
  RegisterElement(THexagonBlock, '', 'Hexagon', 'My Shapes');
end;

Re-categorising and refreshing

Calling RegisterElement again for an existing class re-registers it, so you can move blocks between categories or rename their caption. After changing registrations at runtime, call Rebuild on the selector and/or toolbar so they reflect the change:

procedure TForm1.ConsolidateCategory;
begin
  { Re-registering an existing class moves it to the new category. }
  RegisterElement(TTMSFNCBloxBlock, '', 'Simple block', 'My Blocks');
  RegisterElement(TTMSFNCBloxTextBlock, '', 'Text block', 'My Blocks');
  RegisterElement(TTMSFNCBloxLine, '', 'Line', 'My Blocks');

  { Refresh the palette so the new category shows. }
  BloxSelector1.Rebuild;
end;

Putting it together

The full custom-element flow ties the pieces together: register the custom block from OnRegisterElements, then create and add an instance — combining registration with programmatic insertion of the custom shape:

procedure TForm1.BloxControl1RegisterElements(Sender: TObject);
begin
  RegisterElement(THexagonBlock, '', 'Hexagon', 'My Shapes');
end;

procedure TForm1.AddHexagonClick(Sender: TObject);
var
  LBlock: THexagonBlock;
begin
  LBlock := THexagonBlock.Create;
  LBlock.Left := 80;
  LBlock.Top := 80;
  LBlock.Width := 140;
  LBlock.Height := 120;
  LBlock.Text := 'Custom';
  LBlock.FillColor := MakeGraphicsColor(150, 80, 200);
  LBlock.TextColor := gcWhite;
  BloxControl1.Blox.Add(LBlock);
end;

Pitfalls

  • Draw in 0–100 space, not pixels. GetBlockPath coordinates are relative to the original rectangle so the shape scales with the block; using pixel values produces a shape that does not resize.
  • Register before the control needs the block. Wire OnRegisterElements (or call RegisterElements on the control) before relying on a custom id in StartInsertingElement.
  • Refresh the palette after runtime re-registration. The selector/toolbar do not always auto-refresh — call Rebuild if a new block does not appear.

See also