Appearance
Blox separates what an element is from how it looks. Every block and line
carries its own Fill, Stroke, Shadow, and font, and the control itself has
a background, a snap grid, and rulers. This chapter shows how to style elements
and how to set up the canvas surface so a diagram reads cleanly and matches your
application theme.
Element fill, stroke and shadow
Each element exposes Fill (TTMSFNCBloxFill), Stroke (TTMSFNCBloxStroke),
and Shadow (TTMSFNCBloxShadow). For the common cases the FillColor,
StrokeColor, and StrokeWidth shortcuts are enough; reach into the objects for
gradients, stroke styles, and shadows.
| Object | Key members |
|---|---|
Fill |
FillType (ftNone, ftSolid, ftTexture, ftGradient), Color, SecondColor, GradientMode (gsHorizontal, gsVertical), Texture |
Stroke |
Color, Width, Style (ssSolid, ssDash, ssDot, ssDashDot, …) |
Shadow |
Visible, Color, HOffset, VOffset |
procedure TForm1.StyleBlock(ABlock: TTMSFNCBloxBlock);
begin
ABlock.Fill.FillType := ftSolid;
ABlock.Fill.Color := MakeGraphicsColor(232, 240, 254);
ABlock.Stroke.Color := MakeGraphicsColor(74, 144, 217);
ABlock.Stroke.Width := 2;
ABlock.Stroke.Style := ssDash;
end;
A gradient fill combined with a soft shadow gives blocks depth — this example sets both on one block:
procedure TForm1.AddGradientBlock;
var
LBlock: TTMSFNCBloxBlock;
begin
LBlock := TTMSFNCBloxBlock.Create;
LBlock.Left := 80;
LBlock.Top := 80;
LBlock.Width := 200;
LBlock.Height := 100;
LBlock.Shape := RoundRect;
LBlock.Text := 'Gradient';
LBlock.TextColor := gcWhite;
LBlock.Fill.FillType := ftGradient;
LBlock.Fill.GradientMode := gsVertical;
LBlock.Fill.Color := MakeGraphicsColor(74, 144, 217);
LBlock.Fill.SecondColor := MakeGraphicsColor(23, 110, 168);
LBlock.Shadow.Visible := True;
LBlock.Shadow.Color := MakeGraphicsColor(120, 120, 120);
LBlock.Shadow.HOffset := 4;
LBlock.Shadow.VOffset := 4;
BloxControl1.Blox.Add(LBlock);
end;
The canvas surface
The control's own Fill and Stroke paint the diagram background. DefaultUnit
(duMili, duCenti, duInch) sets the measuring unit applied to the rulers and
snap grid.
procedure TForm1.ConfigureCanvas;
begin
BloxControl1.BeginUpdate;
try
{ Background surface. }
BloxControl1.Fill.Color := MakeGraphicsColor(250, 250, 252);
{ Measuring unit applied to rulers and snap grid. }
BloxControl1.DefaultUnit := duCenti;
{ Snap grid. }
BloxControl1.SnapGrid.Active := True;
BloxControl1.SnapGrid.Visible := True;
BloxControl1.SnapGrid.Style := gsDots;
BloxControl1.SnapGrid.SizeX := 0.5;
BloxControl1.SnapGrid.SizeY := 0.5;
{ Rulers. }
BloxControl1.TopRuler.Visible := True;
BloxControl1.LeftRuler.Visible := True;
BloxControl1.TopRuler.AutoFactor := True;
BloxControl1.LeftRuler.AutoFactor := True;
finally
BloxControl1.EndUpdate;
end;
end;
Snap grid
SnapGrid aligns inserting, moving, and resizing to a regular step. Active
turns snapping on, Force snaps inserted objects immediately, SizeX / SizeY
set the step (or set SnapToRuler to follow the ruler ticks instead), Style
chooses gsDots or gsLines, and Visible shows or hides it.
Rulers
LeftRuler and TopRuler (TTMSFNCBloxRuler) draw measurement scales. Set
Visible, the MeasureUnit, Divisions between values, and AutoFactor (when
True, the ruler step adapts as the diagram is zoomed). The snap-grid and ruler
setup is shown in the canvas snippet above.
Selection handle appearance
Handles are the affordance a user reads before touching anything: their colour says "this is selected", their shape says what kind of manipulation is on offer, and a greyed handle says "this action is blocked here". Branding them is therefore not decoration — it is how a diagram editor communicates its own rules — and the control keeps two separate palettes so a group never looks like a lone element.
ElementHandleAppearance and GroupHandleAppearance (both
TTMSFNCBloxHandlesAppearance) hold four handle roles, each a
TTMSFNCBloxHandleAppearance:
| Role | Handles it styles |
|---|---|
Size |
The eight sizing handles around the element bounds. |
Rotate |
The rotation handle. |
RotCenter |
The marker for the rotation centre. |
Custom |
Handles a custom element class contributes itself. |
Each role carries FillColor, StrokeColor, DisabledFillColor,
DisabledStrokeColor, Size (radius in pixels), Style and Visible. The
Disabled pair is what gets painted when the element forbids that action — an
element with crNoResize in Restrictions (so CanResize returns False)
shows grey sizing handles rather than none, which is usually what you want:
users see the handle and learn the element is fixed.
Style is a TTMSFNCBloxHandleStyle: hhRectangle, hhEllipse, hhDiamond,
hhCrossedEllipse, hhRectangleX or hhEllipseX. Out of the box element
handles are light blue with a dodger-blue stroke, group handles light coral with
a firebrick stroke, Size is 3 (6 on FMX mobile, for touch), and Style is
hhRectangle.
Overriding handles on one element
Element.HandlesAppearance is an overlay, not a replacement. When the element
is drawn, the control-level palette is copied and then only the values the
element has actually set are applied on top, detected by sentinel:
| Member | Overrides only when | So the "inherit" value is |
|---|---|---|
| the four colours | not gcNull |
gcNull (the constructor default) |
Size |
greater than 0 |
0 |
Style |
not hhRectangle |
hhRectangle |
Visible |
never — see the pitfall below | — |
That is why a fresh element inherits everything: its own appearance is constructed entirely from sentinels.
procedure TForm1.StyleSelectionHandles;
var
LElement: TTMSFNCBloxElement;
begin
BloxControl1.BeginUpdate;
try
{ Handles drawn around a single selected element. }
BloxControl1.ElementHandleAppearance.Size.FillColor := gcWhite;
BloxControl1.ElementHandleAppearance.Size.StrokeColor := gcSteelblue;
BloxControl1.ElementHandleAppearance.Size.Size := 4;
BloxControl1.ElementHandleAppearance.Size.Style := hhRectangle;
{ Disabled colors are what a restricted element paints instead. }
BloxControl1.ElementHandleAppearance.Size.DisabledFillColor := gcSilver;
BloxControl1.ElementHandleAppearance.Size.DisabledStrokeColor := gcSilver;
{ The rotation handle and the rotation-center marker are styled apart. }
BloxControl1.ElementHandleAppearance.Rotate.FillColor := gcSeagreen;
BloxControl1.ElementHandleAppearance.Rotate.Style := hhEllipse;
BloxControl1.ElementHandleAppearance.RotCenter.FillColor := gcCoral;
BloxControl1.ElementHandleAppearance.RotCenter.Style := hhCrossedEllipse;
{ A second palette keeps a group visually distinct from a lone element. }
BloxControl1.GroupHandleAppearance.Size.FillColor := gcWhite;
BloxControl1.GroupHandleAppearance.Size.StrokeColor := gcCrimson;
BloxControl1.GroupHandleAppearance.Size.Size := 4;
BloxControl1.GroupHandleAppearance.Size.Style := hhDiamond;
{ Per-element overlay: only the values assigned here replace the
control-level ones; everything left at its sentinel keeps inheriting. }
if BloxControl1.Blox.ElementCount > 0 then
begin
LElement := BloxControl1.Blox.Elements[0];
LElement.HandlesAppearance.Size.FillColor := gcDarkorange;
LElement.HandlesAppearance.Size.Size := 6;
LElement.HandlesAppearance.Size.Style := hhEllipse;
{ Restrict the element so its handles paint the Disabled colors. }
LElement.Restrictions := LElement.Restrictions + [crNoRotation];
end;
finally
BloxControl1.EndUpdate;
end;
end;
Putting it together
A polished diagram styles both the elements and the surface they sit on. This example configures the canvas background, snap grid, and rulers, then adds a gradient-filled, shadowed block onto it — combining element appearance with the canvas setup:
procedure TForm1.BuildStyledScene;
var
LBlock: TTMSFNCBloxBlock;
begin
BloxControl1.BeginUpdate;
try
{ Canvas surface. }
BloxControl1.Fill.Color := MakeGraphicsColor(250, 250, 252);
BloxControl1.DefaultUnit := duCenti;
BloxControl1.SnapGrid.Active := True;
BloxControl1.SnapGrid.Style := gsDots;
BloxControl1.TopRuler.Visible := True;
BloxControl1.LeftRuler.Visible := True;
{ A gradient, shadowed block on that surface. }
LBlock := TTMSFNCBloxBlock.Create;
LBlock.Left := 60;
LBlock.Top := 60;
LBlock.Width := 200;
LBlock.Height := 100;
LBlock.Shape := RoundRect;
LBlock.Text := 'Styled';
LBlock.TextColor := gcWhite;
LBlock.Fill.FillType := ftGradient;
LBlock.Fill.GradientMode := gsVertical;
LBlock.Fill.Color := MakeGraphicsColor(74, 144, 217);
LBlock.Fill.SecondColor := MakeGraphicsColor(23, 110, 168);
LBlock.Shadow.Visible := True;
LBlock.Shadow.HOffset := 4;
LBlock.Shadow.VOffset := 4;
BloxControl1.Blox.Add(LBlock);
finally
BloxControl1.EndUpdate;
end;
end;
Pitfalls
Visiblecannot be overridden per element. The per-element overlay copies colours,SizeandStylebut neverVisible, so settingElement.HandlesAppearance.Size.Visible := Falsehas no effect. Hide a handle role at the control level, or restrict the element instead so it paints theDisabledcolours.You cannot force
hhRectangleon one element.hhRectangleis the sentinel for "inherit the style", so an element cannot opt back to rectangles when the control-level style is something else. Invert the design: makehhRectanglethe control-level style and give the exceptional elements the other shapes.A gradient needs
FillType := ftGradient. SettingColorandSecondColoralone has no effect whileFillTypeisftSolid.SnapToRuleroverridesSizeX/SizeY. When it isTrue, the explicit step sizes are ignored and the grid follows the ruler subdivisions.The
FillColorshortcut impliesftSolid. To switch back to a gradient after using it, setFillTypeexplicitly again.