Table of Contents

Interaction and editing

The blox control is interactive: end users select, move, resize, rotate, connect, and delete elements, and you drive editing commands from code through the presenter. This chapter explains the editing states, how to insert elements programmatically, the interaction options that constrain editing, and the command set (selection, clipboard, undo/redo, grouping, z-order, panning, and zoom).

Editing states

TTMSFNCBloxControl implements the concept of a state that decides what a user gesture does:

  • Browsing — select, move, resize, rotate (the default).
  • Inserting — the next click inserts a new element.
  • Panning — dragging scrolls the diagram.

You switch states through the Presenter. The control returns to browsing when an action completes (or when you cancel it).

Inserting elements programmatically

Presenter.StartInsertingElement puts the control in insert mode. It has three overloads — by element instance, by element class, or by registered element id. The AKeepInserting parameter keeps the control in insert mode so the user can place several copies; CancelInsertingBlock returns to browsing.

procedure TForm1.InsertActionBlockClick(Sender: TObject);
begin
  { The next clicks on the canvas insert action blocks until cancelled. }
  BloxControl1.Presenter.StartInsertingElement(TTMSFNCBloxFlowActionBlock, True);
end;

procedure TForm1.InsertByIdClick(Sender: TObject);
begin
  { Or insert a single element identified by its registered id. }
  BloxControl1.Presenter.StartInsertingElement('TTMSFNCBloxTextBlock');
end;

procedure TForm1.CancelInsertClick(Sender: TObject);
begin
  BloxControl1.Presenter.CancelInsertingBlock;
end;

Interaction options

Interaction (TTMSFNCBloxInteraction) constrains what users may do:

Member Effect
ReadOnly Disables all editing — view-only.
SelectionMode slmMultiple (per-object handles) or slmGroup (Visio-style group handles).
KeyActions Which keyboard actions are enabled (kaMove, kaResize, kaDelete, kaSelect, kaPage, kaEscape).
CanMoveOutOfBounds Whether blocks may move outside the control bounds.
TouchElementHandling Enables touch panning/zooming on mobile.
Zooming Enables zoom via mouse, keyboard, or touch.
ChangeTextWithDoubleClick Whether a double-click starts in-place text editing.
procedure TForm1.ConfigureInteraction;
begin
  BloxControl1.Interaction.SelectionMode := slmGroup;
  BloxControl1.Interaction.CanMoveOutOfBounds := False;
  BloxControl1.Interaction.ChangeTextWithDoubleClick := True;
  BloxControl1.Interaction.Zooming := True;
  BloxControl1.Interaction.KeyActions := [kaEscape, kaMove, kaResize, kaSelect, kaDelete];
end;

procedure TForm1.SetViewOnly(AReadOnly: Boolean);
begin
  { Disable all interactive editing for a view-only diagram. }
  BloxControl1.Interaction.ReadOnly := AReadOnly;
end;

Editing commands

The presenter exposes the full command set. Selection: SelectAll, UnselectAll, DeleteSelecteds. Clipboard: CopyElementsToClipboard, PasteElementsFromClipboard, guarded by CanPaste. History: Undo, Redo, guarded by CanUndo / CanRedo. Grouping: GroupSelectedBlocks, UngroupSelectedBlocks. Z-order: BringSelectedsToFront, SendSelectedsToBack.

Grouping and resize tracking

Grouping turns a multi-element selection into one TTMSFNCBloxGroup that moves and resizes as a unit. Interaction.SelectionMode decides how the group is then manipulated: slmGroup gives the group a single Visio-style handle set, while slmMultiple keeps per-element handles inside it. GroupSelectedBlocks returns the new group, or nil when there was nothing to group.

Membership is readable from any element. IsGroup identifies a group element, IsMember a grouped one, GroupBlock is the immediate parent group, and MasterGroup is the outermost one — so nesting is visible by comparing the two. Both are nil for a standalone element.

Resizing reports through two events with different costs. OnElementResizing fires repeatedly during the drag, so keep the handler cheap. OnAfterResize fires once when the gesture completes — and the presenter raises it before it calls Modified and before it pushes the 'resize' entry onto the undo stack, so state read there is the committed size but the undo entry does not exist yet.

procedure TForm1.GroupSelection;
var
  LGroup: TTMSFNCBloxGroup;
begin
  { Group handles behave Visio-style; slmMultiple keeps per-element handles. }
  BloxControl1.Interaction.SelectionMode := slmGroup;

  if BloxControl1.Presenter.SelectedCount > 1 then
  begin
    LGroup := BloxControl1.Presenter.GroupSelectedBlocks;
    if Assigned(LGroup) then
      Caption := 'Grouped ' + IntToStr(BloxControl1.Presenter.SelectedCount) +
        ' element(s)';
  end;
end;

procedure TForm1.UngroupSelection;
begin
  BloxControl1.Presenter.UngroupSelectedBlocks;
end;

function TForm1.DescribeMembership(AElement: TTMSFNCBloxElement): string;
begin
  { GroupBlock is the immediate parent group and is nil for an ungrouped
    element - and also for the outermost group itself. MasterGroup is the
    outermost group, which for that top-level group is the group itself. }
  if AElement.IsGroup then
    Result := 'group'
  else if AElement.IsMember then
  begin
    Result := 'member';
    if Assigned(AElement.GroupBlock) then
      Result := Result + ' of ' + AElement.GroupBlock.ElementId;
    if Assigned(AElement.MasterGroup) and
       (AElement.MasterGroup <> AElement.GroupBlock) then
      Result := Result + ' nested under ' + AElement.MasterGroup.ElementId;
  end
  else
    Result := 'standalone';
end;

procedure TForm1.BloxControlElementResizing(Sender: TObject;
  Element: TTMSFNCBloxElement; X, Y: Single);
begin
  { Fires repeatedly while a size handle is dragged - cheap work only. }
  StatusLabel.Text := Format('%s %.0f x %.0f', [DescribeMembership(Element), X, Y]);
end;

procedure TForm1.BloxControlAfterResize(Sender: TObject);
begin
  { Fires once when the gesture completes, BEFORE OnModified and before the
    'resize' entry reaches the undo stack. }
  StatusLabel.Text := 'Resize committed';
end;

procedure TForm1.WireResizeTracking;
begin
  { Shift + arrow resizing needs kaResize in KeyActions (present by default). }
  BloxControl1.Interaction.KeyActions :=
    BloxControl1.Interaction.KeyActions + [kaResize];

  BloxControl1.OnElementResizing := BloxControlElementResizing;
  BloxControl1.OnAfterResize := BloxControlAfterResize;
end;

Shift with the arrow keys resizes the selection when kaResize is in Interaction.KeyActions, which it is by default.

Putting it together

This handler set wires a toolbar of edit commands, enabling each only when valid — combining selection, clipboard, history, grouping, and z-order in one place:

procedure TForm1.CopyClick(Sender: TObject);
begin
  BloxControl1.Presenter.CopyElementsToClipboard;
end;

procedure TForm1.PasteClick(Sender: TObject);
begin
  if BloxControl1.Presenter.CanPaste then
    BloxControl1.Presenter.PasteElementsFromClipboard;
end;

procedure TForm1.GroupClick(Sender: TObject);
begin
  BloxControl1.Presenter.GroupSelectedBlocks;
end;

procedure TForm1.BringToFrontClick(Sender: TObject);
begin
  BloxControl1.Presenter.BringSelectedsToFront;
end;

procedure TForm1.UpdateEditButtons;
begin
  { Reflect availability so buttons enable only when the action is valid. }
  UndoButton.Enabled := BloxControl1.Presenter.CanUndo;
  RedoButton.Enabled := BloxControl1.Presenter.CanRedo;
  PasteButton.Enabled := BloxControl1.Presenter.CanPaste;
end;

procedure TForm1.UndoClick(Sender: TObject);
begin
  if BloxControl1.Presenter.CanUndo then
    BloxControl1.Presenter.Undo;
end;

procedure TForm1.DeleteClick(Sender: TObject);
begin
  BloxControl1.Presenter.DeleteSelecteds;
end;
A diagram with multiple selected blocks showing group selection handles A diagram with multiple selected blocks showing group selection handles

Panning and zoom

Zoom is the Zoom property (1.0 = 100%); changing it re-scales the whole diagram and raises OnZoomChanged. Presenter.StartPanning enters panning mode, and CancelPanning returns to browsing. Presenter.ClientToCanvas / CanvasToClient convert between pixel and blox coordinates when you need to map a mouse position.

procedure TForm1.ZoomInClick(Sender: TObject);
begin
  BloxControl1.Zoom := BloxControl1.Zoom * 1.25;   { 1.0 = 100% }
end;

procedure TForm1.ResetZoomClick(Sender: TObject);
begin
  BloxControl1.Zoom := 1.0;
end;

procedure TForm1.StartPanClick(Sender: TObject);
begin
  BloxControl1.Presenter.StartPanning;
end;

procedure TForm1.StopPanClick(Sender: TObject);
begin
  BloxControl1.Presenter.CancelPanning;
end;

Pitfalls

  • ReadOnly disables programmatic insertion paths that rely on user gestures too — drive the diagram through Blox.Add when the control is read-only.
  • Guard clipboard and history commands. Call CanPaste, CanUndo, and CanRedo before the corresponding action so buttons reflect availability.
  • Zoom works in factor units, not percent. Use 1.5 for 150%, not 150.

See also