Two missing parts of the standard VCL Styles is the lack of the capacity to theme the popup menus and the standard Windows dialogs. I started to work a year ago in the dialogs area, but due to my limited time I was not able to finish that. But a few months ago I receive a very interesting mail from Mahdi Safsafi (SMP3) that show me his own work on this topic. So we decided merge the code of his project and the VCL Styles Utils. So finally the VCL Styles Utils project was updated to support standard dialogs, popup and system menus.
How it works?
The key is using a WH_CBT Hook, detecting the HCBT_CREATEWND and HCBT_DESTROYWND codes and then checking if the class of the window is #32770 (the class for a dialog box.) or the #32768 (the class for a popupmenu) from here you can replace the window procedure (WndProc) using the SetWindowLongPtr function with the GWL_WNDPROC index. Now we have the control of the messages sent by the windows dialogs and menus and we can iterate over the child controls and replace the window procedure again using the GWL_WNDPROC index. Finally depending of the class of the control (button, syslistview32, Combobox and so on) a Wrapper class (like the VCL does) is created to handle the messages related to the paint of the control.
Check the next source code which install the hook and process the Win32 controls
unit Vcl.Styles.SysControls;
interface
implementation
uses
Winapi.Windows,
System.Generics.Collections,
System.SysUtils,
Vcl.Controls,
Vcl.Dialogs,
Vcl.Styles,
Vcl.Themes,
Vcl.Styles.PopupWnd,
Vcl.Styles.EditWnd,
Vcl.Styles.StaticWnd,
Vcl.Styles.ThemedDialog,
Vcl.Styles.ToolbarWindow32Wnd,
Vcl.Styles.SysListView32Wnd,
Vcl.Styles.ButtonWnd,
Vcl.Styles.UnknownControlWnd,
Vcl.Styles.ControlWnd,
Vcl.Styles.ComboBoxWnd,
Vcl.Styles.ToolTipsWnd;
type
TThemedSysControls = class
private
class var
FBalloonHint: TBalloonHint;
FPreviousSysBtn: Integer;
FPreviousHandle: THandle;
FHook: HHook;
protected
class function HookActionCallBack(Code: Integer; wParam: wParam;
lParam: lParam): LRESULT; stdcall; static;
procedure InstallHook;
procedure RemoveHook;
public
constructor Create; overload;
destructor Destroy; override;
end;
var
MenuItemInfoArray: array of TMenuItemInfo;
TooltipsWndList: TObjectDictionary<HWND, TooltipsWnd>;
PopupWndList: TObjectDictionary<HWND, TPopupWnd>;
StaticWndList: TObjectDictionary<HWND, TStaticWnd>;
DialogWndList: TObjectDictionary<HWND, TDialogWnd>;
EditWndList: TObjectDictionary<HWND, TEditWnd>;
ComboBoxWndList: TObjectDictionary<HWND, TComboBoxWnd>;
UnknownControlList: TObjectDictionary<HWND, TUnknownControlWnd>;
ToolbarWindow32WndList : TObjectDictionary<HWND, TToolbarWindow32Wnd>;
SysListView32WndList : TObjectDictionary<HWND, TSysListView32Wnd>;
BtnWndArrayList : TObjectDictionary<HWND, TButtonWnd>;
ThemedSysControls: TThemedSysControls;
{ TThemedSysControls }
constructor TThemedSysControls.Create;
begin
inherited;
FBalloonHint := TBalloonHint.Create(nil);
FBalloonHint.Style := bhsStandard;
FBalloonHint.Delay := 1500;
FBalloonHint.HideAfter := 3000;
FPreviousHandle := 0;
FHook := 0;
InstallHook;
PopupWndList:= TObjectDictionary<HWND, TPopupWnd>.Create([doOwnsValues]);
TooltipsWndList:= TObjectDictionary<HWND, TooltipsWnd>.Create([doOwnsValues]);
StaticWndList:= TObjectDictionary<HWND, TStaticWnd>.Create([doOwnsValues]);
DialogWndList:= TObjectDictionary<HWND,TDialogWnd>.Create([doOwnsValues]);
EditWndList:= TObjectDictionary<HWND, TEditWnd>.Create([doOwnsValues]);
ComboBoxWndList:= TObjectDictionary<HWND, TComboBoxWnd>.Create([doOwnsValues]);
UnknownControlList:= TObjectDictionary<HWND, TUnknownControlWnd>.Create([doOwnsValues]);
ToolbarWindow32WndList:= TObjectDictionary<HWND, TToolbarWindow32Wnd>.Create([doOwnsValues]);
SysListView32WndList := TObjectDictionary<HWND, TSysListView32Wnd>.Create([doOwnsValues]);
BtnWndArrayList := TObjectDictionary<HWND, TButtonWnd>.Create([doOwnsValues]);
end;
destructor TThemedSysControls.Destroy;
begin
RemoveHook;
PopupWndList.Free;
TooltipsWndList.Free;
StaticWndList.Free;
DialogWndList.Free;
EditWndList.Free;
ComboBoxWndList.Free;
UnknownControlList.Free;
ToolbarWindow32WndList.Free;
SysListView32WndList.Free;
BtnWndArrayList.Free;
FBalloonHint.Free;
inherited;
end;
class function TThemedSysControls.HookActionCallBack(Code: Integer;
wParam: wParam; lParam: lParam): LRESULT;
var
Msg: TMOUSEHOOKSTRUCT;
C: array [0 .. 256] of Char;
procedure HideSysToolTip;
var
hSysToolTip: THandle;
begin
For hSysToolTip := 65550 To 65600 do
begin
If IsWindowVisible(hSysToolTip) then
begin
GetClassName(hSysToolTip, C, 256);
ShowWindow(hSysToolTip, SW_HIDE);
end;
end;
end;
procedure ShowToolTip(HintTitle: String);
begin
HideSysToolTip;
if FPreviousSysBtn <> Integer(Msg.wHitTestCode) then
begin
FBalloonHint.HideHint;
FBalloonHint.Title := HintTitle;
FPreviousSysBtn := Msg.wHitTestCode;
FBalloonHint.ShowHint(Msg.pt);
end;
end;
var
CBTSturct: TCBTCreateWnd;
sClassName : string;
begin
if (StyleServices.Enabled) and not (StyleServices.IsSystemStyle) then
begin
if Code = HCBT_SYSCOMMAND then
begin
FBalloonHint.HideHint;
FPreviousSysBtn := 0;
end
else
if Code = HCBT_DESTROYWND then
begin
sClassName := GetWindowClassName(wParam);
if sClassName = '#32768' then
{PopupMenu}
begin
if PopupWndList.ContainsKey(wParam) then
PopupWndList.Remove(wParam);
//OutputDebugString(PChar('remove PopupWndList count '+IntToStr(PopupWndList.Count)));
end
else
if sClassName = '#32770' then
{Dialog}
begin
if DialogWndList.ContainsKey(wParam) then
DialogWndList.Remove(wParam);
//OutputDebugString(PChar('remove DialogWndList count '+IntToStr(DialogWndList.Count)));
end
else
if sClassName = 'Button' then
{Button}
begin
if BtnWndArrayList.ContainsKey(wParam) then
BtnWndArrayList.Remove(wParam);
//OutputDebugString(PChar('remove BtnWndArrayList count '+IntToStr(BtnWndArrayList.Count)));
end
else
if (sClassName = 'ScrollBar') or (sClassName = 'ReBarWindow32') {or (sClassName = 'ToolbarWindow32')} then
begin
if UnknownControlList.ContainsKey(wParam) then
UnknownControlList.Remove(wParam);
end
else
if sClassName = 'SysListView32' then
begin
if SysListView32WndList.ContainsKey(wParam) then
SysListView32WndList.Remove(wParam);
end
else
if sClassName = 'ToolbarWindow32' then
begin
if ToolbarWindow32WndList.ContainsKey(wParam) then
ToolbarWindow32WndList.Remove(wParam);
end
else
if sClassName = 'Edit' then
begin
if EditWndList.ContainsKey(wParam) then
EditWndList.Remove(wParam);
end
else
if sClassName = 'Static' then
begin
if StaticWndList.ContainsKey(wParam) then
StaticWndList.Remove(wParam);
end
else
if sClassName = 'ComboBox' then
begin
if ComboBoxWndList.ContainsKey(wParam) then
ComboBoxWndList.Remove(wParam);
end
else
if sClassName = 'tooltips_class32' then
begin
if TooltipsWndList.ContainsKey(wParam) then
TooltipsWndList.Remove(wParam);
end
end
else
if Code = HCBT_CREATEWND then
begin
CBTSturct := PCBTCreateWnd(lParam)^;
sClassName := GetWindowClassName(wParam);
//PopupMenu
if Integer(CBTSturct.lpcs.lpszClass) = 32768 then
PopupWndList.Add(wParam, TPopupWnd.Create(wParam))
else
//Dialog
if Integer(CBTSturct.lpcs.lpszClass) = 32770 then
begin
if (CBTSturct.lpcs.cx <> 0) and (CBTSturct.lpcs.cy <> 0) then
DialogWndList.Add(wParam, TDialogWnd.Create(wParam))
end
else
if sClassName = 'Button' then
BtnWndArrayList.Add(wParam, TButtonWnd.Create(wParam))
else
if (sClassName = 'ScrollBar') or (sClassName = 'ReBarWindow32') {or (sClassName = 'ToolbarWindow32')} then
UnknownControlList.Add(wParam, TUnknownControlWnd.Create(wParam))
else
if sClassName = 'SysListView32' then
SysListView32WndList.Add(wParam, TSysListView32Wnd.Create(wParam))
else
if sClassName = 'ToolbarWindow32' then
begin
if not UseLatestCommonDialogs then
ToolbarWindow32WndList.Add(wParam, TToolbarWindow32Wnd.Create(wParam));
end
else
if sClassName = 'Edit' then
EditWndList.Add(wParam, TEditWnd.Create(wParam))
else
if sClassName = 'Static' then
begin
{ This condition can solve the Edit animated cursor : see ColorDialog !! }
if (CBTSturct.lpcs.Style and SS_ICON <> SS_ICON) and
(CBTSturct.lpcs.Style and SS_BITMAP <> SS_BITMAP) and
(CBTSturct.lpcs.Style and SS_GRAYRECT <> SS_GRAYRECT) and
(CBTSturct.lpcs.Style and SS_GRAYFRAME <> SS_GRAYFRAME) then
StaticWndList.Add(wParam, TStaticWnd.Create(wParam));
end
else
if sClassName = 'ComboBox' then
ComboBoxWndList.Add(wParam, TComboBoxWnd.Create(wParam))
else
if sClassName = 'tooltips_class32' then
TooltipsWndList.Add(wParam, TooltipsWnd.Create(wParam))
end
end;
Result := CallNextHookEx(FHook, Code, wParam, lParam);
end;
procedure TThemedSysControls.InstallHook;
begin
FHook := SetWindowsHookEx(WH_CBT, @TThemedSysControls.HookActionCallBack, 0, GetCurrentThreadId);
end;
procedure TThemedSysControls.RemoveHook;
begin
if FHook <> 0 then
UnhookWindowsHookEx(FHook);
end;
initialization
ThemedSysControls:=nil;
if StyleServices.Available then
ThemedSysControls := TThemedSysControls.Create;
finalization
if Assigned(ThemedSysControls) then
ThemedSysControls.Free;
end.
Menus
Standard TMainMenu with VCL Styles Enabled.

using the Vcl.Styles.SysControls unit

SysMenu with VCL Styles Enabled.
using the Vcl.Styles.SysControls unit
System menu with VCL Styles Enabled.
System menu using the Vcl.Styles.SysControls unit

Dialogs
Open Dialog With VCL Styles enabled
Open Dialog using the Vcl.Styles.SysControls unit

Even the shell menu inside of the dialog is themed
Others Dialogs
You can activate this functionality in your apps just adding the Vcl.Styles.SysControls unit to your project. Also a new sample project was added to test all the new features.
As always all your comments and suggestions are welcome.











November 11, 2013 at 3:30 pm
Best Project of the Year! Why is EMBA not able to fix simplest bugs?
Pingback: The VCL Styles Utils Project now supports dialo...
November 12, 2013 at 4:12 am
Beatyfull work!
Just one question: since Embarcadero seems not interested in correcting certain errors, is it possible in some way to patch VCL to solve the bug in TMainMenu and TPopUpMenu that they doesn’t respect the Screen.MainMenu.Font.Size property? Last year I opend a issue in QC but they don’t solve it yet. I just patched by myself Vcl.PlatformVclStylesActnCtrls.pas to get this working on TActionMainMenuBar because it suffers the same problem.
November 12, 2013 at 10:12 am
Thanks, for the TActionMainMenuBar and TPopupActionBar I wrote this post. Now For the TMainMenu you must modify the style hook related to this control located inside of the TFormStyleHook and for the TPopupMenu you can request this in the issue page of the project.
November 13, 2013 at 5:13 am
Rodrigo I read your post for TActionMainMenuBar and I’m using this solution but I have to say that it doesn’t work if use a font.size bigger than 16 (for example 24) because the size of the single menu elements it seems not correctly recalculated. If you use a small font size you cann’t see this issue.
November 13, 2013 at 10:52 am
Davide, The code of that article is part of the VCL Styles Utils project, so you can report the issue using the issue page of the project http://code.google.com/p/vcl-styles-utils/issues/list
November 13, 2013 at 11:10 am
I’ve already done it but the issue was closed as wontfix (see issue 3). I tried to fix it by myself but the result is not perfect.
November 13, 2013 at 6:56 am
How can I modify the style hook for TMainMenu? Thanks
December 27, 2013 at 10:57 am
The TMainMenu Style hook is included (as a strict private class) in the TFormStyleHook, so in order to edit such style hook, you must modify the TFormStyleHook as well. The easy way is copy the TFormStyleHook class to a new unit and then made the changes.
November 12, 2013 at 6:05 am
Kudos for excellent work! Well done, Rodrigo!
Thanks a lot!
November 12, 2013 at 6:08 am
And of course, a big Thank you to Mahdi!
November 12, 2013 at 10:01 am
Thanks.
November 12, 2013 at 12:30 pm
Fantastic stuff!
November 12, 2013 at 1:24 pm
Thanks
November 13, 2013 at 4:06 am
Great thanks for the work!
But i have a problem. The TMenuItem property Break is still not working. I was trying this in the sample project and the popup menu isn’t displayed properly. Am i doing something wrong or is this still an not implemented feature?
November 13, 2013 at 10:50 am
You can report this issue using the issue page of the project http://code.google.com/p/vcl-styles-utils/issues/list
November 13, 2013 at 4:08 pm
Hi Rodrigo,
I have added all your units to the .dpr uses section. The app is themed now, but the menus and printer dialog are stil in Windows classic. What is wrong here? Is the order important?
November 13, 2013 at 4:13 pm
Check the sample application of the repository to see how it works. http://code.google.com/p/vcl-styles-utils/source/browse/#svn%2Ftrunk%2FVcl%20SysControls%20Demo
November 13, 2013 at 4:45 pm
I found the problem. It is working in Windows XP/7, but not on W2k. A customer app still needs W2k.
November 13, 2013 at 9:16 pm
The “Look In” label was left aligned instead of right aligned inside the Open dialog box.
November 13, 2013 at 9:22 pm
Please report this issue using the issue page of the project http://code.google.com/p/vcl-styles-utils/issues/list
November 17, 2013 at 11:33 am
I am just bluffed by your work!
Skinning the system dialogs is something I try to tackle for years now. I was missing the talent of you both…
Is it possible to toggle the skinning of system dialogs at runtime?
November 18, 2013 at 11:10 am
Thanks, for the moment the only way to disable apply the VCL Styles in the dialogs is using the Windows Style. Maybe in the future we will add this option, So you can request this feature using the issue page of the project http://code.google.com/p/vcl-styles-utils/issues/list
November 22, 2013 at 10:14 am
I’m without words, except: “I’m without words!!!”
December 2, 2013 at 5:04 pm
Amazing. Thanks very much Rodrigo!
January 12, 2014 at 5:11 am
Where can I download all units at once? This seems a nice project!
January 12, 2014 at 1:09 pm
You can get the instructions to checkout the source of the project from here http://code.google.com/p/vcl-styles-utils/source/checkout
January 12, 2014 at 4:29 pm
Well I cut and pasted them all… Could not find a hint :)
January 14, 2014 at 8:29 pm
You must install a subversion client like TortoiseSVN and then follow the instructions to checkout the source of the project from here http://code.google.com/p/vcl-styles-utils/source/checkout
August 23, 2014 at 3:29 am
Very nice, thank you Rodrigo!
September 15, 2014 at 3:14 pm
{ Add Vcl Styles to system menu }
TVclStylesSystemMenu.Create(Self);
This adds styles in an unsorted list. Please add a optional parameter TVclStylesSystemMenu.Create(Self, True), where the last parameter loads in styles in alphabetically sorted order.