RadioItems in menu on TActionMainMenuBar - delphi

I use the TActionMainMenuBar to show menu based on TActions.
I grouped the actions by setting the same GroupIndex. So they can operate like RadioGroup, but the problem is that there are checks is drawn instead of radio buttons.
Is there any way to change that?

Here is my fix for TPlatformDefaultStyleActionBars.
Most of the code just copied from standard units, except TFixedThemedMenuItemStyle.DoDrawMenuCheck.
Note you must override also TXPStyleMenuItem if you want to run your software on pre-Vista OS.
uses
// ... add these units
StdStyleActnCtrls, XPStyleActnCtrls, XPActnCtrls, ImgList, Types, Themes,
StdActnMenus, ThemedActnCtrls, ListActns, UxTheme;
type
TFixedThemedMenuItemStyle = class(TThemedMenuItem)
private
FCheckRect: TRect;
FGutterRect: TRect;
FPaintRect: TRect;
FSubMenuGlyphRect: TRect;
FSeparatorHeight: Integer;
procedure DoDrawMenuCheck;
procedure DoDrawText(DC: HDC; const Text: string; var Rect: TRect; Flags: Longint);
protected
procedure DrawGlyph(const Location: TPoint); override;
public
procedure CalcBounds; override;
end;
TFixedPlatformDefaultStyleActionBars = class(TPlatformDefaultStyleActionBars)
public
function GetControlClass(ActionBar: TCustomActionBar;
AnItem: TActionClientItem): TCustomActionControlClass; override;
function GetStyleName: string; override;
end;
TForm1 = class(TForm)
ActionMainMenuBar1: TActionMainMenuBar;
ActionManager1: TActionManager;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
Style: TFixedPlatformDefaultStyleActionBars;
public
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
Style := TFixedPlatformDefaultStyleActionBars.Create();
ActionManager1.Style := Style;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Style.Free();
end;
procedure TFixedThemedMenuItemStyle.CalcBounds;
const
CheckMarkStates: array[Boolean] of Integer =
(MC_CHECKMARKDISABLED, MC_CHECKMARKNORMAL);
SubMenuStates: array[Boolean] of Integer = (MSM_DISABLED, MSM_NORMAL);
var
DC: HDC;
LFont: HFONT;
LTheme: HTheme;
LBounds: TRect;
LImageSize: TPoint;
LHeight, LWidth, Offset: Integer;
LGlyphSize, LGutterSize, LSeparatorSize, LSubMenuGlyphSize: TSize;
LCheckMargins, LGutterMargins, LMenuItemMargins, LSeparatorMargins, LSubMenuGlyphMargins: TMargins;
begin
// Fill in parent object's private fields.
inherited;
DC := CreateCompatibleDC(0);
try
LFont := SelectObject(DC, Screen.MenuFont.Handle);
try
Font.Assign(Screen.MenuFont);
inherited;
LTheme := ThemeServices.Theme[teMenu];
LHeight := 0;
LWidth := 0;
// Check/Glyph
GetThemePartSize(LTheme, DC, MENU_POPUPCHECK,
CheckMarkStates[Enabled], nil, TS_TRUE, LGlyphSize);
GetThemeMargins(LTheme, DC, MENU_POPUPCHECK,
CheckMarkStates[Enabled], TMT_CONTENTMARGINS, nil, LCheckMargins);
// Gutter
GetThemePartSize(LTheme, DC, MENU_POPUPGUTTER, 0, nil, TS_TRUE, LGutterSize);
GetThemeMargins(LTheme, DC, MENU_POPUPGUTTER, 0, TMT_SIZINGMARGINS, nil, LGutterMargins);
// Menu item
GetThemeMargins(LTheme, DC, MENU_POPUPITEM, MPI_NORMAL, TMT_SIZINGMARGINS, nil, LMenuItemMargins);
GetThemePartSize(LTheme, DC, MENU_POPUPSUBMENU, SubMenuStates[Enabled], nil, TS_TRUE, LSubMenuGlyphSize);
GetThemeMargins(LTheme, DC, MENU_POPUPSUBMENU, SubMenuStates[Enabled], TMT_CONTENTMARGINS, nil, LSubMenuGlyphMargins);
// Calculate check/glyph size
LImageSize := GetImageSize;
if LImageSize.Y > LGlyphSize.cy then
LGlyphSize.cy := LImageSize.Y;
if LImageSize.X > LGlyphSize.cx then
LGlyphSize.cx := LImageSize.X;
Inc(LHeight, LGlyphSize.cy);
Inc(LWidth, LGlyphSize.cx);
// Add margins for check/glyph
Inc(LHeight, LCheckMargins.cyTopHeight + LCheckMargins.cyBottomHeight);
Inc(LWidth, LCheckMargins.cxLeftWidth + LCheckMargins.cxRightWidth);
FCheckRect := Rect(0, 0,
LGlyphSize.cx + LCheckMargins.cxRightWidth + LCheckMargins.cxRightWidth,
LGlyphSize.cy + LCheckMargins.cyBottomHeight + LCheckMargins.cyBottomHeight);
// Add size and margins for gutter
Inc(LWidth, LGutterMargins.cxLeftWidth);
FGutterRect.Left := LWidth;
FGutterRect.Right := FGutterRect.Left + LGutterSize.cx;
Inc(LWidth, LGutterSize.cx + LGutterMargins.cxRightWidth);
// Add margins for menu item
Inc(LWidth, LMenuItemMargins.cxLeftWidth + LMenuItemMargins.cxRightWidth);
Offset := LWidth - TextBounds.Left - LMenuItemMargins.cxRightWidth;
LBounds := TextBounds;
OffsetRect(LBounds, Offset, -1);
TextBounds := LBounds;
// Add size of potential submenu glyph
Inc(LWidth, LSubMenuGlyphSize.cx);
Inc(LWidth, LSubMenuGlyphMargins.cxLeftWidth);
Inc(LWidth, LSubMenuGlyphMargins.cxRightWidth);
// Add Width of menu item to FSubMenuGlyphRect before using
FSubMenuGlyphRect := Rect(-LSubMenuGlyphMargins.cxRightWidth - LSubMenuGlyphSize.cx,
(Height - LSubMenuGlyphSize.cy) div 2,
-LSubMenuGlyphMargins.cxRightWidth,
((Height - LSubMenuGlyphSize.cy) div 2) + LSubMenuGlyphSize.cy);
// Add margins for menu short cut
if ActionClient <> nil then
begin
LBounds := Rect(0, 0, 0, 0);
DoDrawText(DC, ActionClient.ShortCutText, LBounds, DT_CALCRECT or DT_NOCLIP);
end
else
LBounds := ShortCutBounds;
Offset := FSubMenuGlyphRect.Left - LBounds.Right -
LMenuItemMargins.cxRightWidth - LSubMenuGlyphMargins.cxLeftWidth;
OffsetRect(LBounds, Offset, 0);
// Add Width of menu item to ShortCutBounds before using
ShortCutBounds := LBounds;
Inc(LWidth, LMenuItemMargins.cxLeftWidth + LMenuItemMargins.cxRightWidth);
// Adjust size if separator
if Separator then
begin
GetThemePartSize(LTheme, DC, MENU_POPUPSEPARATOR, 0, nil, TS_TRUE, LSeparatorSize);
GetThemeMargins(LTheme, DC, MENU_POPUPSEPARATOR, 0, TMT_SIZINGMARGINS, nil, LSeparatorMargins);
LHeight := LSeparatorSize.cy + LSeparatorMargins.cyBottomHeight;
LWidth := LSeparatorSize.cx;
FSeparatorHeight := LSeparatorSize.cy;
end;
FGutterRect.Top := 0;
FGutterRect.Bottom := LHeight;
SetBounds(Left, Top,
LWidth + TextBounds.Right - TextBounds.Left + ShortCutBounds.Right - ShortCutBounds.Left,
LHeight);
finally
SelectObject(DC, LFont);
end;
finally
DeleteDC(DC);
end;
end;
// THE ONLY SERIOUS DIFFERENCE: RENDERING BULLETS INSTEAD OF CHECKMARKS FOR RADIO ITEMS
procedure TFixedThemedMenuItemStyle.DoDrawMenuCheck;
const
CheckMarkBkgs: array[Boolean] of Integer = (MCB_DISABLED, MCB_NORMAL);
CheckMarkStates: array[Boolean] of Integer = (MC_CHECKMARKDISABLED, MC_CHECKMARKNORMAL);
RadioMarkStates: array[Boolean] of Integer = (MC_BULLETDISABLED, MC_BULLETNORMAL);
begin
if IsChecked then
begin
DrawThemeBackground(ThemeServices.Theme[teMenu], Canvas.Handle,
MENU_POPUPCHECKBACKGROUND, CheckMarkBkgs[Enabled], FCheckRect, nil);
if not HasGlyph then
begin
if IsGrouped then
begin
DrawThemeBackground(ThemeServices.Theme[teMenu], Canvas.Handle,
MENU_POPUPCHECK, RadioMarkStates[Enabled], FCheckRect, nil);
end
else
begin
DrawThemeBackground(ThemeServices.Theme[teMenu], Canvas.Handle,
MENU_POPUPCHECK, CheckMarkStates[Enabled], FCheckRect, nil);
end;
end;
end;
end;
procedure TFixedThemedMenuItemStyle.DoDrawText(
DC: HDC; const Text: string; var Rect: TRect; Flags: Integer);
const
MenuStates: array[Boolean] of Integer = (MPI_DISABLED, MPI_NORMAL);
var
Options: TDTTOpts;
begin
// Setup Options
{$IF NOT DEFINED(CLR)}
FillChar(Options, SizeOf(Options), 0);
Options.dwSize := SizeOf(Options);
{$ELSE}
Options.dwSize := Marshal.SizeOf(TypeOf(Options));
{$IFEND}
Options.dwFlags := DTT_TEXTCOLOR or DTT_COMPOSITED;
if Flags and DT_CALCRECT = DT_CALCRECT then
Options.dwFlags := Options.dwFlags or DTT_CALCRECT;
// Retrieve text color
GetThemeColor(ThemeServices.Theme[teMenu], MENU_POPUPITEM,
MenuStates[Enabled or ActionBar.DesignMode], TMT_TEXTCOLOR, Options.crText);
// Draw menu item text
DrawThemeTextEx(ThemeServices.Theme[teMenu], DC, MENU_POPUPITEM,
MenuStates[Enabled or ActionBar.DesignMode], Text, Length(Text), Flags, Rect, Options);
end;
procedure TFixedThemedMenuItemStyle.DrawGlyph(const Location: TPoint);
var
LImageSize, LLocation: TPoint;
begin
if (Action is TCustomAction) and TCustomAction(Action).Checked then
DoDrawMenuCheck;
if HasGlyph then
begin
LImageSize := GetImageSize;
LLocation.X := ((FCheckRect.Right - FCheckRect.Left) - LImageSize.X) div 2;
LLocation.Y := ((FCheckRect.Bottom - FCheckRect.Top) - LImageSize.Y) div 2;
inherited DrawGlyph(LLocation);
end;
end;
type
TActionControlStyle = (csStandard, csXPStyle, csThemed);
function GetActionControlStyle: TActionControlStyle;
begin
if Win32MajorVersion >= 6 then
begin
if ThemeServices.Theme[teMenu] <> 0 then
Result := csThemed
else
Result := csXPStyle;
end
else
if CheckWin32Version(5, 1) then
Result := csXPStyle
else
Result := csStandard;
end;
function TFixedPlatformDefaultStyleActionBars.GetControlClass(ActionBar: TCustomActionBar;
AnItem: TActionClientItem): TCustomActionControlClass;
begin
if ActionBar is TCustomActionToolBar then
begin
if AnItem.HasItems then
case GetActionControlStyle of
csStandard: Result := TStandardDropDownButton;
csXPStyle: Result := TXPStyleDropDownBtn;
else
Result := TThemedDropDownButton;
end
else
if (AnItem.Action is TStaticListAction) or
(AnItem.Action is TVirtualListAction) then
Result := TCustomComboControl
else
case GetActionControlStyle of
csStandard: Result := TStandardButtonControl;
csXPStyle: Result := TXPStyleButton;
else
Result := TThemedButtonControl;
end
end
else if ActionBar is TCustomActionMainMenuBar then
case GetActionControlStyle of
csStandard: Result := TStandardMenuButton;
csXPStyle: Result := TXPStyleMenuButton;
else
Result := TThemedMenuButton;
end
else if ActionBar is TCustomizeActionToolBar then
begin
with TCustomizeActionToolbar(ActionBar) do
if not Assigned(RootMenu) or
(AnItem.ParentItem <> TCustomizeActionToolBar(RootMenu).AdditionalItem) then
case GetActionControlStyle of
csStandard: Result := TStandardMenuItem;
csXPStyle: Result := TXPStyleMenuItem;
else
Result := TFixedThemedMenuItemStyle;
end
else
case GetActionControlStyle of
csStandard: Result := TStandardAddRemoveItem;
csXPStyle: Result := TXPStyleAddRemoveItem;
else
Result := TThemedAddRemoveItem;
end
end
else if ActionBar is TCustomActionPopupMenu then
case GetActionControlStyle of
csStandard: Result := TStandardMenuItem;
csXPStyle: Result := TXPStyleMenuItem;
else
Result := TFixedThemedMenuItemStyle;
end
else
case GetActionControlStyle of
csStandard: Result := TStandardButtonControl;
csXPStyle: Result := TXPStyleButton;
else
Result := TThemedButtonControl;
end
end;
function TFixedPlatformDefaultStyleActionBars.GetStyleName: string;
begin
Result := 'My fixed platform style';
end;

Related

Caption Buttons not respond to mouse clicks after Non - Client painting with DWM in Delphi

I am painting my Application's non-client area with the help of Desktop Window Manager, adding a new button for testing purposes.
After compiling, my custom button is clickable, but the default caption buttons (Minimize, Maximize and Close) do nothing when I hover over them or click on them.
The repainted Title Bar responds to dragging and double-clicks. The Form
maximizes when I double-click the Title Bar as default. And the Close button responds to the very corner of it near Form's Right Border.
I have written my painting procedure as described in this post.
The new codes I added:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ImgList, Buttons;
type
TForm1 = class(TForm)
ImageList1: TImageList;
SpeedButton1: TSpeedButton;
function GetSysIconRect: TRect;
procedure PaintWindow(DC: HDC);
procedure InvalidateTitleBar;
procedure FormCreate(Sender: TObject);
procedure WndProc(var Message: TMessage);
procedure FormPaint(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
protected
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
procedure WMActivate(var Message: TWMActivate); message WM_ACTIVATE;
procedure CMTextChanged(var Message: TMessage);
procedure WMWindowPosChanging(var Message: TWMWindowPosChanging); message WM_WINDOWPOSCHANGING;
procedure WMNCRButtonUp(var Message: TWMNCRButtonUp); message WM_NCRBUTTONUP;
private
{ Private declarations }
FWndFrameSize: Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
DWMAPI, CommCtrl, Themes, UXTheme, StdCtrls;
{$R *.dfm}
{$IF not Declared(UnicodeString)}
type
UnicodeString = WideString;
{$IFEND}
procedure DrawGlassCaption(Form: TForm; const Text: UnicodeString;
Color: TColor; var R: TRect; HorzAlignment: TAlignment = taLeftJustify;
VertAlignment: TTextLayout = tlCenter; ShowAccel: Boolean = False); overload;
const
BasicFormat = DT_SINGLELINE or DT_END_ELLIPSIS;
HorzFormat: array[TAlignment] of UINT = (DT_LEFT, DT_RIGHT, DT_CENTER);
VertFormat: array[TTextLayout] of UINT = (DT_TOP, DT_VCENTER, DT_BOTTOM);
AccelFormat: array[Boolean] of UINT = (DT_NOPREFIX, 0);
var
DTTOpts: TDTTOpts;
Element: TThemedWindow;
IsVistaAndMaximized: Boolean;
NCM: TNonClientMetrics;
ThemeData: HTHEME;
procedure DoTextOut;
begin
with ThemeServices.GetElementDetails(Element) do
DrawThemeTextEx(ThemeData, Form.Canvas.Handle, Part, State, PWideChar(Text),
Length(Text), BasicFormat or AccelFormat[ShowAccel] or
HorzFormat[HorzAlignment] or VertFormat[VertAlignment], #R, DTTOpts);
end;
begin
if Color = clNone then Exit;
IsVistaAndMaximized := (Form.WindowState = wsMaximized) and
(Win32MajorVersion = 6) and (Win32MinorVersion = 0);
ThemeData := OpenThemeData(0, 'CompositedWindow::Window');
Assert(ThemeData <> 0, SysErrorMessage(GetLastError));
Try
NCM.cbSize := SizeOf(NCM);
if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, #NCM, 0) then
if Form.BorderStyle in [bsToolWindow, bsSizeToolWin] then
Form.Canvas.Font.Handle := CreateFontIndirect(NCM.lfSmCaptionFont)
else
Form.Canvas.Font.Handle := CreateFontIndirect(NCM.lfCaptionFont);
ZeroMemory(#DTTOpts, SizeOf(DTTOpts));
DTTOpts.dwSize := SizeOf(DTTOpts);
DTTOpts.dwFlags := DTT_COMPOSITED or DTT_TEXTCOLOR;
if Color <> clDefault then
DTTOpts.crText := ColorToRGB(Color)
else if IsVistaAndMaximized then
DTTOpts.dwFlags := DTTOpts.dwFlags and not DTT_TEXTCOLOR
else if Form.Active then
DTTOpts.crText := GetSysColor(COLOR_CAPTIONTEXT)
else
DTTOpts.crText := GetSysColor(COLOR_INACTIVECAPTIONTEXT);
if not IsVistaAndMaximized then
begin
DTTOpts.dwFlags := DTTOpts.dwFlags or DTT_GLOWSIZE;
DTTOpts.iGlowSize := 15;
end;
if Form.WindowState = wsMaximized then
if Form.Active then
Element := twMaxCaptionActive
else
Element := twMaxCaptionInactive
else if Form.BorderStyle in [bsToolWindow, bsSizeToolWin] then
if Form.Active then
Element := twSmallCaptionActive
else
Element := twSmallCaptionInactive
else
if Form.Active then
Element := twCaptionActive
else
Element := twCaptionInactive;
DoTextOut;
if IsVistaAndMaximized then DoTextOut;
Finally
CloseThemeData(ThemeData);
end;
end;
function GetDwmBorderIconsRect(Form: TForm): TRect;
begin
if DwmGetWindowAttribute(Form.Handle, DWMWA_CAPTION_BUTTON_BOUNDS, #Result, SizeOf(Result)) <> S_OK then SetRectEmpty(Result);
end;
procedure DrawGlassCaption(Form: TForm; var R: TRect;
HorzAlignment: TAlignment = taLeftJustify; VertAlignment: TTextLayout = tlCenter;
ShowAccel: Boolean = False); overload;
begin
DrawGlassCaption(Form, Form.Caption, clDefault, R,
HorzAlignment, VertAlignment, ShowAccel);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
R: TRect;
begin
if DwmCompositionEnabled then
begin
SetRectEmpty(R);
AdjustWindowRectEx(R, GetWindowLong(Handle, GWL_STYLE), False,
GetWindowLong(Handle, GWL_EXSTYLE));
FWndFrameSize := R.Right;
GlassFrame.Top := -R.Top;
GlassFrame.Enabled := True;
SetWindowPos(Handle, 0, Left, Top, Width, Height, SWP_FRAMECHANGED);
DoubleBuffered := True;
end;
end;
procedure TForm1.InvalidateTitleBar;
var
R: TRect;
begin
if not HandleAllocated then Exit;
R.Left := 0;
R.Top := 0;
R.Right := Width;
R.Bottom := GlassFrame.Top;
InvalidateRect(Handle, #R, False);
end;
procedure TForm1.CMTextChanged(var Message: TMessage);
begin
inherited;
InvalidateTitleBar;
end;
procedure TForm1.WMActivate(var Message: TWMActivate);
begin
inherited;
InvalidateTitleBar;
end;
procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
var
ClientPos: TPoint;
IconRect: TRect;
begin
inherited;
if not GlassFrame.Enabled then Exit;
case Message.Result of
HTCLIENT:
HTMINBUTTON, HTMAXBUTTON, HTCLOSE:
begin
Message.Result := HTCAPTION;
Exit;
end;
else
Exit;
end;
ClientPos := ScreenToClient(Point(Message.XPos, Message.YPos));
if ClientPos.Y > GlassFrame.Top then Exit;
if ControlAtPos(ClientPos, True) <> nil then Exit;
IconRect := GetSysIconRect;
if (ClientPos.X < IconRect.Right) and ((WindowState = wsMaximized) or
((ClientPos.Y >= IconRect.Top) and (ClientPos.Y < IconRect.Bottom))) then
Message.Result := HTSYSMENU
else if ClientPos.Y < FWndFrameSize then
Message.Result := HTTOP
else
Message.Result := HTCAPTION;
end;
procedure ShowSystemMenu(Form: TForm; const Message: TWMNCRButtonUp);
var
Cmd: WPARAM;
Menu: HMENU;
procedure UpdateItem(ID: UINT; Enable: Boolean; MakeDefaultIfEnabled: Boolean = False);
const
Flags: array[Boolean] of UINT = (MF_GRAYED, MF_ENABLED);
begin
EnableMenuItem(Menu, ID, MF_BYCOMMAND or Flags[Enable]);
if MakeDefaultIfEnabled and Enable then
SetMenuDefaultItem(Menu, ID, MF_BYCOMMAND);
end;
begin
Menu := GetSystemMenu(Form.Handle, False);
if Form.BorderStyle in [bsSingle, bsSizeable, bsToolWindow, bsSizeToolWin] then
begin
SetMenuDefaultItem(Menu, UINT(-1), 0);
UpdateItem(SC_RESTORE, Form.WindowState <> wsNormal, True);
UpdateItem(SC_MOVE, Form.WindowState <> wsMaximized);
UpdateItem(SC_SIZE, (Form.WindowState <> wsMaximized) and
(Form.BorderStyle in [bsSizeable, bsSizeToolWin]));
UpdateItem(SC_MINIMIZE, (biMinimize in Form.BorderIcons) and
(Form.BorderStyle in [bsSingle, bsSizeable]));
UpdateItem(SC_MAXIMIZE, (biMaximize in Form.BorderIcons) and
(Form.BorderStyle in [bsSingle, bsSizeable]) and
(Form.WindowState <> wsMaximized), True);
end;
if Message.HitTest = HTSYSMENU then
SetMenuDefaultItem(Menu, SC_CLOSE, MF_BYCOMMAND);
Cmd := WPARAM(TrackPopupMenu(Menu, TPM_RETURNCMD or
GetSystemMetrics(SM_MENUDROPALIGNMENT), Message.XCursor,
Message.YCursor, 0, Form.Handle, nil));
PostMessage(Form.Handle, WM_SYSCOMMAND, Cmd, 0)
end;
procedure TForm1.WMWindowPosChanging(var Message: TWMWindowPosChanging);
const
SWP_STATECHANGED = $8000;
begin
if GlassFrame.Enabled then
if (Message.WindowPos.flags and SWP_STATECHANGED) = SWP_STATECHANGED then
Invalidate
else
InvalidateTitleBar;
inherited;
end;
procedure TForm1.WMNCRButtonUp(var Message: TWMNCRButtonUp);
begin
if not GlassFrame.Enabled or not (biSystemMenu in BorderIcons) then
inherited
else
case Message.HitTest of
HTCAPTION, HTSYSMENU: ShowSystemMenu(Self, Message);
else
inherited;
end;
end;
procedure TForm1.WndProc(var Message: TMessage);
begin
if GlassFrame.Enabled and HandleAllocated and DwmDefWindowProc(Handle,
Message.Msg, Message.WParam, Message.LParam, Message.Result) then
Exit;
inherited;
end;
procedure TForm1.PaintWindow(DC: HDC);
begin
with GetClientRect do
ExcludeClipRect(DC, 0, GlassFrame.Top, Right, Bottom);
inherited;
end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
IconHandle: HICON;
R: TRect;
begin
if ImageList1.Count = 0 then
begin
ImageList1.Width := GetSystemMetrics(SM_CXSMICON);
ImageList1.Height := GetSystemMetrics(SM_CYSMICON);
{$IF NOT DECLARED(TColorDepth)}
ImageList1.Handle := ImageList_Create(ImageList1.Width,
ImageList1.Height, ILC_COLOR32 or ILC_MASK, 1, 1);
{$IFEND}
IconHandle := Icon.Handle;
if IconHandle = 0 then IconHandle := Application.Icon.Handle;
ImageList_AddIcon(ImageList1.Handle, IconHandle);
end;
R := GetSysIconRect;
ImageList1.Draw(Canvas, R.Left, R.Top, 0);
R.Left := R.Right + FWndFrameSize - 3;
if WindowState = wsMaximized then
R.Top := FWndFrameSize
else
R.Top := 0;
R.Right := GetDwmBorderIconsRect(Self).Left - FWndFrameSize - 1;
R.Bottom := GlassFrame.Top;
DrawGlassCaption(Self, R);
end;
function TForm1.GetSysIconRect: TRect;
begin
if not (biSystemMenu in BorderIcons) or not (BorderStyle in [bsSingle, bsSizeable]) then
SetRectEmpty(Result)
else
begin
Result.Left := 0;
Result.Right := GetSystemMetrics(SM_CXSMICON);
Result.Bottom := GetSystemMetrics(SM_CYSMICON);
if WindowState = wsMaximized then
Result.Top := GlassFrame.Top - Result.Bottom - 2
else
Result.Top := 6;
Inc(Result.Bottom, Result.Top);
end;
end;
procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
if not GlassFrame.Enabled then
inherited
else
with Message.CalcSize_Params.rgrc[0] do
begin
Inc(Left, FWndFrameSize);
Dec(Right, FWndFrameSize);
Dec(Bottom, FWndFrameSize);
end;
end;
end.
Please help me find what is causing the Caption Buttons to become unresponsive to mouse clicks.
The standard buttons do not work because your WM_NCHITTEST handler is returning HTCAPTION for them. You are lying to Windows, telling it that the mouse is not over the buttons even if it really is. If the inherited handler returns HTMINBUTTON, HTMAXBUTTON, or HTCLOSE, just Exit without modifying the Message.Result:
procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
var
ClientPos: TPoint;
IconRect: TRect;
begin
inherited;
if not GlassFrame.Enabled then Exit;
case Message.Result of
HTCLIENT:
HTMINBUTTON, HTMAXBUTTON, HTCLOSE:
begin
//Message.Result := HTCAPTION; // <-- here
Exit;
end;
else
Exit;
end;
...
end;

Custom Button Component : caption is displaying different color in Delphi7

i am working with Delphi 7
Below is my code for sample button component. When i place button component on form at design time it is displaying different color in Delphi7 as attached in screen shot.
in Delphi 5 same is Working fine. i mean it is displaying caption as black color.
unit TestButton1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Math;
type
TButtonShape = (bsRectangle, bsOval, bsTriangle);
type
TTestButton = class(TCustomControl)
private
{ Private declarations }
FCaption: TCaption;
FButtonDown: boolean;
FBorderStyle: TBorderStyle;
FBtnHighLight: TColor;
FBtnShadow: TColor;
FBtnFace: TColor;
procedure DrawButton;
procedure DrawButtonDown;
procedure DrawCaption(rc: TRect);
procedure DrawButtonUp;
procedure SetCaption(Value: TCaption);
procedure SetButtonColor(Value: TColor);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure WndProc(var Msg: TMessage); override;
published
{ Published declarations }
property Caption: TCaption read FCaption write SetCaption;
property Color: TColor read FBtnFace write SetButtonColor;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TTestButton]);
end;
{ TTestButton }
constructor TTestButton.Create(AOwner: TComponent);
begin
inherited;
if AOwner is TWinControl then Parent := TWinControl(AOwner);
FCaption := Name;
FButtonDown := FALSE;
FBorderStyle := bsNone;
ControlStyle := ControlStyle - [csDoubleClicks];
FBtnHighLight := clBtnHighLight;
FBtnShadow := clBtnShadow;
FBtnFace := clBtnFace;
Width := 75;
Height := 25;
end;
destructor TTestButton.Destroy;
begin
inherited;
end;
procedure TTestButton.DrawButton;
var rc: TRect;
begin
with rc do
Begin
Top := 0;
Left := 0;
Bottom := Height;
Right := Width;
end;
with Canvas do
Begin
if FBorderStyle = bsSingle then
Begin
Brush.Color := clBlack;
Framerect(rc);
end;
end;
if FButtonDown then DrawButtonDown
else DrawButtonUp;
end;
procedure TTestButton.DrawButtonDown;
var rc: TRect;
Cnv: TCanvas;
begin
with rc do
Begin
Top := 0;
Left := 0;
Bottom := Height - 1;
Right := Width - 1;
end;
Cnv := TCanvas.Create;
Cnv.Handle := CreateCompatibleDC(Canvas.Handle);
SelectObject(Cnv.Handle, CreateCompatibleBitmap(Canvas.Handle, Width, Height));
with Canvas do
Begin
Brush.Color := FBtnFace;
FillRect(rc);
Pen.Color := clBlack;
MoveTo(rc.Left, rc.Bottom - 1);
LineTo(rc.Left, rc.Top);
LineTo(rc.Right, rc.Top);
Pen.Color := FBtnShadow;
MoveTo(rc.Left + 1, rc.Bottom - 2);
LineTo(rc.Left + 1, rc.Top + 1);
LineTo(rc.Right - 1, rc.Top + 1);
Pen.Color := FBtnHighlight;
MoveTo(rc.Left, rc.Bottom);
LineTo(rc.Right, rc.Bottom);
Lineto(rc.Right, rc.Top - 1);
rc.Top := rc.Top + 1;
rc.Left := rc.Left + 1;
end;
rc.Top := rc.Top + 1;
rc.Left := rc.Left + 1;
if FCaption > '' then DrawCaption(rc);
end;
procedure TTestButton.DrawButtonUp;
var rc: TRect;
begin
with rc do
Begin
Top := 0;
Left := 0;
Bottom := Height - 1;
Right := Width - 1;
end;
with Canvas do
Begin
Brush.Color := FBtnFace;
FillRect(rc);
Pen.Color := FBtnHighlight;
MoveTo(rc.Left, rc.Bottom - 1);
LineTo(rc.Left, rc.Top);
LineTo(rc.Right, rc.Top);
Pen.Color := FBtnShadow;
MoveTo(rc.Left + 1, rc.Bottom - 1);
LineTo(rc.Right - 1, rc.Bottom - 1);
LineTo(rc.Right - 1, rc.Top);
Pen.Color := clBlack;
MoveTo(rc.Left, rc.Bottom);
LineTo(rc.Right, rc.Bottom);
Lineto(rc.Right, rc.Top - 1);
end;
if FCaption > '' then DrawCaption(rc);
end;
procedure TTestButton.DrawCaption(rc: TRect);
begin
Canvas.Brush.Style := bsClear;
SetTextColor(Canvas.Handle, Canvas.Font.Color);
DrawText(Canvas.Handle, PChar(FCaption), Length(FCaption), rc, DT_CENTER + DT_VCENTER + DT_SINGLELINE);
end;
procedure TTestButton.SetButtonColor(Value: TColor);
var OldValue: TColor;
begin
OldValue := FBtnFace;
FBtnFace := Value;
if (OldValue <> Value) then Repaint;
end;
procedure TTestButton.SetCaption(Value: TCaption);
begin
FCaption := Value;
Repaint;
end;
procedure TTestButton.WndProc(var Msg: TMessage);
begin
inherited;
with Msg do if Msg = WM_PAINT then
Begin
DrawButton;
Result := 0;
Exit;
end
else if Msg = WM_LBUTTONDOWN then
Begin
if not (csDesigning in ComponentState) then FButtonDown := TRUE;
Repaint;
Result := 0;
Exit;
end
else if Msg = WM_LBUTTONUP then
Begin
FButtonDown := FALSE;
Repaint;
Result := 0;
Exit;
end
end;
end.
at design time, Button caption is displaying in black color with delphi5 and with delphi7 button caption is displaying in different color.
Why it is working with delphi5 and why it is not working with Delphi7.
SetTextColor(Canvas.Handle, Canvas.Font.Color); in DrawCaption sets font color to same value it already has.
Different Delphi versions can set different initial values therefore you see the difference in behavior.
Use SetTextColor(Canvas.Handle, clBlack); instead (if you want black text color)

How can I make a TListView header caption centered in a VCL styled application?

I'm having a list view control (ListView here), and I'm filling it by the code like this:
var
Item: TListItem;
Column: TListColumn;
begin
ListView.ViewStyle := vsReport;
Column := ListView.Columns.Add;
Column.Width := 200;
Column.Alignment:= taCenter;
Column.Caption:= 'Column 1';
Column:= ListView.Columns.Add;
Column.Width := 200;
Column.Alignment := taCenter;
Column.Caption := 'Column 2';
Item := ListView.Items.Add;
Item.Caption := 'Item 1';
Item.SubItems.Add('Subitem 1');
end;
The problem is that when I use a VCL style in my application, the text is not centered in the list view header:
How can I make the header caption centered in a VCL styled application?
The style hook responsible for drawing the column header never checks for the text alignment of the column and always draws text with left alignment, clearly an oversight.
First create a descendant of Vcl.ComCtrls.TListViewStyleHook and a class helper for the ancestor so that we can access the private variable we will need.
TListViewStyleHookHelper = class helper for TListViewStyleHook
function getFHeaderHandle: HWnd;
end;
TListViewStyleHookEx = class(Vcl.ComCtrls.TListViewStyleHook)
strict protected
procedure DrawHeaderSection(Canvas: TCanvas; R: TRect; Index: Integer;
const Text: string; IsPressed, IsBackground: Boolean); override;
end;
Fixing the method:
uses
Winapi.Commctrl;
function TListViewStyleHookHelper.getFHeaderHandle: HWnd;
begin
Result := Self.FHeaderHandle;
end;
procedure TListViewStyleHookEx.DrawHeaderSection(Canvas: TCanvas; R: TRect;
Index: Integer; const Text: string; IsPressed, IsBackground: Boolean);
var
Item: THDItem;
ImageList: HIMAGELIST;
DrawState: TThemedHeader;
IconWidth, IconHeight: Integer;
Details: TThemedElementDetails;
LListView: TListView;
DT_Align: Integer;
begin
FillChar(Item, SizeOf(Item), 0);
Item.mask := HDI_FORMAT;
Header_GetItem(getFHeaderHandle, Index, Item);
if IsBackground then
DrawState := thHeaderItemNormal
else if IsPressed then
DrawState := thHeaderItemPressed
else
DrawState := thHeaderItemNormal;
Details := StyleServices.GetElementDetails(DrawState);
StyleServices.DrawElement(Canvas.Handle, Details, R);
ImageList := SendMessage(getFHeaderHandle, HDM_GETIMAGELIST, 0, 0);
Item.mask := HDI_FORMAT or HDI_IMAGE;
InflateRect(R, -2, -2);
IconWidth := 0;
if (ImageList <> 0) and Header_GetItem(getFHeaderHandle, Index, Item) then
begin
if Item.fmt and HDF_IMAGE = HDF_IMAGE then
begin
ImageList_Draw(ImageList, Item.iImage, Canvas.Handle, R.Left, R.Top,
ILD_TRANSPARENT);
ImageList_GetIconSize(ImageList, IconWidth, IconHeight);
Inc(R.Left, IconWidth + 5);
end;
end;
if IconWidth = 0 then
Inc(R.Left, 2);
DT_Align := 0;
if Control is TListView then
begin
LListView := TListView(Control);
if (Index > -1) and (Index < LListView.Columns.Count) then
case LListView.Columns[Index].Alignment of
taLeftJustify:
DT_Align := 0;
taRightJustify:
DT_Align := 2;
taCenter:
DT_Align := 1;
end;
end;
DrawControlText(Canvas, Details, Text, R, DT_VCENTER or DT_Align or
DT_SINGLELINE or DT_END_ELLIPSIS);
end;
And finally we have to register our extended style hook for the TListView control:
Initialization
TCustomStyleEngine.RegisterStyleHook(TListView, TListViewStyleHookEx);
Finalization
TCustomStyleEngine.UnRegisterStyleHook(TListView, TListViewStyleHookEx);

Flat toolbar buttons with Delphi VCL Styles enabled?

Without VCL styles enabled, my TActionToolbar(s) look like flat toolbars. However, if I enable pretty much any VCL style, suddenly all the toolbar buttons look like 3d buttons.
The VCL Style Viewer app shows toolbar buttons with both flat and button-like appearance:
How can I make my TActionToolbar have the flat toolbar button style instead of looking like a bunch of buttons when I enable VCL Styles?
The draw methods used by all the controls related to the TActionManager are handled by a TPlatformDefaultStyleActionBars class from here the classes used to paint the controls are selected depending of the windows version, if the vcl styles are enabled and so on. On this case the csThemed TActionControlStyle is selected and the classes defined in the Vcl.ThemedActnCtrls unit are used.
So to modify the aspect of the buttons you need create a TActionBarStyleEx descendent class and then override the classes and methods defined in the Vcl.ThemedActnCtrls unit. fortunately this work was already done in the Vcl.PlatformVclStylesActnCtrls unit which is part of the Vcl Styles Utils project. So only you need make some small modifications in order to get desired results.
Try this sample (this is a modified version of the Vcl.PlatformVclStylesActnCtrls unit) I added some comments to show where the code must be modified.
unit Vcl.PlatformVclStylesActnCtrls;
interface
uses
Vcl.ActnMan,
Vcl.Buttons,
Vcl.PlatformDefaultStyleActnCtrls;
type
TPlatformVclStylesStyle = class(TPlatformDefaultStyleActionBars)
public
function GetControlClass(ActionBar: TCustomActionBar; AnItem: TActionClientItem): TCustomActionControlClass; override;
function GetStyleName: string; override;
end;
var
PlatformVclStylesStyle: TPlatformVclStylesStyle;
implementation
uses
Vcl.Menus,
Winapi.Windows,
System.SysUtils,
Vcl.ActnMenus,
Vcl.ActnCtrls,
Vcl.ThemedActnCtrls,
Vcl.Forms,
Vcl.ListActns,
Vcl.ActnColorMaps,
Vcl.Themes,
Vcl.XPActnCtrls,
Vcl.StdActnMenus,
Vcl.Graphics;
type
TActionControlStyle = (csStandard, csXPStyle, csThemed);
TThemedMenuItemEx = class(Vcl.ThemedActnCtrls.TThemedMenuItem)
private
procedure NativeDrawText(DC: HDC; const Text: string; var Rect: TRect; Flags: Longint);
protected
procedure DrawText(var Rect: TRect; var Flags: Cardinal; Text: string); override;
end;
TThemedMenuButtonEx = class(Vcl.ThemedActnCtrls.TThemedMenuButton)
private
procedure NativeDrawText(const Text: string; var Rect: TRect; Flags: Longint);
protected
procedure DrawText(var ARect: TRect; var Flags: Cardinal;
Text: string); override;
end;
TThemedMenuItemHelper = class Helper for TThemedMenuItem
private
function GetPaintRect: TRect;
property PaintRect: TRect read GetPaintRect;
end;
TThemedButtonControlEx = class(TThemedButtonControl)
protected
procedure DrawBackground(var PaintRect: TRect); override;
end;
{ TThemedMenuItemHelper }
function TThemedMenuItemHelper.GetPaintRect: TRect;
begin
Result:=Self.FPaintRect;
end;
function GetActionControlStyle: TActionControlStyle;
begin
if TStyleManager.IsCustomStyleActive then
Result := csThemed
else
if TOSVersion.Check(6) then
begin
if StyleServices.Theme[teMenu] <> 0 then
Result := csThemed
else
Result := csXPStyle;
end
else
if TOSVersion.Check(5, 1) then
Result := csXPStyle
else
Result := csStandard;
end;
{ TPlatformDefaultStyleActionBarsStyle }
function TPlatformVclStylesStyle.GetControlClass(ActionBar: TCustomActionBar;
AnItem: TActionClientItem): TCustomActionControlClass;
begin
if ActionBar is TCustomActionToolBar then
begin
if AnItem.HasItems then
case GetActionControlStyle of
csStandard: Result := TStandardDropDownButton;
csXPStyle: Result := TXPStyleDropDownBtn;
else
Result := TThemedDropDownButton;
end
else
if (AnItem.Action is TStaticListAction) or (AnItem.Action is TVirtualListAction) then
Result := TCustomComboControl
else
case GetActionControlStyle of
csStandard: Result := TStandardButtonControl;
csXPStyle: Result := TXPStyleButton;
else
Result := TThemedButtonControlEx;//this is the class used to draw the buttons of the TActionToolbar
end
end
else
if ActionBar is TCustomActionMainMenuBar then
case GetActionControlStyle of
csStandard: Result := TStandardMenuButton;
csXPStyle: Result := TXPStyleMenuButton;
else
Result := TThemedMenuButtonEx;
end
else
if ActionBar is TCustomizeActionToolBar then
begin
with TCustomizeActionToolbar(ActionBar) do
if not Assigned(RootMenu) or (AnItem.ParentItem <> TCustomizeActionToolBar(RootMenu).AdditionalItem) then
case GetActionControlStyle of
csStandard: Result := TStandardMenuItem;
csXPStyle: Result := TXPStyleMenuItem;
else
Result := TThemedMenuItemEx;
end
else
case GetActionControlStyle of
csStandard: Result := TStandardAddRemoveItem;
csXPStyle: Result := TXPStyleAddRemoveItem;
else
Result := TThemedAddRemoveItem;
end
end
else
if ActionBar is TCustomActionPopupMenu then
case GetActionControlStyle of
csStandard: Result := TStandardMenuItem;
csXPStyle: Result := TXPStyleMenuItem;
else
Result := TThemedMenuItemEx;
end
else
case GetActionControlStyle of
csStandard: Result := TStandardButtonControl;
csXPStyle: Result := TXPStyleButton;
else
Result := TThemedButtonControl;
end
end;
function TPlatformVclStylesStyle.GetStyleName: string;
begin
Result := 'Platform VclStyles Style';
end;
{ TThemedMenuItemEx }
procedure TThemedMenuItemEx.NativeDrawText(DC: HDC; const Text: string;
var Rect: TRect; Flags: Integer);
const
MenuStates: array[Boolean] of TThemedMenu = (tmPopupItemDisabled, tmPopupItemNormal);
var
LCaption: string;
LFormats: TTextFormat;
LColor: TColor;
LDetails: TThemedElementDetails;
LNativeStyle : TCustomStyleServices;
begin
LNativeStyle:=TStyleManager.SystemStyle;
LFormats := TTextFormatFlags(Flags);
if Selected and Enabled then
begin
LDetails := StyleServices.GetElementDetails(tmPopupItemHot);
if TOSVersion.Check(5, 1) then
SetBkMode(DC, Winapi.Windows.TRANSPARENT);
end
else
LDetails := StyleServices.GetElementDetails(MenuStates[Enabled or ActionBar.DesignMode]);
if not StyleServices.GetElementColor(LDetails, ecTextColor, LColor) or (LColor = clNone) then
LColor := ActionBar.ColorMap.FontColor;
LCaption := Text;
if (tfCalcRect in LFormats) and ( (LCaption = '') or (LCaption[1] = cHotkeyPrefix) and (LCaption[2] = #0) ) then
LCaption := LCaption + ' ';
LNativeStyle.DrawText(DC, LDetails, LCaption, Rect, LFormats, LColor);
end;
procedure TThemedMenuItemEx.DrawText(var Rect: TRect; var Flags: Cardinal;
Text: string);
var
LRect: TRect;
begin
if Selected and Enabled then
StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(tmPopupItemHot), PaintRect)
else if Selected then
StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(tmPopupItemDisabledHot), PaintRect);
if (Parent is TCustomActionBar) and (not ActionBar.PersistentHotkeys) then
Text := FNoPrefix;
Canvas.Font := Screen.MenuFont;
if ActionClient.Default then
Canvas.Font.Style := Canvas.Font.Style + [fsBold];
LRect := PaintRect;
NativeDrawText(Canvas.Handle, Text, LRect, Flags or DT_CALCRECT or DT_NOCLIP);
OffsetRect(LRect, Rect.Left,
((PaintRect.Bottom - PaintRect.Top) - (LRect.Bottom - LRect.Top)) div 2);
NativeDrawText(Canvas.Handle, Text, LRect, Flags);
if ShowShortCut and ((ActionClient <> nil) and not ActionClient.HasItems) then
begin
Flags := DrawTextBiDiModeFlags(DT_RIGHT);
LRect := TRect.Create(ShortCutBounds.Left, LRect.Top, ShortCutBounds.Right, LRect.Bottom);
LRect.Offset(Width, 0);
NativeDrawText(Canvas.Handle, ActionClient.ShortCutText, LRect, Flags);
end;
end;
{ TThemedMenuButtonEx }
procedure TThemedMenuButtonEx.NativeDrawText(const Text: string; var Rect: TRect;
Flags: Integer);
const
MenuStates: array[Boolean] of TThemedMenu = (tmMenuBarItemNormal, tmMenuBarItemHot);
var
LCaption: string;
LFormats: TTextFormat;
LColor: TColor;
LDetails: TThemedElementDetails;
LNativeStyle : TCustomStyleServices;
begin
LNativeStyle:=TStyleManager.SystemStyle;
LFormats := TTextFormatFlags(Flags);
if Enabled then
LDetails := StyleServices.GetElementDetails(MenuStates[Selected or MouseInControl or ActionBar.DesignMode])
else
LDetails := StyleServices.GetElementDetails(tmMenuBarItemDisabled);
Canvas.Brush.Style := bsClear;
if Selected then
Canvas.Font.Color := clHighlightText
else
Canvas.Font.Color := clMenuText;
if not StyleServices.GetElementColor(LDetails, ecTextColor, LColor) or (LColor = clNone) then
LColor := ActionBar.ColorMap.FontColor;
LCaption := Text;
if (tfCalcRect in LFormats) and ( (LCaption = '') or (LCaption[1] = cHotkeyPrefix) and (LCaption[2] = #0) ) then
LCaption := LCaption + ' ';
if Enabled then
LDetails := StyleServices.GetElementDetails(MenuStates[Selected or MouseInControl]);
LNativeStyle.DrawText(Canvas.Handle, LDetails, LCaption, Rect, LFormats, LColor);
end;
procedure TThemedMenuButtonEx.DrawText(var ARect: TRect; var Flags: Cardinal;
Text: string);
var
LRect: TRect;
begin
if Parent is TCustomActionMainMenuBar then
if not TCustomActionMainMenuBar(Parent).PersistentHotkeys then
Text := StripHotkey(Text);
LRect := ARect;
Inc(LRect.Left);
Canvas.Font := Screen.MenuFont;
NativeDrawText(Text, LRect, Flags or DT_CALCRECT or DT_NOCLIP);
NativeDrawText(Text, LRect, Flags);
end;
{ TThemedButtonControlEx }
//Here you must modify the code to draw the buttons
procedure TThemedButtonControlEx.DrawBackground(var PaintRect: TRect);
const
DisabledState: array[Boolean] of TThemedToolBar = (ttbButtonDisabled, ttbButtonPressed);
CheckedState: array[Boolean] of TThemedToolBar = (ttbButtonHot, ttbButtonCheckedHot);
var
SaveIndex: Integer;
begin
if not StyleServices.IsSystemStyle and ActionClient.Separator then Exit;
SaveIndex := SaveDC(Canvas.Handle);
try
if Enabled and not (ActionBar.DesignMode) then
begin
if (MouseInControl or IsChecked) and
Assigned(ActionClient) {and not ActionClient.Separator)} then
begin
StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(CheckedState[IsChecked or (FState = bsDown)]), PaintRect);
if not MouseInControl then
StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(ttbButtonPressed), PaintRect);
end
else
;//StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(ttbButtonNormal), PaintRect);// the code to draw the button in normal state was commented to get the desired look and feel
end
else
;//StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(DisabledState[IsChecked]), PaintRect);// the code to draw the button in disabled state was commented to get the desired look and feel
finally
RestoreDC(Canvas.Handle, SaveIndex);
end;
end;
initialization
PlatformVclStylesStyle := TPlatformVclStylesStyle.Create;
RegisterActnBarStyle(PlatformVclStylesStyle);
DefaultActnBarStyle :=PlatformVclStylesStyle.GetStyleName;
finalization
UnregisterActnBarStyle(PlatformVclStylesStyle);
PlatformVclStylesStyle.Free;
end.
To use it only add the Vcl.PlatformVclStylesActnCtrls unit to your project and then set the Style of your TActionManager like so :
ActionManager1.Style:=PlatformVclStylesStyle;
Before
After

Tag editor component for Delphi/C++Builder

I need a VCL tag editor component for Delphi or C++Builder, similar to what's available for JavaScript: e.g. this one, or this one or StackOverflow's own tags editor.
Is there something like this available or do I need to make it from scratch?
Some specific things that I need are:
Editor should allow either scrolling or become multi-line if more tags are present than the editor's width allows. If multi-line, there should be an option to define some maximum height however, preventing it from becoming too tall
Option to select whether tags are created when pressing space or comma key
Prompt text in the editor, when it is not focused (for example "Add new tag")
Ideally, you should be able to move between tags (highlighting them) using the keyboard arrows, so you can delete any tag using the keyboard only
Of course you want to do this yourself! Writing GUI controls is fun and rewarding!
You could do something like
unit TagEditor;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, StdCtrls, Forms, Graphics,
Types, Menus;
type
TClickInfo = cardinal;
GetTagIndex = word;
const TAG_LOW = 0;
const TAG_HIGH = MAXWORD - 2;
const EDITOR = MAXWORD - 1;
const NOWHERE = MAXWORD;
const PART_BODY = $00000000;
const PART_REMOVE_BUTTON = $00010000;
function GetTagPart(ClickInfo: TClickInfo): cardinal;
type
TTagClickEvent = procedure(Sender: TObject; TagIndex: integer;
const TagCaption: string) of object;
TRemoveConfirmEvent = procedure(Sender: TObject; TagIndex: integer;
const TagCaption: string; var CanRemove: boolean) of object;
TTagEditor = class(TCustomControl)
private
{ Private declarations }
FTags: TStringList;
FEdit: TEdit;
FBgColor: TColor;
FBorderColor: TColor;
FTagBgColor: TColor;
FTagBorderColor: TColor;
FSpacing: integer;
FTextColor: TColor;
FLefts, FRights, FWidths,
FTops, FBottoms: array of integer;
FCloseBtnLefts, FCloseBtnTops: array of integer;
FCloseBtnWidth: integer;
FSpaceAccepts: boolean;
FCommaAccepts: boolean;
FSemicolonAccepts: boolean;
FTrimInput: boolean;
FNoLeadingSpaceInput: boolean;
FTagClickEvent: TTagClickEvent;
FAllowDuplicates: boolean;
FPopupMenu: TPopupMenu;
FMultiLine: boolean;
FTagHeight: integer;
FEditPos: TPoint;
FActualTagHeight: integer;
FShrunk: boolean;
FEditorColor: TColor;
FTagAdded: TNotifyEvent;
FTagRemoved: TNotifyEvent;
FOnChange: TNotifyEvent;
FOnRemoveConfirm: TRemoveConfirmEvent;
FMouseDownClickInfo: TClickInfo;
FCaretVisible: boolean;
FDragging: boolean;
FAutoHeight: boolean;
FNumRows: integer;
procedure SetBorderColor(const Value: TColor);
procedure SetTagBgColor(const Value: TColor);
procedure SetTagBorderColor(const Value: TColor);
procedure SetSpacing(const Value: integer);
procedure TagChange(Sender: TObject);
procedure SetTags(const Value: TStringList);
procedure SetTextColor(const Value: TColor);
procedure ShowEditor;
procedure HideEditor;
procedure EditKeyPress(Sender: TObject; var Key: Char);
procedure mnuDeleteItemClick(Sender: TObject);
procedure SetMultiLine(const Value: boolean);
procedure SetTagHeight(const Value: integer);
procedure EditExit(Sender: TObject);
function Accept: boolean;
procedure SetBgColor(const Value: TColor);
function GetClickInfoAt(X, Y: integer): TClickInfo;
function GetSeparatorIndexAt(X, Y: integer): integer;
procedure CreateCaret;
procedure DestroyCaret;
function IsFirstOnRow(TagIndex: integer): boolean; inline;
function IsLastOnRow(TagIndex: integer): boolean;
procedure SetAutoHeight(const Value: boolean);
protected
{ Protected declarations }
procedure Paint; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer;
Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
procedure KeyPress(var Key: Char); override;
procedure WndProc(var Message: TMessage); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer;
Y: Integer); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
property TabOrder;
property TabStop;
property Color;
property Anchors;
property Align;
property Tag;
property Cursor;
property BgColor: TColor read FBgColor write SetBgColor;
property BorderColor: TColor read FBorderColor write SetBorderColor;
property TagBgColor: TColor read FTagBgColor write SetTagBgColor;
property TagBorderColor: TColor read FTagBorderColor
write SetTagBorderColor;
property Spacing: integer read FSpacing write SetSpacing;
property Tags: TStringList read FTags write SetTags;
property TextColor: TColor read FTextColor write SetTextColor;
property SpaceAccepts: boolean read FSpaceAccepts write FSpaceAccepts
default true;
property CommaAccepts: boolean read FCommaAccepts write FCommaAccepts
default true;
property SemicolonAccepts: boolean read FSemicolonAccepts
write FSemicolonAccepts default true;
property TrimInput: boolean read FTrimInput write FTrimInput default true;
property NoLeadingSpaceInput: boolean read FNoLeadingSpaceInput
write FNoLeadingSpaceInput default true;
property AllowDuplicates: boolean read FAllowDuplicates write FAllowDuplicates
default false;
property MultiLine: boolean read FMultiLine write SetMultiLine default false;
property TagHeight: integer read FTagHeight write SetTagHeight default 32;
property EditorColor: TColor read FEditorColor write FEditorColor
default clWindow;
property AutoHeight: boolean read FAutoHeight write SetAutoHeight;
property OnTagClick: TTagClickEvent read FTagClickEvent write FTagClickEvent;
property OnTagAdded: TNotifyEvent read FTagAdded write FTagAdded;
property OnTagRemoved: TNotifyEvent read FTagRemoved write FTagRemoved;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnRemoveConfirm: TRemoveConfirmEvent read FOnRemoveConfirm
write FOnRemoveConfirm;
end;
procedure Register;
implementation
uses Math, Clipbrd;
procedure Register;
begin
RegisterComponents('Rejbrand 2009', [TTagEditor]);
end;
function IsKeyDown(const VK: integer): boolean;
begin
IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;
function GetTagPart(ClickInfo: TClickInfo): cardinal;
begin
result := ClickInfo and $FFFF0000;
end;
{ TTagEditor }
constructor TTagEditor.Create(AOwner: TComponent);
var
mnuItem: TMenuItem;
begin
inherited;
FEdit := TEdit.Create(Self);
FEdit.Parent := Self;
FEdit.BorderStyle := bsNone;
FEdit.Visible := false;
FEdit.OnKeyPress := EditKeyPress;
FEdit.OnExit := EditExit;
FTags := TStringList.Create;
FTags.OnChange := TagChange;
FBgColor := clWindow;
FBorderColor := clWindowFrame;
FTagBgColor := clSkyBlue;
FTagBorderColor := clNavy;
FSpacing := 8;
FTextColor := clWhite;
FSpaceAccepts := true;
FCommaAccepts := true;
FSemicolonAccepts := true;
FTrimInput := true;
FNoLeadingSpaceInput := true;
FAllowDuplicates := false;
FMultiLine := false;
FTagHeight := 32;
FShrunk := false;
FEditorColor := clWindow;
FCaretVisible := false;
FDragging := false;
FPopupMenu := TPopupMenu.Create(Self);
mnuItem := TMenuItem.Create(PopupMenu);
mnuItem.Caption := 'Delete';
mnuItem.OnClick := mnuDeleteItemClick;
mnuItem.Hint := 'Deletes the selected tag.';
FPopupMenu.Items.Add(mnuItem);
TabStop := true;
end;
procedure TTagEditor.EditExit(Sender: TObject);
begin
if FEdit.Text <> '' then
Accept
else
HideEditor;
end;
procedure TTagEditor.mnuDeleteItemClick(Sender: TObject);
begin
if Sender is TMenuItem then
begin
FTags.Delete(TMenuItem(Sender).Tag);
if Assigned(FTagRemoved) then
FTagRemoved(Self);
end;
end;
procedure TTagEditor.TagChange(Sender: TObject);
begin
Invalidate;
if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TTagEditor.WndProc(var Message: TMessage);
begin
inherited;
case Message.Msg of
WM_SETFOCUS:
Invalidate;
WM_KILLFOCUS:
begin
if FCaretVisible then DestroyCaret;
FDragging := false;
Invalidate;
end;
WM_COPY:
Clipboard.AsText := FTags.DelimitedText;
WM_CLEAR:
FTags.Clear;
WM_CUT:
begin
Clipboard.AsText := FTags.DelimitedText;
FTags.Clear;
end;
WM_PASTE:
begin
if Clipboard.HasFormat(CF_TEXT) then
if FTags.Count = 0 then
FTags.DelimitedText := Clipboard.AsText
else
FTags.DelimitedText := FTags.DelimitedText + ',' + Clipboard.AsText;
end;
end;
end;
function TTagEditor.Accept: boolean;
begin
Assert(FEdit.Visible);
result := false;
if FTrimInput then
FEdit.Text := Trim(FEdit.Text);
if (FEdit.Text = '') or
((not AllowDuplicates) and (FTags.IndexOf(FEdit.Text) <> -1)) then
begin
beep;
Exit;
end;
FTags.Add(FEdit.Text);
result := true;
HideEditor;
if Assigned(FTagAdded) then
FTagAdded(Self);
Invalidate;
end;
procedure TTagEditor.EditKeyPress(Sender: TObject; var Key: Char);
begin
if (Key = chr(VK_SPACE)) and (FEdit.Text = '') and FNoLeadingSpaceInput then
begin
Key := #0;
Exit;
end;
if ((Key = chr(VK_SPACE)) and FSpaceAccepts) or
((Key = ',') and FCommaAccepts) or
((Key = ';') and FSemicolonAccepts) then
Key := chr(VK_RETURN);
case ord(Key) of
VK_RETURN:
begin
Accept;
ShowEditor;
Key := #0;
end;
VK_BACK:
begin
if (FEdit.Text = '') and (FTags.Count > 0) then
begin
FTags.Delete(FTags.Count - 1);
if Assigned(FTagRemoved) then
FTagRemoved(Sender);
end;
end;
VK_ESCAPE:
begin
HideEditor;
Self.SetFocus;
Key := #0;
end;
end;
end;
destructor TTagEditor.Destroy;
begin
FPopupMenu.Free;
FTags.Free;
FEdit.Free;
inherited;
end;
procedure TTagEditor.HideEditor;
begin
FEdit.Text := '';
FEdit.Hide;
// SetFocus;
end;
procedure TTagEditor.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited;
case Key of
VK_END:
ShowEditor;
VK_DELETE:
Perform(WM_CLEAR, 0, 0);
VK_INSERT:
Perform(WM_PASTE, 0, 0);
end;
end;
procedure TTagEditor.KeyPress(var Key: Char);
begin
inherited;
case Key of
^C:
begin
Perform(WM_COPY, 0, 0);
Key := #0;
Exit;
end;
^X:
begin
Perform(WM_CUT, 0, 0);
Key := #0;
Exit;
end;
^V:
begin
Perform(WM_PASTE, 0, 0);
Key := #0;
Exit;
end;
end;
ShowEditor;
FEdit.Perform(WM_CHAR, ord(Key), 0);
end;
function TTagEditor.GetClickInfoAt(X, Y: integer): TClickInfo;
var
i: integer;
begin
result := NOWHERE;
if (X >= FEditPos.X) and (Y >= FEditPos.Y) then
Exit(EDITOR);
for i := 0 to FTags.Count - 1 do
if InRange(X, FLefts[i], FRights[i]) and InRange(Y, FTops[i], FBottoms[i]) then
begin
result := i;
if InRange(X, FCloseBtnLefts[i], FCloseBtnLefts[i] + FCloseBtnWidth) and
InRange(Y, FCloseBtnTops[i], FCloseBtnTops[i] + FActualTagHeight) and
not FShrunk then
result := result or PART_REMOVE_BUTTON;
break;
end;
end;
function TTagEditor.IsFirstOnRow(TagIndex: integer): boolean;
begin
result := (TagIndex = 0) or (FTops[TagIndex] > FTops[TagIndex-1]);
end;
function TTagEditor.IsLastOnRow(TagIndex: integer): boolean;
begin
result := (TagIndex = FTags.Count - 1) or (FTops[TagIndex] < FTops[TagIndex+1]);
end;
function TTagEditor.GetSeparatorIndexAt(X, Y: integer): integer;
var
i: Integer;
begin
result := FTags.Count;
Y := Max(Y, FSpacing + 1);
for i := FTags.Count - 1 downto 0 do
begin
if Y < FTops[i] then Continue;
if (IsLastOnRow(i) and (X >= FRights[i])) or
((X < FRights[i]) and (IsFirstOnRow(i) or (FRights[i-1] < X))) then
begin
result := i;
if (IsLastOnRow(i) and (X >= FRights[i])) then inc(result);
Exit;
end;
end;
end;
procedure TTagEditor.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
FMouseDownClickInfo := GetClickInfoAt(X, Y);
if GetTagIndex(FMouseDownClickInfo) <> EDITOR then
SetFocus;
end;
procedure TTagEditor.CreateCaret;
begin
if not FCaretVisible then
FCaretVisible := Windows.CreateCaret(Handle, 0, 0, FActualTagHeight);
end;
procedure TTagEditor.DestroyCaret;
begin
if not FCaretVisible then Exit;
Windows.DestroyCaret;
FCaretVisible := false;
end;
procedure TTagEditor.MouseMove(Shift: TShiftState; X, Y: Integer);
var
SepIndex: integer;
begin
inherited;
if IsKeyDown(VK_LBUTTON) and
InRange(GetTagIndex(FMouseDownClickInfo), TAG_LOW, TAG_HIGH) then
begin
FDragging := true;
Screen.Cursor := crDrag;
SepIndex := GetSeparatorIndexAt(X, Y);
TForm(Parent).Caption := IntToStr(SepIndex);
CreateCaret;
if SepIndex = FTags.Count then
SetCaretPos(FLefts[SepIndex - 1] + FWidths[SepIndex - 1] + FSpacing div 2,
FTops[SepIndex - 1])
else
SetCaretPos(FLefts[SepIndex] - FSpacing div 2, FTops[SepIndex]);
ShowCaret(Handle);
Exit;
end;
case GetTagIndex(GetClickInfoAt(X,Y)) of
NOWHERE: Cursor := crArrow;
EDITOR: Cursor := crIBeam;
TAG_LOW..TAG_HIGH: Cursor := crHandPoint;
end;
end;
procedure TTagEditor.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
var
pnt: TPoint;
CanRemove: boolean;
ClickInfo: TClickInfo;
i: word;
p: cardinal;
SepIndex: integer;
begin
inherited;
if FDragging then
begin
DestroyCaret;
FDragging := false;
Screen.Cursor := crDefault;
SepIndex := GetSeparatorIndexAt(X, Y);
if not InRange(SepIndex, GetTagIndex(FMouseDownClickInfo),
GetTagIndex(FMouseDownClickInfo) + 1) then
FTags.Move(GetTagIndex(FMouseDownClickInfo), SepIndex -
IfThen(SepIndex > GetTagIndex(FMouseDownClickInfo), 1, 0));
Exit;
end;
ClickInfo := GetClickInfoAt(X, Y);
if ClickInfo <> FMouseDownClickInfo then Exit;
i := GetTagIndex(ClickInfo);
p := GetTagPart(ClickInfo);
case i of
EDITOR:
ShowEditor;
NOWHERE: ;
else
case Button of
mbLeft:
begin
case p of
PART_BODY:
if Assigned(FTagClickEvent) then
FTagClickEvent(Self, i, FTags[i]);
PART_REMOVE_BUTTON:
begin
if Assigned(FOnRemoveConfirm) then
begin
CanRemove := false;
FOnRemoveConfirm(Self, i, FTags[i], CanRemove);
if not CanRemove then Exit;
end;
FTags.Delete(i);
if Assigned(FTagRemoved) then
FTagRemoved(Self);
end;
end;
end;
mbRight:
begin
FPopupMenu.Items[0].Tag := i;
pnt := ClientToScreen(Point(X,Y));
FPopupMenu.Items[0].Caption := 'Delete tag "' + FTags[i] + '"';
FPopupMenu.Popup(pnt.X, pnt.Y);
end;
end;
end;
end;
procedure TTagEditor.Paint;
var
i: integer;
w: integer;
x, y: integer;
R: TRect;
MeanWidth: integer;
S: string;
DesiredHeight: integer;
begin
inherited;
Canvas.Brush.Color := FBgColor;
Canvas.Pen.Color := FBorderColor;
Canvas.Rectangle(ClientRect);
Canvas.Font.Assign(Self.Font);
SetLength(FLefts, FTags.Count);
SetLength(FRights, FTags.Count);
SetLength(FTops, FTags.Count);
SetLength(FBottoms, FTags.Count);
SetLength(FWidths, FTags.Count);
SetLength(FCloseBtnLefts, FTags.Count);
SetLength(FCloseBtnTops, FTags.Count);
FCloseBtnWidth := Canvas.TextWidth('×');
FShrunk := false;
// Do metrics
FNumRows := 1;
if FMultiLine then
begin
FActualTagHeight := FTagHeight;
x := FSpacing;
y := FSpacing;
for i := 0 to FTags.Count - 1 do
begin
FWidths[i] := Canvas.TextWidth(FTags[i] + ' ×') + 2*FSpacing;
FLefts[i] := x;
FRights[i] := x + FWidths[i];
FTops[i] := y;
FBottoms[i] := y + FTagHeight;
if x + FWidths[i] + FSpacing > ClientWidth then
{ no need to make room for the editor, since it can reside on the next row! }
begin
x := FSpacing;
inc(y, FTagHeight + FSpacing);
inc(FNumRows);
FLefts[i] := x;
FRights[i] := x + FWidths[i];
FTops[i] := y;
FBottoms[i] := y + FTagHeight;
end;
FCloseBtnLefts[i] := x + FWidths[i] - FCloseBtnWidth - FSpacing;
FCloseBtnTops[i] := y;
inc(x, FWidths[i] + FSpacing);
end;
end
else // i.e., not FMultiLine
begin
FActualTagHeight := ClientHeight - 2*FSpacing;
x := FSpacing;
y := FSpacing;
for i := 0 to FTags.Count - 1 do
begin
FWidths[i] := Canvas.TextWidth(FTags[i] + ' ×') + 2*FSpacing;
FLefts[i] := x;
FRights[i] := x + FWidths[i];
FTops[i] := y;
FBottoms[i] := y + FActualTagHeight;
inc(x, FWidths[i] + FSpacing);
FCloseBtnLefts[i] := FRights[i] - FCloseBtnWidth - FSpacing;
FCloseBtnTops[i] := y;
end;
FShrunk := x + 64 {FEdit} > ClientWidth;
if FShrunk then
begin
// Enough to remove close buttons?
x := FSpacing;
y := FSpacing;
for i := 0 to FTags.Count - 1 do
begin
FWidths[i] := Canvas.TextWidth(FTags[i]) + 2*FSpacing;
FLefts[i] := x;
FRights[i] := x + FWidths[i];
FTops[i] := y;
FBottoms[i] := y + FActualTagHeight;
inc(x, FWidths[i] + FSpacing);
FCloseBtnLefts[i] := FRights[i] - FCloseBtnWidth - FSpacing;
FCloseBtnTops[i] := y;
end;
if x + 64 {FEdit} > ClientWidth then // apparently no
begin
MeanWidth := (ClientWidth - 2*FSpacing - 64 {FEdit}) div FTags.Count - FSpacing;
x := FSpacing;
for i := 0 to FTags.Count - 1 do
begin
FWidths[i] := Min(FWidths[i], MeanWidth);
FLefts[i] := x;
FRights[i] := x + FWidths[i];
inc(x, FWidths[i] + FSpacing);
end;
end;
end;
end;
FEditPos := Point(FSpacing, FSpacing + (FActualTagHeight - FEdit.Height) div 2);
if FTags.Count > 0 then
FEditPos := Point(FRights[FTags.Count - 1] + FSpacing,
FTops[FTags.Count - 1] + (FActualTagHeight - FEdit.Height) div 2);
if FMultiLine and (FEditPos.X + 64 > ClientWidth) and (FTags.Count > 0) then
begin
FEditPos := Point(FSpacing,
FTops[FTags.Count - 1] + FTagHeight + FSpacing +
(FActualTagHeight - FEdit.Height) div 2);
inc(FNumRows);
end;
DesiredHeight := FSpacing + FNumRows*(FTagHeight+FSpacing);
if FMultiLine and FAutoHeight and (ClientHeight <> DesiredHeight) then
begin
ClientHeight := DesiredHeight;
Invalidate;
Exit;
end;
// Draw
for i := 0 to FTags.Count - 1 do
begin
x := FLefts[i];
y := FTops[i];
w := FWidths[i];
R := Rect(x, y, x + w, y + FActualTagHeight);
Canvas.Brush.Color := FTagBgColor;
Canvas.Pen.Color := FTagBorderColor;
Canvas.Rectangle(R);
Canvas.Font.Color := FTextColor;
Canvas.Brush.Style := bsClear;
R.Left := R.Left + FSpacing;
S := FTags[i];
if not FShrunk then
S := S + ' ×';
DrawText(Canvas.Handle, PChar(S), -1, R, DT_SINGLELINE or DT_VCENTER or
DT_LEFT or DT_END_ELLIPSIS or DT_NOPREFIX);
Canvas.Brush.Style := bsSolid;
end;
if FEdit.Visible then
begin
FEdit.Left := FEditPos.X;
FEdit.Top := FEditPos.Y;
FEdit.Width := ClientWidth - FEdit.Left - FSpacing;
end;
if Focused then
begin
R := Rect(2, 2, ClientWidth - 2, ClientHeight - 2);
SetBkColor(Canvas.Handle, clWhite);
SetTextColor(clBlack);
Canvas.DrawFocusRect(R);
end;
end;
procedure TTagEditor.SetAutoHeight(const Value: boolean);
begin
if FAutoHeight <> Value then
begin
FAutoHeight := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetBgColor(const Value: TColor);
begin
if FBgColor <> Value then
begin
FBgColor := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetBorderColor(const Value: TColor);
begin
if FBorderColor <> Value then
begin
FBorderColor := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetMultiLine(const Value: boolean);
begin
if FMultiLine <> Value then
begin
FMultiLine := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetTagBgColor(const Value: TColor);
begin
if FTagBgColor <> Value then
begin
FTagBgColor := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetTagBorderColor(const Value: TColor);
begin
if FTagBorderColor <> Value then
begin
FTagBorderColor := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetTagHeight(const Value: integer);
begin
if FTagHeight <> Value then
begin
FTagHeight := Value;
Invalidate;
end;
end;
procedure TTagEditor.SetTags(const Value: TStringList);
begin
FTags.Assign(Value);
Invalidate;
end;
procedure TTagEditor.SetTextColor(const Value: TColor);
begin
if FTextColor <> Value then
begin
FTextColor := Value;
Invalidate;
end;
end;
procedure TTagEditor.ShowEditor;
begin
FEdit.Left := FEditPos.X;
FEdit.Top := FEditPos.Y;
FEdit.Width := ClientWidth - FEdit.Left - FSpacing;
FEdit.Color := FEditorColor;
FEdit.Text := '';
FEdit.Show;
FEdit.SetFocus;
end;
procedure TTagEditor.SetSpacing(const Value: integer);
begin
if FSpacing <> Value then
begin
FSpacing := Value;
Invalidate;
end;
end;
initialization
Screen.Cursors[crHandPoint] := LoadCursor(0, IDC_HAND); // Get the normal hand cursor
end.
which yields
Sample video
Demo (Compiled EXE)
If I get more time later on today I will do some more work on this control, e.g., button highlighting on mouse hover, tag click event, button max width etc.
Update: Added a lot of features.
Update: Added multi-line feature.
Update: More features.
Update: Added clipboard interface, fixed some issues, etc.
Update: Added drag-and-drop reordering and fixed some minor issues. By the way, this is the last version I'll post here. Later versions (if there will be any) will be posted at http://specials.rejbrand.se/dev/controls/.
Update: Added AutoHeight property, made edit box vertically centred, and changed the drag cursor. (Yeah, I couldn't resist making yet another update.)

Resources