// *************************************************************************************************** // // Unit Vcl.Styles.Utils.SysControls // unit for the VCL Styles Utils // https://github.com/RRUZ/vcl-styles-utils/ // // The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License") // you may not use this file except in compliance with the License. You may obtain a copy of the // License at http://www.mozilla.org/MPL/ // // Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF // ANY KIND, either express or implied. See the License for the specific language governing rights // and limitations under the License. // // // Portions created by Mahdi Safsafi [SMP3] e-mail SMP@LIVE.FR // Portions created by Rodrigo Ruz V. are Copyright (C) 2013-2020 Rodrigo Ruz V. // All Rights Reserved. // // ************************************************************************************************** unit Vcl.Styles.Utils.SysControls; interface uses System.Classes, System.Types, System.SysUtils, System.Generics.Collections, Winapi.Windows, Winapi.Messages, Vcl.Controls, Vcl.Graphics, Vcl.Themes, Vcl.Styles.Utils.SysStyleHook; type PChildControlInfo = ^TChildControlInfo; TChildControlInfo = record Parent: HWND; ParentStyle: NativeInt; StyleHookClass: TSysStyleHookClass; end; PControlInfo = ^TControlInfo; TControlInfo = record Handle: HWND; Parent: HWND; Style: NativeInt; ParentStyle: NativeInt; ExStyle: NativeInt; ParentExStyle: NativeInt; ClassName: PChar; ParentClassName: PChar; end; type TSysHookAction = (cAdded, cRemoved); TBeforeHookingControl = function(Info: PControlInfo): Boolean; TSysHookNotification = procedure(Action: TSysHookAction; Info: PControlInfo); TSysStyleManager = class(TComponent) private class var FEnabled: Boolean; FHook_WH_CBT: HHook; FBeforeHookingControlProc: TBeforeHookingControl; FSysHookNotificationProc: TSysHookNotification; FRegSysStylesList: TObjectDictionary; FSysStyleHookList: TObjectDictionary; FChildRegSysStylesList: TObjectDictionary; FHookVclControls: Boolean; FUseStyleColorsChildControls: Boolean; class var FHookDialogIcons: Boolean; protected /// /// Install the Hook /// class procedure InstallHook_WH_CBT; /// /// Remove the Hook /// class procedure RemoveHook_WH_CBT; /// /// Hook Callback /// class function HookActionCallBackCBT(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall; static; public /// /// Register a Sys Style Hook for an specified class. /// class procedure RegisterSysStyleHook(const SysControlClass: String; SysStyleHookClass: TSysStyleHookClass); /// /// UnRegister a Sys Style Hook for an specified class. /// class procedure UnRegisterSysStyleHook(const SysControlClass: String; SysStyleHookClass: TSysStyleHookClass); class constructor Create; class destructor Destroy; constructor Create(AOwner: TComponent); override; destructor Destroy; override; /// /// Event to preventvor allow hook a control. /// class Property OnBeforeHookingControl: TBeforeHookingControl read FBeforeHookingControlProc write FBeforeHookingControlProc; /// /// Notify when a hook foir control is added or removed /// class Property OnHookNotification: TSysHookNotification read FSysHookNotificationProc write FSysHookNotificationProc; /// /// Enable or disable the style of the controls /// class property Enabled: Boolean read FEnabled write FEnabled; /// /// Allow set the current VCL Style font and background color in  child /// controls. /// class property UseStyleColorsChildControls: Boolean read FUseStyleColorsChildControls write FUseStyleColorsChildControls; /// /// Allow disable or enable the hook of VCL Controls /// class property HookVclControls: Boolean read FHookVclControls write FHookVclControls; /// /// Allow disable or enable the hook of the icons dialogs /// class property HookDialogIcons: Boolean read FHookDialogIcons write FHookDialogIcons; /// /// Collection of Styled (Hooked) Controls /// class property SysStyleHookList: TObjectDictionary read FSysStyleHookList; /// /// Collection of Styled Child Controls /// class property ChildRegSysStylesList: TObjectDictionary read FChildRegSysStylesList; class procedure AddControlDirectly(Handle: HWND; const sClassName : string; IncludeChildControls : Boolean = False); end; implementation uses WinApi.CommCtrl, Vcl.Styles.Utils.Misc; function FindWinFromRoot(Root: HWND; ClassName: PChar): HWND; var Next, Child: HWND; S: String; begin Result := 0; Next := GetWindow(Root, GW_CHILD or GW_HWNDFIRST); while (Next > 0) do begin S := GetWindowClassName(Next); if S = String(ClassName) then Exit(Next); Next := GetWindow(Next, GW_HWNDNEXT); Child := GetWindow(Next, GW_CHILD or GW_HWNDFIRST); if Child > 0 then Result := FindWinFromRoot(Next, ClassName); if Result > 0 then Exit; end; end; { -------------------------------------------------------------------------------------- } { TSysStyleManager } function BeforeHookingControl(Info: PControlInfo): Boolean; var LInfo: TControlInfo; Root, C: HWND; begin { Return true to allow control hooking ! Return false to prevent control hooking ! } { NB: The ClassName is always in lowercase . } LInfo := Info^; Result := True; Root := GetAncestor(LInfo.Parent, GA_ROOT); if FindWinFromRoot(Root, 'DirectUIHWND') > 0 then begin Result := False; Exit; end; if SameText(LInfo.ClassName, WC_LISTVIEW) then begin if SameText(LInfo.ParentClassName, 'listviewpopup') then Result:=False; end else if SameText(LInfo.ClassName, TRACKBAR_CLASS) then begin if SameText(LInfo.ParentClassName, 'ViewControlClass') then Result:=False; end else //Prevent hook Toolbars on DirectUIHWND if SameText(LInfo.ClassName, TOOLBARCLASSNAME) then begin if SameText(LInfo.ParentClassName, 'ViewControlClass') then Result:=False else if Root > 0 then begin C := FindWinFromRoot(Root, REBARCLASSNAME); Result := not(C > 0); end; end; end; procedure HookNotification(Action: TSysHookAction; Info: PControlInfo); begin end; class constructor TSysStyleManager.Create; begin FHook_WH_CBT:=0; FBeforeHookingControlProc := @BeforeHookingControl; FSysHookNotificationProc := @HookNotification; FUseStyleColorsChildControls := True; FEnabled := True; FHookDialogIcons := False; FHookVclControls := False; FSysStyleHookList := TObjectDictionary.Create([doOwnsValues]); FRegSysStylesList := TObjectDictionary.Create; FChildRegSysStylesList := TObjectDictionary.Create; //FSysStyleHookList := TObjectDictionary.Create([]); InstallHook_WH_CBT; end; class destructor TSysStyleManager.Destroy; begin RemoveHook_WH_CBT; FRegSysStylesList.Free; FSysStyleHookList.Free; // remove the childs too because doOwnsValues FChildRegSysStylesList.Free; inherited; end; class procedure TSysStyleManager.AddControlDirectly(Handle: HWND; const sClassName : string; IncludeChildControls : Boolean = False); var LStyleHook : TSysStyleHook; ParentStyle : DWORD; procedure AddChildControl(ChildHandle: HWND); var Info: TChildControlInfo; sChildClassName : string; LStyleHook : TSysStyleHook; begin { Hook the control directly ! } ZeroMemory(@Info, sizeof(TChildControlInfo)); Info.Parent := Handle; Info.ParentStyle := ParentStyle; sChildClassName := LowerCase(GetWindowClassName(ChildHandle)); if FRegSysStylesList.ContainsKey(sChildClassName) then begin LStyleHook:=FRegSysStylesList[LowerCase(sChildClassName)].Create(ChildHandle); FSysStyleHookList.Add(ChildHandle, LStyleHook); SendMessage(ChildHandle, CM_CONTROLHOOKEDDIRECTLY, 0, 0); InvalidateRect(ChildHandle, nil, False); // if Assigned(FSysHookNotificationProc) then // FSysHookNotificationProc(cAdded, @Info); end; end; function EnumChildProc(const hWindow: hWnd; const LParam : LParam): boolean; stdcall; begin AddChildControl(hWindow); Result:= True; end; begin if not FRegSysStylesList.ContainsKey(LowerCase(sClassName)) then Exit; { Hook the control directly ! } if FSysStyleHookList.ContainsKey(Handle) then FSysStyleHookList.Remove(Handle); LStyleHook:=FRegSysStylesList[LowerCase(sClassName)].Create(Handle); FSysStyleHookList.Add(Handle, LStyleHook); SendMessage(Handle, CM_CONTROLHOOKEDDIRECTLY, 0, 0); // if Assigned(FSysHookNotificationProc) then // FSysHookNotificationProc(cAdded, @Info); if IncludeChildControls then begin ParentStyle:=GetWindowLongPtr(Handle, GWL_STYLE); EnumChildWindows(Handle, @EnumChildProc, 0); end; end; constructor TSysStyleManager.Create(AOwner: TComponent); begin inherited; end; destructor TSysStyleManager.Destroy; begin inherited; end; type TSysStyleClass = class(TSysStyleHook); class function TSysStyleManager.HookActionCallBackCBT(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; var CBTSturct: TCBTCreateWnd; sClassName, Tmp: string; {LHWND,} Parent: HWND; Style, ParentStyle, ExStyle, ParentExStyle: NativeInt; Info: TControlInfo; procedure RemoveUnusedHooks; var LHandle : THandle; begin for LHandle in TSysStyleManager.SysStyleHookList.Keys do if TSysStyleClass(TSysStyleManager.SysStyleHookList.Items[LHandle]).MustRemove then TSysStyleManager.SysStyleHookList.Remove(LHandle); end; procedure AddChildControl(Handle: HWND); var Info: TChildControlInfo; begin { The child control will be hooked inside it's parent control. } ZeroMemory(@Info, sizeof(TChildControlInfo)); Info.Parent := Parent; Info.ParentStyle := ParentStyle; Info.StyleHookClass := FRegSysStylesList[sClassName]; if FChildRegSysStylesList.ContainsKey(Handle) then FChildRegSysStylesList.Remove(Handle); FChildRegSysStylesList.Add(Handle, Info); if Assigned(FSysHookNotificationProc) then FSysHookNotificationProc(cAdded, @Info); end; procedure AddControl(Handle: HWND); var LStyleHook : TSysStyleHook; begin { Hook the control directly ! } RemoveUnusedHooks; if FSysStyleHookList.ContainsKey(Handle) then FSysStyleHookList.Remove(Handle); LStyleHook:=FRegSysStylesList[sClassName].Create(Handle); FSysStyleHookList.Add(Handle, LStyleHook); SendMessage(Handle, CM_CONTROLHOOKEDDIRECTLY, 0, 0); if Assigned(FSysHookNotificationProc) then FSysHookNotificationProc(cAdded, @Info); end; begin Result := CallNextHookEx(FHook_WH_CBT, nCode, wParam, lParam); if not FEnabled then Exit; // if (nCode = HCBT_ACTIVATE) and not(StyleServices.IsSystemStyle) then // begin // LHWND := HWND(wParam); // if(LHWND>0) then // begin // sClassName:= GetWindowClassName(LHWND); // if (sClassName<>'') and (not TSysStyleManager.SysStyleHookList.ContainsKey(LHWND)) and (SameText(sClassName,'#32770')) then // begin // TSysStyleManager.AddControlDirectly(LHWND, sClassName); // InvalidateRect(LHWND, nil, False); // end; // end; // end; if (nCode = HCBT_CREATEWND) and not(StyleServices.IsSystemStyle) then begin CBTSturct := PCBTCreateWnd(lParam)^; sClassName := GetWindowClassName(wParam); sClassName := LowerCase(sClassName); // if SameText(sClassName, '#32770') then // OutputDebugString(PChar('Class '+sclassName+' '+IntToHex(wParam, 8))); Parent := CBTSturct.lpcs.hwndParent; Style := CBTSturct.lpcs.Style; ExStyle := CBTSturct.lpcs.dwExStyle; ParentExStyle := 0; ParentStyle := 0; if Parent > 0 then begin ParentStyle := GetWindowLongPtr(Parent, GWL_STYLE); ParentExStyle := GetWindowLongPtr(Parent, GWL_EXSTYLE); end; if FRegSysStylesList.ContainsKey(sClassName) then begin Info.Handle := wParam; Info.Parent := Parent; Info.Style := Style; Info.ParentStyle := ParentStyle; Info.ExStyle := ExStyle; Info.ParentExStyle := ParentExStyle; Tmp := sClassName; Info.ClassName := PChar(Tmp); Tmp := LowerCase(GetWindowClassName(Parent)); Info.ParentClassName := PChar(Tmp); if not HookVclControls then if IsVCLControl(wParam) then Exit; if Assigned(FBeforeHookingControlProc) then if not FBeforeHookingControlProc(@Info) then Exit; if (Style and DS_CONTROL = DS_CONTROL) then begin { TabSheet ! } AddControl(wParam); PostMessage(wParam, CM_INITCHILDS, 0, 0); end else if (Style and WS_POPUP = WS_POPUP) then begin { Parent Control ! } AddControl(wParam); end else if (Style and WS_CHILD = WS_CHILD) then begin { Child Control ! } if FSysStyleHookList.ContainsKey(Parent) then begin { Parent is already hooked . } if IsVCLControl(Parent) then { Parent is a VCL control . } AddControl(wParam) else AddChildControl(wParam) end else { Parent not registered (not hooked). } AddControl(wParam); end else { Not (WS_CHILD or WS_POPUP) !! } AddControl(wParam); end; // if FSysStyleHookList.ContainsKey(wParam) or FChildRegSysStylesList.ContainsKey(wParam) then // OutputDebugString(PChar('Hooked '+IntToHex(wParam, 8))); end; if nCode = HCBT_DESTROYWND then begin //OutputDebugString(PChar('HCBT_DESTROYWND Handle '+IntToHex(wParam, 8))); if FSysStyleHookList.ContainsKey(wParam) then begin ZeroMemory(@Info, sizeof(TControlInfo)); Info.Handle := wParam; if Assigned(FSysHookNotificationProc) then OnHookNotification(cRemoved, @Info); // FSysStyleHookList.Remove(wParam); -> removed in WM_NCDESTROY end; end; end; class procedure TSysStyleManager.InstallHook_WH_CBT; begin FHook_WH_CBT := SetWindowsHookEx(WH_CBT, @HookActionCallBackCBT, 0, GetCurrentThreadId); end; class procedure TSysStyleManager.RegisterSysStyleHook(const SysControlClass: String; SysStyleHookClass: TSysStyleHookClass); begin if FRegSysStylesList.ContainsKey(LowerCase(SysControlClass)) then FRegSysStylesList.Remove(LowerCase(SysControlClass)); FRegSysStylesList.Add(LowerCase(SysControlClass), SysStyleHookClass); end; class procedure TSysStyleManager.RemoveHook_WH_CBT; begin if FHook_WH_CBT <> 0 then UnhookWindowsHookEx(FHook_WH_CBT); end; class procedure TSysStyleManager.UnRegisterSysStyleHook(const SysControlClass: String; SysStyleHookClass: TSysStyleHookClass); begin if FRegSysStylesList.ContainsKey(LowerCase(SysControlClass)) then FRegSysStylesList.Remove(LowerCase(SysControlClass)); end; end.