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.