Table of Contents

In-place Editing

TTMSFNCLabelEdit displays formatted label text and can switch into an embedded edit control. Use it for editable names, captions, tags, or settings where a separate dialog would interrupt the workflow.

Label edit

Configure Editing

The control inherits formatted text rendering and adds an Edit control plus accept and cancel buttons. Set Editable to allow user edits, or set it to False for read-only label behavior.

procedure TForm1.ConfigureLabelEdit;
begin
  TMSFNCLabelEdit1.Text := 'Project name';
  TMSFNCLabelEdit1.Editable := True;
  TMSFNCLabelEdit1.HorizontalTextAlign := gtaLeading;
end;

procedure TForm1.StartLabelEdit;
begin
  TMSFNCLabelEdit1.EditMode := True;
end;

Commit and Cancel

When the user accepts the edit, OnAccept fires after the text has been committed. When the user cancels, the previous text is restored and OnCancel fires.

procedure TForm1.ConfigureLabelEditEvents;
begin
  TMSFNCLabelEdit1.OnAccept := TMSFNCLabelEdit1Accept;
  TMSFNCLabelEdit1.OnCancel := TMSFNCLabelEdit1Cancel;
end;

procedure TForm1.TMSFNCLabelEdit1Accept(Sender: TObject);
begin
  Caption := TMSFNCLabelEdit1.Text;
end;

procedure TForm1.TMSFNCLabelEdit1Cancel(Sender: TObject);
begin
  StatusLabel.Text := 'Edit canceled.';
end;

Edit Lifecycle

Use OnEditStart and OnEditEnd for UI state changes such as highlighting the edited row or enabling validation helpers. OnEditKeyDown is available when keyboard handling needs to intercept a key before the edit completes.

Button Styling

AcceptButton and CancelButton are image controls. Their strokes are exposed through AcceptButtonStroke and CancelButtonStroke, and they inherit the label edit BitmapContainer for shared bitmap usage.

procedure TForm1.ConfigureLabelEditButtons;
begin
  TMSFNCLabelEdit1.BitmapContainer := TMSFNCBitmapContainer1;
  TMSFNCLabelEdit1.AcceptButtonStroke.Color := gcGreen;
  TMSFNCLabelEdit1.CancelButtonStroke.Color := gcRed;
end;

Combining in-place editing, commit/cancel, and lifecycle events

The example below opens the editor from a Rename command, highlights the row for the duration of the edit, validates the committed value, and styles the drawn accept and cancel glyphs.

Two behaviours shape the pattern. Clicking an editable label already enters edit mode, so code that opens the editor assigns EditMode := True rather than calling a start method. And every one of the four events is a plain TNotifyEvent that fires after the fact: OnAccept runs once Text already holds the new value and edit mode has closed, so a rejected value is undone by writing the previous one back rather than by vetoing the commit. Capture that previous value in OnEditStart.

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCLabelEdit1.Text     := 'Project name';
  TMSFNCLabelEdit1.Editable := True;

  TMSFNCLabelEdit1.OnEditStart := DoEditStart;
  TMSFNCLabelEdit1.OnEditEnd   := DoEditEnd;
  TMSFNCLabelEdit1.OnAccept    := DoAccept;
  TMSFNCLabelEdit1.OnCancel    := DoCancel;

  { Style the drawn check and cross, and share the form's bitmap container so
    custom glyphs can be resolved by name. }
  TMSFNCLabelEdit1.BitmapContainer := TMSFNCBitmapContainer1;
  TMSFNCLabelEdit1.AcceptButtonStroke.Color := gcGreen;
  TMSFNCLabelEdit1.CancelButtonStroke.Color := gcRed;
end;

{ Clicking an editable label already enters edit mode; there is no StartEdit
  method, so code that opens the editor (a Rename command, an F2 shortcut)
  assigns EditMode instead. }
procedure TForm1.btnRenameClick(Sender: TObject);
begin
  TMSFNCLabelEdit1.EditMode := True;
end;

procedure TForm1.DoEditStart(Sender: TObject);
begin
  FPreviousName := TMSFNCLabelEdit1.Text;   // keep our own copy to roll back to
  HighlightRow(True);
end;

procedure TForm1.DoEditEnd(Sender: TObject);
begin
  HighlightRow(False);
end;

{ OnAccept fires AFTER the edited value has been committed to Text and edit mode
  has already closed, and it carries no cancel parameter - so a rejected value
  is undone by writing the previous one back, not by vetoing the commit. }
procedure TForm1.DoAccept(Sender: TObject);
begin
  if Trim(TMSFNCLabelEdit1.Text) = '' then
  begin
    TMSFNCLabelEdit1.Text := FPreviousName;
    ShowMessage('Name cannot be blank.');
    Exit;
  end;

  SaveProjectName(TMSFNCLabelEdit1.Text);
end;

{ OnCancel fires after the control has already restored the previous text. }
procedure TForm1.DoCancel(Sender: TObject);
begin
  StatusLabel.Text := 'Rename cancelled';
end;
Note

OnEditKeyDown fires after the control has handled Return and Escape itself, so it cannot suppress commit or cancel. Use it for additional shortcuts, and do validation in OnAccept.

Glyph sizing across DPI scales

The high-DPI support added in 1.0.1.0 rescales the accept and cancel button widths along with the rest of the control when the form's DPI changes, so the button size and the stroke width are authored once in design (96 DPI) units and need no runtime arithmetic.

The glyphs themselves are the interesting part: by default they are drawn as vector strokes from AcceptButtonStroke and CancelButtonStroke, not loaded from bitmaps, so they are already sharp at any scale. Supplying a bitmap opts that drawing out — the image renderer takes over — and the bitmap then needs scaled variants of its own, added to the button's Bitmaps collection with one entry per display scale.

procedure TForm1.ConfigureLabelEditForHighDPI;
begin
  { By default the accept and cancel glyphs are drawn as vector strokes, not
    bitmaps, so they stay sharp at any display scale. Express the button size
    and the stroke width in design (96 DPI) units once - the control rescales
    both when the form's DPI changes. }
  TMSFNCLabelEdit1.AcceptButton.Width := 25;
  TMSFNCLabelEdit1.CancelButton.Width := 25;
  TMSFNCLabelEdit1.AcceptButtonStroke.Width := 2;
  TMSFNCLabelEdit1.CancelButtonStroke.Width := 2;

  { Replacing a glyph with a bitmap opts out of the vector drawing, so that
    bitmap now needs scaled variants of its own. AcceptButton and CancelButton
    are TTMSFNCImage instances whose Bitmaps property is a TTMSFNCScaledBitmaps
    collection: one entry per display scale, picked at paint time.

    Turn AutoSize off first: the buttons ship with AutoSize enabled, which
    resizes them to the resolved bitmap's raw pixel size - so the 2.0 variant
    would make the button twice as wide instead of twice as sharp. }
  TMSFNCLabelEdit1.AcceptButton.AutoSize := False;
  TMSFNCLabelEdit1.CancelButton.AutoSize := False;

  TMSFNCLabelEdit1.AcceptButton.Bitmaps.Clear;
  TMSFNCLabelEdit1.AcceptButton.Bitmaps.AddBitmapFromFile('accept.png', 1.0);
  TMSFNCLabelEdit1.AcceptButton.Bitmaps.AddBitmapFromFile('accept@2x.png', 2.0);

  TMSFNCLabelEdit1.CancelButton.Bitmaps.Clear;
  TMSFNCLabelEdit1.CancelButton.Bitmaps.AddBitmapFromFile('cancel.png', 1.0);
  TMSFNCLabelEdit1.CancelButton.Bitmaps.AddBitmapFromFile('cancel@2x.png', 2.0);

  if TTMSFNCUtils.IsHighDPIScale(Self) then
    StatusLabel.Text := Format('Glyphs resolved at %.2fx',
      [TTMSFNCUtils.GetDPIScale(Self)]);
end;
Important

Both buttons ship with AutoSize enabled, which resizes them to the resolved bitmap's raw pixel size. Leave it on and the 2.0 variant makes the button twice as wide instead of twice as sharp — switch AutoSize off and keep an explicit Width when you assign scaled bitmaps.

See also