Table of Contents

Custom drawing

When the appearance properties cannot express the look you need, the tab set exposes before/after draw events for every visual part of a tab — background, text, bitmap, badge, progress indicator and close button — plus the menu buttons. A Before* event hands you a var ADefaultDraw: Boolean you can set to False to fully replace the built-in rendering; an After* event lets you overlay extra graphics on top of the finished tab. Reach for these only when a property cannot do the job: they run on every repaint, so keep the handlers cheap.

Before and after draw hooks

Tab part Before (can suppress) After (overlay)
Background OnBeforeDrawTabBackground OnAfterDrawTabBackground
Text OnBeforeDrawTabText OnAfterDrawTabText
Bitmap OnBeforeDrawTabBitmap OnAfterDrawTabBitmap
Badge OnBeforeDrawTabBadge OnAfterDrawTabBadge
Progress OnBeforeDrawTabProgress OnAfterDrawTabProgress
Close button OnBeforeDrawTabCloseButton OnAfterDrawTabCloseButton
Menu buttons OnBeforeDrawMenuButton OnAfterDrawMenuButton

Each handler receives the AGraphics surface, the ATabIndex, and the ARect to draw into. Background, text and close-button events also pass the tab's state — a TTMSFNCTabSetTabState (ttsNormal, ttsHover, ttsDown, ttsActive, ttsDisabled) for the tab, or a TTMSFNCTabSetButtonState for the buttons.

Accent the active tab

OnAfterDrawTabBackground paints after the default tab, so an overlay never hides the label. Here it draws a top accent bar on the active tab:

procedure TForm1.TMSFNCTabSet1AfterDrawTabBackground(Sender: TObject;
  AGraphics: TTMSFNCGraphics; ATabIndex: Integer; ARect: TRectF;
  AState: TTMSFNCTabSetTabState);
begin
  // After-draw paints over the finished tab, so the accent never hides text.
  if AState = ttsActive then
  begin
    AGraphics.Fill.Color := gcSteelBlue;
    AGraphics.Fill.Kind := gfkSolid;
    AGraphics.DrawRectangle(RectF(ARect.Left, ARect.Top, ARect.Right, ARect.Top + 3));
  end;
end;
Active tab with a top accent bar Active tab with a top accent bar

Replace the badge

To draw a part from scratch, handle its Before* event and set ADefaultDraw := False. This replaces the default badge with a filled circle:

procedure TForm1.TMSFNCTabSet1BeforeDrawTabBadge(Sender: TObject;
  AGraphics: TTMSFNCGraphics; ATabIndex: Integer; ARect: TRectF; AText: String;
  var ADefaultDraw: Boolean);
begin
  // Replace the built-in badge entirely.
  ADefaultDraw := False;

  AGraphics.Fill.Color := gcCrimson;
  AGraphics.Fill.Kind := gfkSolid;
  AGraphics.Stroke.Color := gcWhite;
  AGraphics.DrawEllipse(ARect);

  AGraphics.Font.Color := gcWhite;
  AGraphics.DrawText(ARect, AText, False, gtaCenter, gtaCenter);
end;

Combining an accent and a custom badge

Putting it together — wire the after-draw background accent and the before-draw badge so the active tab gets an accent bar while every badge is a custom circle. Because one is an After* overlay and the other a Before* replacement, they compose without conflicting:

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCTabSet1.OnAfterDrawTabBackground := TMSFNCTabSet1AfterDrawTabBackground;
  TMSFNCTabSet1.OnBeforeDrawTabBadge := TMSFNCTabSet1BeforeDrawTabBadge;
end;

procedure TForm1.TMSFNCTabSet1AfterDrawTabBackground(Sender: TObject;
  AGraphics: TTMSFNCGraphics; ATabIndex: Integer; ARect: TRectF;
  AState: TTMSFNCTabSetTabState);
begin
  if AState = ttsActive then
  begin
    AGraphics.Fill.Color := gcSteelBlue;
    AGraphics.Fill.Kind := gfkSolid;
    AGraphics.DrawRectangle(RectF(ARect.Left, ARect.Top, ARect.Right, ARect.Top + 3));
  end;
end;

procedure TForm1.TMSFNCTabSet1BeforeDrawTabBadge(Sender: TObject;
  AGraphics: TTMSFNCGraphics; ATabIndex: Integer; ARect: TRectF; AText: String;
  var ADefaultDraw: Boolean);
begin
  ADefaultDraw := False;
  AGraphics.Fill.Color := gcCrimson;
  AGraphics.Fill.Kind := gfkSolid;
  AGraphics.Stroke.Color := gcWhite;
  AGraphics.DrawEllipse(ARect);
  AGraphics.Font.Color := gcWhite;
  AGraphics.DrawText(ARect, AText, False, gtaCenter, gtaCenter);
end;

Common mistakes

  • Forgetting ADefaultDraw := False. In a Before* handler your drawing is layered under the default unless you suppress it, so custom fills look wrong.
  • Heavy work in a draw handler. These fire on every repaint; precompute colors and avoid allocations inside them.
  • Drawing outside ARect. Stay within the supplied rectangle, or your graphics bleed into neighbouring tabs.
  • TTMSFNCTabSetOnBeforeDrawTabBackground, OnAfterDrawTabBackground, OnBeforeDrawTabBadge, OnBeforeDrawTabText, OnBeforeDrawMenuButton

See also