argumentum Posted May 12 Posted May 12 looks nice WildByDesign 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
mLipok Posted May 12 Posted May 12 On 5/11/2026 at 7:16 PM, UEZ said: Took the code from my example and modified it: Awesome code. Sorry to butt in here a bit .... but I just couldn't resist and checked if this code could do something I've always wanted to do. Through the color of the red frame and the highlighting of the active element (its background and text color) it clearly indicates which tab is active. expandcollapse popup#include <ColorConstants.au3> #include <GUIConstantsEx.au3> #include <GuiTab.au3> #include <TabConstants.au3> #include <WindowsNotifsConstants.au3> #include <WindowsSysColorConstants.au3> #include <WinAPIGdi.au3> #include <WinAPISysWin.au3> #include <WinAPITheme.au3> ; Initialize System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Global $g_hTab_CB, $g_pTab_CB, $g_hProc, $g_hTab Global Const $COLOR_BUTTON_BG = 0x383838, $COLOR_BG_DARK = 0x202020, $COLOR_GUI_BG = 0x101010, $COLOR_BORDER = 0x606060, $COLOR_BORDER_DARK = 0x303030 Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl (24H2/25H2)", 500, 300) GUISetBkColor($COLOR_GUI_BG) GUISetFont(10) Local $idTab = GUICtrlCreateTab(20, 20, 460, 260) $g_hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateTabItem("tab1") GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateTabItem("tab3") GUICtrlCreateTabItem("tab4") ; Remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; Set dark titlebar _WinAPI_DwmSetWindowAttribute($hGUI, $DWMWA_USE_IMMERSIVE_DARK_MODE, True) ; Set theme if OS supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme(GUICtrlGetHandle($idTab), 'DarkMode_DarkTheme') ; Register Subclassing / Window Procedure $g_hTab_CB = DllCallbackRegister(_WinProc, "ptr", "hwnd;uint;wparam;lparam") $g_pTab_CB = DllCallbackGetPtr($g_hTab_CB) $g_hProc = _WinAPI_SetWindowLong($g_hTab, $GWL_WNDPROC, $g_pTab_CB) GUISetState(@SW_SHOW) Local $idMsg While 1 $idMsg = GUIGetMsg() If $idMsg = $GUI_EVENT_CLOSE Then ExitLoop WEnd ; Cleanup: Restore original Window Procedure _WinAPI_SetWindowLong($g_hTab, $GWL_WNDPROC, $g_hProc) DllCallbackFree($g_hTab_CB) EndFunc ;==>Example Func _is24H2Plus() ; Check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme." & @CRLF) EndIf Return $b24H2Plus EndFunc ;==>_is24H2Plus Func _WinProc($hWnd, $iMsg, $wParam, $lParam) ;coded by UEZ Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erasing to avoid flickering Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) If @error Or Not $hDC Then Return _WinAPI_CallWindowProc($g_hProc, $hWnd, $iMsg, $wParam, $lParam) Local $tClient = _WinAPI_GetClientRect($hWnd) Local $iWidth = $tClient.Right Local $iHeight = $tClient.Bottom ; Prepare Double Buffering Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) ; --- 1. Clipping (Exclude child controls from drawing) --- Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then $tCR = _WinAPI_GetWindowRect($hTabUpDown) If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf ; 2. Draw main background (Dark color) Local $hBrushBg = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_BG_DARK)) ; _WinAPI_FillRect($hMemDC, $tClient, $hBrushBg) Local $iTabCount = _SendMessage($hWnd, $TCM_GETITEMCOUNT, 0, 0) Local $iCurSel = _SendMessage($hWnd, $TCM_GETCURSEL, 0, 0) ; 3. Prepare the Body Frame (The area beneath the tabs) Local $tFirstTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, 0, DllStructGetPtr($tFirstTabRect)) Local $tBodyRect = DllStructCreate($tagRECT) $tBodyRect.Left = 0 $tBodyRect.Top = $tFirstTabRect.Bottom ; Starts at the bottom edge of the tabs $tBodyRect.Right = $iWidth $tBodyRect.Bottom = $iHeight Local $hBrushBorder = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_RED)) _WinAPI_FrameRect($hMemDC, $tBodyRect, $hBrushBorder) ; 4. Draw the "Gap" to the right of the tabs in GUI background color If $iTabCount > 0 Then Local $tLastTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $iTabCount - 1, DllStructGetPtr($tLastTabRect)) Local $tGapRect = DllStructCreate($tagRECT) $tGapRect.Left = $tLastTabRect.Right + 2 $tGapRect.Top = 0 $tGapRect.Right = $iWidth $tGapRect.Bottom = $tLastTabRect.Bottom Local $hBrushGui = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_GUI_BG)) _WinAPI_FillRect($hMemDC, $tGapRect, $hBrushGui) _WinAPI_DeleteObject($hBrushGui) EndIf _WinAPI_SetBkMode($hMemDC, 1) ; Transparent background for text ;~ _WinAPI_SetTextColor($hMemDC, _ColorToCOLORREF(0xF0F0F0)) ; 5. Draw individual tabs For $i = 0 To $iTabCount - 1 Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $i, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then ContinueLoop $tRECT.top -= 2 Local $bSelected = ($i = $iCurSel) Local $iTabColor = $bSelected ? $COLOR_DARKSLATEGRAY : $COLOR_BG_DARK Local $hTabBrush = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($iTabColor)) ; Fill tab background _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush) If $bSelected Then _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor($COLOR_VIOLET)) ; Draw border ONLY for the active tab (Top, Left, Right) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushBorder) ; OPEN BOTTOM: Draw a line in tab-color over the body-border to merge them Local $tOpenLine = DllStructCreate($tagRECT) $tOpenLine.Left = $tRECT.Left + 1 $tOpenLine.Top = $tRECT.Bottom - 1 ; Exactly on the border line of the body $tOpenLine.Right = $tRECT.Right - 1 $tOpenLine.Bottom = $tRECT.Bottom + 1 _WinAPI_FillRect($hMemDC, $tOpenLine, $hTabBrush) Else _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor($COLOR_RED)) ; Draw rectangle around non active tabs Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_BORDER_DARK)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) _WinAPI_DeleteObject($hBrushTabRecDark) EndIf _WinAPI_DeleteObject($hTabBrush) ; Draw text centered Local $sText = _GUICtrlTab_GetItemText($hWnd, $i) Local $tTextRect = DllStructCreate($tagRECT) With $tTextRect .Left = $tRECT.Left + 6 .Top = $tRECT.Top + ($bSelected ? 1 : 3) .Right = $tRECT.Right - 6 .Bottom = $tRECT.Bottom - 3 EndWith DllCall("user32.dll", "int", "DrawTextW", "handle", $hMemDC, "wstr", $sText, "int", -1, "struct*", $tTextRect, "uint", BitOR($DT_CENTER, $DT_VCENTER, $DT_SINGLELINE, $DT_NOPREFIX)) Next ; 6. Copy memory DC to screen DC (BitBlt) _WinAPI_BitBlt($hDC, 0, 0, $iWidth, $iHeight, $hMemDC, 0, 0, $SRCCOPY) ; Cleanup _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteObject($hBrushBg) _WinAPI_DeleteObject($hBrushBorder) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 EndSwitch Return _WinAPI_CallWindowProc($g_hProc, $hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>_WinProc Func _ColorToCOLORREF($iColor) ; Convert RGB to BGR Local $iR = BitAND(BitShift($iColor, 16), 0xFF) Local $iG = BitAND(BitShift($iColor, 8), 0xFF) Local $iB = BitAND($iColor, 0xFF) Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR) EndFunc ;==>_ColorToCOLORREF Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall("user32.dll", "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx WildByDesign 1 Signature beginning:* Please remember: "AutoIt"..... * Wondering who uses AutoIt and what it can be used for ? * Forum Rules ** ADO.au3 UDF * POP3.au3 UDF * XML.au3 UDF * IE on Windows 11 * How to ask ChatGPT for AutoIt Code * for other useful stuff click the following button: Spoiler Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind. My contribution (my own projects): * Debenu Quick PDF Library - UDF * Debenu PDF Viewer SDK - UDF * Acrobat Reader - ActiveX Viewer * UDF for PDFCreator v1.x.x * XZip - UDF * AppCompatFlags UDF * CrowdinAPI UDF * _WinMergeCompare2Files() * _JavaExceptionAdd() * _IsBeta() * Writing DPI Awareness App - workaround * _AutoIt_RequiredVersion() * Chilkatsoft.au3 UDF * TeamViewer.au3 UDF * JavaManagement UDF * VIES over SOAP * WinSCP UDF * GHAPI UDF - modest begining - comunication with GitHub REST API * ErrorLog.au3 UDF - A logging Library * Include Dependency Tree (Tool for analyzing script relations) * Show_Macro_Values.au3 * My contribution to others projects or UDF based on others projects: * _sql.au3 UDF * POP3.au3 UDF * RTF Printer - UDF * XML.au3 UDF * ADO.au3 UDF * SMTP Mailer UDF * Dual Monitor resolution detection * * 2GUI on Dual Monitor System * _SciLexer.au3 UDF * SciTE - Lexer for console pane * Useful links: * Forum Rules * Forum etiquette * Forum Information and FAQs * How to post code on the forum * AutoIt Online Documentation * AutoIt Online Beta Documentation * SciTE4AutoIt3 getting started * Convert text blocks to AutoIt code * Games made in Autoit * Programming related sites * Polish AutoIt Tutorial * DllCall Code Generator * Wiki: * Expand your knowledge - AutoIt Wiki * Collection of User Defined Functions * How to use HelpFile * Good coding practices in AutoIt * OpenOffice/LibreOffice/XLS Related: WriterDemo.au3 * XLS/MDB from scratch with ADOX IE Related: * How to use IE.au3 UDF with AutoIt v3.3.14.x * Why isn't Autoit able to click a Javascript Dialog? * Clicking javascript button with no ID * IE document >> save as MHT file * IETab Switcher (by LarsJ ) * HTML Entities * _IEquerySelectorAll() (by uncommon) * IE in TaskScheduler * IE Embedded Control Versioning (use IE9+ and HTML5 in a GUI) * PDF Related: * How to get reference to PDF object embeded in IE * IE on Windows 11 * I encourage you to read: * Global Vars * Best Coding Practices * Please explain code used in Help file for several File functions * OOP-like approach in AutoIt * UDF-Spec Questions * EXAMPLE: How To Catch ConsoleWrite() output to a file or to CMD *I also encourage you to check awesome @trancexx code: * Create COM objects from modules without any demand on user to register anything. * Another COM object registering stuff * OnHungApp handler * Avoid "AutoIt Error" message box in unknown errors * HTML editor * winhttp.au3 related : * https://www.autoitscript.com/forum/topic/206771-winhttpau3-download-problem-youre-speaking-plain-http-to-an-ssl-enabled-server-port/ "Homo sum; humani nil a me alienum puto" - Publius Terentius Afer"Program are meant to be read by humans and only incidentally for computers and execute" - Donald Knuth, "The Art of Computer Programming" , be and \\//_. Anticipating Errors : "Any program that accepts data from a user must include code to validate that data before sending it to the data store. You cannot rely on the data store, ...., or even your programming language to notify you of problems. You must check every byte entered by your users, making sure that data is the correct type for its field and that required fields are not empty." Signature last update: 2023-04-24
WildByDesign Posted May 12 Author Posted May 12 (edited) @UEZ If you have a moment, could you please help me with something? From the following example, I would like to prevent the top 2 pixels from being painted over on the active tab only. Sorry for the screenshot clips being so small. The top screenshot shows what is happening. The top 2 pixels on the active tab is also being painted over. On a darker GUI background colour, that makes it difficult to see that the active tab is actually the active tab. The bottom screenshot just shows what that top 2 pixels contains and why it is important. Do you know how to exclude the top 2 pixels from active tab only from being painted over? Thank you. EDIT: Sorry I forgot to post my current testing example: EDIT2: I'm wondering if the _WinAPI_ExtFloodFill function would be beneficial in the "; paints strip on top of each tab" section since it would target the specific white color and leave the active tab alone. expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WindowsSysColorConstants.au3> #include <WinAPITheme.au3> ; initiate System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("MustDeclareVars", True) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl", 400, 300) GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, 360, 260, -1) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateLabel("label0", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab----1") GUICtrlCreateLabel("label1", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("label2", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) Local $idAddTab = GUICtrlCreateButton("Trigger UpDown", 50, 120) _WinAPI_SetWindowTheme(GUICtrlGetHandle($idAddTab), 'DarkMode_Explorer') GUICtrlCreateTabItem("tab3") GUICtrlCreateLabel("label3", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("") ; end tabitem definition ; set DarkMode_DarkTheme visual theme on Tab control as long as OSBuild supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') ; get handle for UpDown control to apply theme Local $hUpDown = _WinAPI_FindWindowEx($hTab, "msctls_updown32") If _is24H2Plus() And $hUpDown Then _WinAPI_SetWindowTheme($hUpDown, 'DarkMode_DarkTheme') Local $pAddress = _WinAPI_GetWindowLong($hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(WM_PAINT, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; needed to fix button frame when button is in tab control GUIRegisterMsg($WM_CTLCOLORBTN, "_WM_CTLCOLORBTN") GUISetState() While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop Case $idAddTab _CreateNewTab() EndSwitch WEnd _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _CreateNewTab() GUICtrlCreateTabItem("tab4") GUICtrlCreateLabel("label4", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab5") GUICtrlCreateLabel("label5", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab6") GUICtrlCreateLabel("label6", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) EndFunc Func WM_PAINT($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erase to avoid flicker Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect = _WinAPI_GetClientRect($hWnd) Local $iW = $tRect.Right Local $iH = $tRect.Bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, 20) ; 20 = $PRF_CLIENT or $PRF_CHILDREN Local $hBrush = _WinAPI_CreateSolidBrush(0x191919) Local $iTabCount = _GUICtrlTab_GetItemCount($hWnd) If $iTabCount > 0 Then Local $tRectLast = _GUICtrlTab_GetItemRectEx($hWnd, $iTabCount - 1) ; Exclude overlapping GUI controls from painting Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom ; Exclude overlapping GUI controls from painting While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) ; Ensure control is within tab control If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or _ $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd ; exclude UpDown control from being painted over - ExcludeClipRect code from UEZ Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) Local $tPR = _WinAPI_GetWindowRect($hWnd) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hMemDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf ; paints area to the right of all tabs Local $tPatch = DllStructCreate($tagRECT) $tPatch.Left = $tRectLast.Right - 2 $tPatch.Top = 0 $tPatch.Right = $iW $tPatch.Bottom = $tRectLast.Bottom _WinAPI_FillRect($hMemDC, $tPatch, $hBrush) ; paints area to the left of all tabs $tPatch.Left = 0 $tPatch.Right = 2 _WinAPI_FillRect($hMemDC, $tPatch, $hBrush) ; paints strip on top of each tab $tPatch.Left = 0 $tPatch.Top = 0 $tPatch.Right = $iW $tPatch.Bottom = 2 _WinAPI_FillRect($hMemDC, $tPatch, $hBrush) EndIf _WinAPI_DeleteObject($hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" Then If _is24H2Plus() Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>WM_PAINT Func _is24H2Plus() ; check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme.") EndIf Return $b24H2Plus EndFunc Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall('user32.dll', "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max Func _WM_CTLCOLORBTN($hWnd, $iMsg, $wParam, $lParam) #forceref $hWnd, $iMsg, $wParam Local $hCtrl = $lParam Local $hBrush = _WinAPI_CreateSolidBrush(0x262626) Return $hBrush EndFunc ;==>_CTLCOLORBTN Edited May 12 by WildByDesign
WildByDesign Posted May 12 Author Posted May 12 (edited) I think I got it! Not the most elegant of coding, but it works. The UpDown control made it more complicated. I just hope that my measurements work on any system. EDIT: Improved code. EDIT2: I can see some potential for failure on my measurements depending on tab size... EDIT3: I think I've mitigated the potential failure. expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WindowsSysColorConstants.au3> #include <WinAPITheme.au3> ; initiate System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("MustDeclareVars", True) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl", 400, 300) GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, 360, 260, -1) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateLabel("label0", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab----1") GUICtrlCreateLabel("label1", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("label2", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) Local $idAddTab = GUICtrlCreateButton("Trigger UpDown", 50, 120) _WinAPI_SetWindowTheme(GUICtrlGetHandle($idAddTab), 'DarkMode_Explorer') GUICtrlCreateTabItem("tab3") GUICtrlCreateLabel("label3", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("") ; end tabitem definition ; set DarkMode_DarkTheme visual theme on Tab control as long as OSBuild supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') ; get handle for UpDown control to apply theme Local $hUpDown = _WinAPI_FindWindowEx($hTab, "msctls_updown32") If _is24H2Plus() And $hUpDown Then _WinAPI_SetWindowTheme($hUpDown, 'DarkMode_DarkTheme') Local $pAddress = _WinAPI_GetWindowLong($hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(WM_PAINT, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; needed to fix button frame when button is in tab control GUIRegisterMsg($WM_CTLCOLORBTN, "_WM_CTLCOLORBTN") GUISetState() While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop Case $idAddTab _CreateNewTab() EndSwitch WEnd _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _CreateNewTab() GUICtrlCreateTabItem("tab4") GUICtrlCreateLabel("label4", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab5") GUICtrlCreateLabel("label5", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlCreateTabItem("tab6") GUICtrlCreateLabel("label6", 30, 80, 50, 20) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) EndFunc Func WM_PAINT($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erase to avoid flicker Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect = _WinAPI_GetClientRect($hWnd) Local $iW = $tRect.Right Local $iH = $tRect.Bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) Local $iUpDownWidth = 0 _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, 20) ; 20 = $PRF_CLIENT or $PRF_CHILDREN Local $hBrush = _WinAPI_CreateSolidBrush(0x191919) Local $iTabCount = _GUICtrlTab_GetItemCount($hWnd) If $iTabCount > 0 Then Local $tRectLast = _GUICtrlTab_GetItemRectEx($hWnd, $iTabCount - 1) ; Exclude overlapping GUI controls from painting Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom ; Exclude overlapping GUI controls from painting While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) ; Ensure control is within tab control If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or _ $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd ; exclude UpDown control from being painted over - ExcludeClipRect code from UEZ Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) $iUpDownWidth = $tCR.Right - $tCR.Left + 1 Local $tPR = _WinAPI_GetWindowRect($hWnd) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hMemDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf Local $tRect = _WinAPI_GetClientRect($hWnd) _WinAPI_SelectObject($hMemDC, $hBrush) _WinAPI_SelectObject($hDC, $hBrush) _WinAPI_ExtFloodFill($hMemDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hMemDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) EndIf _WinAPI_DeleteObject($hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" Then If _is24H2Plus() Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>WM_PAINT Func _is24H2Plus() ; check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme.") EndIf Return $b24H2Plus EndFunc Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall('user32.dll', "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max Func _WM_CTLCOLORBTN($hWnd, $iMsg, $wParam, $lParam) #forceref $hWnd, $iMsg, $wParam Local $hCtrl = $lParam Local $hBrush = _WinAPI_CreateSolidBrush(0x262626) Return $hBrush EndFunc ;==>_CTLCOLORBTN Edited May 12 by WildByDesign
WildByDesign Posted May 12 Author Posted May 12 @argumentum If you have a moment, can you please test the code in my most recent comment above? Particularly, I need to know if any white areas are showing. Especially after triggering the UpDown control to show.
argumentum Posted May 13 Posted May 13 2 hours ago, WildByDesign said: Especially after triggering the UpDown control to show. ... Func Example() Local $iFormW = 700, $iFormH = 300 ; resizing is important when testing Local $hGUI = GUICreate("DarkTheme TabControl", $iFormW, $iFormH, -1, -1, BitOR($GUI_SS_DEFAULT_GUI,$WS_MAXIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_TABSTOP)) ; <<< GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, $iFormW - 40, $iFormH - 40, -1) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKRIGHT+$GUI_DOCKTOP+$GUI_DOCKBOTTOM) ; <<< ... looks good. WildByDesign 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
WildByDesign Posted May 13 Author Posted May 13 8 hours ago, argumentum said: looks good. Excellent, thanks for confirming. I haven't noticed any issues on my end either, so I think it's ready to implement in GUIDarkTheme UDF for 24H2/25H2 users as long as the build supports DarkMode_DarkTheme. Thanks for the code and suggestion for adding resize. That works great. It's my fault for not adding resize in the beginning but I will make resize from now on. You're right, it is important for testing how it will behave. argumentum 1
WildByDesign Posted May 13 Author Posted May 13 (edited) @UEZ Sorry for tagging you, again. But the reason why I want to ask you specifically is because the tab subclassing technique (with ExcludeClipRect, etc.) is your technique which is used here in this thread, your SampleControls.au3 in Dark Mode and also in GUIDarkTheme UDF. I have an idea and would like your input/opinion on whether or not the idea is good or bad. After adding GUI resize, I can see that there is some flicker with simple labels (while resizing GUI) and therefore likely other controls as well. Idea: We can completely get rid of the whole ExcludeClipRect section regarding controls inside of the tab control: ; Exclude overlapping GUI controls from painting While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) ; Ensure control is within tab control If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or _ $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd As long as we raise the Z-order of the labels or other controls so that they are above the tab control: _WinAPI_SetWindowPos(GUICtrlGetHandle($idLabel), $HWND_TOP, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) In my testing, this completely got rid of the label flicker while resizing the GUI. Switching tabs in the tab control still had all of the control behaving as expected. The controls still don't disappear. And it lowers the amount of work (slightly) that the tab control procedure needs to do overall. Question: Is this safe to do? Is it proper? I mean, it may not be proper. But it does solve some problems in general with tab control subclassing. Here is an updated example that has GUI resize, label Z-order change and no ExcludeClipRect of controls: expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WindowsSysColorConstants.au3> #include <WinAPITheme.au3> ; initiate System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("MustDeclareVars", True) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl", 400, 300, -1, -1, BitOR($GUI_SS_DEFAULT_GUI,$WS_MAXIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_TABSTOP)) GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, 360, 260) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKRIGHT+$GUI_DOCKTOP+$GUI_DOCKBOTTOM) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateLabel("label0", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab1") GUICtrlCreateLabel("label1", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("Resize GUI to trigger UpDown controls.", 30, 80, 300, 100) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab3") GUICtrlCreateLabel("label3", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab4") GUICtrlCreateLabel("label4", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("") ; end tabitem definition ; set DarkMode_DarkTheme visual theme on Tab control as long as OSBuild supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') ; get handle for UpDown control to apply theme Local $hUpDown = _WinAPI_FindWindowEx($hTab, "msctls_updown32") If _is24H2Plus() And $hUpDown Then _WinAPI_SetWindowTheme($hUpDown, 'DarkMode_DarkTheme') Local $pAddress = _WinAPI_GetWindowLong($hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(WM_PAINT, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) _WinAPI_SetWindowPos($hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) GUISetState() While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop EndSwitch WEnd _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func WM_PAINT($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erase to avoid flicker Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect = _WinAPI_GetClientRect($hWnd) Local $iW = $tRect.Right Local $iH = $tRect.Bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) Local $iUpDownWidth = 0 _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, 20) ; 20 = $PRF_CLIENT or $PRF_CHILDREN Local $hBrush = _WinAPI_CreateSolidBrush(0x191919) Local $iTabCount = _GUICtrlTab_GetItemCount($hWnd) If $iTabCount > 0 Then Local $tRectLast = _GUICtrlTab_GetItemRectEx($hWnd, $iTabCount - 1) ; Exclude overlapping GUI controls from painting Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom #cs ; Exclude overlapping GUI controls from painting While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) ; Ensure control is within tab control If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or _ $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd #ce ; exclude UpDown control from being painted over - ExcludeClipRect code from UEZ Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) $iUpDownWidth = $tCR.Right - $tCR.Left Local $tPR = _WinAPI_GetWindowRect($hWnd) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hMemDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf Local $tRect = _WinAPI_GetClientRect($hWnd) _WinAPI_SelectObject($hMemDC, $hBrush) _WinAPI_SelectObject($hDC, $hBrush) _WinAPI_ExtFloodFill($hMemDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hMemDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) EndIf _WinAPI_DeleteObject($hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" Then If _is24H2Plus() Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>WM_PAINT Func _is24H2Plus() ; check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme.") EndIf Return $b24H2Plus EndFunc Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall('user32.dll', "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max EDIT: Fixed GUICtrlSetResizing for labels. Instead of changing Z-order of all controls, just lower Z-order of tab control now with: _WinAPI_SetWindowPos($hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) Edited May 13 by WildByDesign
WildByDesign Posted May 13 Author Posted May 13 (edited) By the way, this same Z-order trick is the only way that I could find to fix the Group box background color problem in GUIDarkTheme UDF. I had tried dozens of other fixes but Group box controls are silly. Edited May 13 by WildByDesign
UEZ Posted May 13 Posted May 13 1 hour ago, WildByDesign said: @UEZ Sorry for tagging you, again. But the reason why I want to ask you specifically is because the tab subclassing technique (with ExcludeClipRect, etc.) is your technique which is used here in this thread, your SampleControls.au3 in Dark Mode and also in GUIDarkTheme UDF. I have an idea and would like your input/opinion on whether or not the idea is good or bad. After adding GUI resize, I can see that there is some flicker with simple labels (while resizing GUI) and therefore likely other controls as well. Idea: We can completely get rid of the whole ExcludeClipRect section regarding controls inside of the tab control: ; Exclude overlapping GUI controls from painting While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) ; Ensure control is within tab control If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or _ $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd As long as we raise the Z-order of the labels or other controls so that they are above the tab control: _WinAPI_SetWindowPos(GUICtrlGetHandle($idLabel), $HWND_TOP, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) In my testing, this completely got rid of the label flicker while resizing the GUI. Switching tabs in the tab control still had all of the control behaving as expected. The controls still don't disappear. And it lowers the amount of work (slightly) that the tab control procedure needs to do overall. Question: Is this safe to do? Is it proper? I mean, it may not be proper. But it does solve some problems in general with tab control subclassing. Here is an updated example that has GUI resize, label Z-order change and no ExcludeClipRect of controls: expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WindowsSysColorConstants.au3> #include <WinAPITheme.au3> ; initiate System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("MustDeclareVars", True) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl", 400, 300, -1, -1, BitOR($GUI_SS_DEFAULT_GUI,$WS_MAXIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_TABSTOP)) GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, 360, 260) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKRIGHT+$GUI_DOCKTOP+$GUI_DOCKBOTTOM) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateLabel("label0", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab1") GUICtrlCreateLabel("label1", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("Resize GUI to trigger UpDown controls.", 30, 80, 300, 100) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab3") GUICtrlCreateLabel("label3", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab4") GUICtrlCreateLabel("label4", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("") ; end tabitem definition ; set DarkMode_DarkTheme visual theme on Tab control as long as OSBuild supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') ; get handle for UpDown control to apply theme Local $hUpDown = _WinAPI_FindWindowEx($hTab, "msctls_updown32") If _is24H2Plus() And $hUpDown Then _WinAPI_SetWindowTheme($hUpDown, 'DarkMode_DarkTheme') Local $pAddress = _WinAPI_GetWindowLong($hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(WM_PAINT, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) _WinAPI_SetWindowPos($hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) GUISetState() While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop EndSwitch WEnd _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func WM_PAINT($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erase to avoid flicker Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect = _WinAPI_GetClientRect($hWnd) Local $iW = $tRect.Right Local $iH = $tRect.Bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) Local $iUpDownWidth = 0 _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, 20) ; 20 = $PRF_CLIENT or $PRF_CHILDREN Local $hBrush = _WinAPI_CreateSolidBrush(0x191919) Local $iTabCount = _GUICtrlTab_GetItemCount($hWnd) If $iTabCount > 0 Then Local $tRectLast = _GUICtrlTab_GetItemRectEx($hWnd, $iTabCount - 1) ; Exclude overlapping GUI controls from painting Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom #cs ; Exclude overlapping GUI controls from painting While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) ; Ensure control is within tab control If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or _ $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd #ce ; exclude UpDown control from being painted over - ExcludeClipRect code from UEZ Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) $iUpDownWidth = $tCR.Right - $tCR.Left Local $tPR = _WinAPI_GetWindowRect($hWnd) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hMemDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, _ "int", $tCR.Left - $tPR.Left, "int", $tCR.Top - $tPR.Top - 1, _ "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf Local $tRect = _WinAPI_GetClientRect($hWnd) _WinAPI_SelectObject($hMemDC, $hBrush) _WinAPI_SelectObject($hDC, $hBrush) _WinAPI_ExtFloodFill($hMemDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hMemDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) EndIf _WinAPI_DeleteObject($hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" Then If _is24H2Plus() Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>WM_PAINT Func _is24H2Plus() ; check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme.") EndIf Return $b24H2Plus EndFunc Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall('user32.dll', "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max EDIT: Fixed GUICtrlSetResizing for labels. Instead of changing Z-order of all controls, just lower Z-order of tab control now with: _WinAPI_SetWindowPos($hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) It looks good to go with. Well done! WildByDesign 1 Please don't send me any personal message and ask for support! I will not reply! Selection of finest graphical examples at Codepen.io The own fart smells best! ✌Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!¯\_(ツ)_/¯ ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ
WildByDesign Posted May 13 Author Posted May 13 I have cleaned up the example considerably and also slimmed down the tab control subclass procedure. I am pretty sure that it's ready to add into GUIDarkTheme UDF now. expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WindowsSysColorConstants.au3> #include <WinAPITheme.au3> ; initiate System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("MustDeclareVars", True) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl", 400, 300, -1, -1, BitOR($GUI_SS_DEFAULT_GUI,$WS_MAXIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_TABSTOP)) GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, 360, 260) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKRIGHT+$GUI_DOCKTOP+$GUI_DOCKBOTTOM) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateLabel("label0", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab1") GUICtrlCreateLabel("label1", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("Resize GUI to trigger UpDown controls.", 30, 80, 300, 100) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab3") GUICtrlCreateLabel("label3", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab4") GUICtrlCreateLabel("label4", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("") ; end tabitem definition ; set DarkMode_DarkTheme visual theme on Tab control as long as OSBuild supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') ; get handle for UpDown control to apply theme Local $hUpDown = _WinAPI_FindWindowEx($hTab, "msctls_updown32") If _is24H2Plus() And $hUpDown Then _WinAPI_SetWindowTheme($hUpDown, 'DarkMode_DarkTheme') Local $pAddress = _WinAPI_GetWindowLong($hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(_ModernTabProc, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; lower the Z-order of the tab control (helps fix various issues) _WinAPI_SetWindowPos($hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) GUISetState() While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop EndSwitch WEnd _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _ModernTabProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erase to avoid flicker Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect = _WinAPI_GetClientRect($hWnd) Local $iW = $tRect.Right Local $iH = $tRect.Bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) Local $iUpDownWidth = 0 Local Const $PRF_CHILDREN = 16 Local Const $PRF_CLIENT = 4 _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, BitOr($PRF_CLIENT, $PRF_CHILDREN)) Local $hBrush = _WinAPI_CreateSolidBrush(0x191919) Local $iTabCount = _GUICtrlTab_GetItemCount($hWnd) If $iTabCount > 0 Then ; determine if tab control contains an UpDown (spin) control Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) Local $tPR = _WinAPI_GetWindowRect($hWnd) ; get width of UpDown control $iUpDownWidth = $tCR.Right - $tCR.Left ; exclude UpDown control from being painted over DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, "int", $tCR.Left - $tPR.Left, "int", _ $tCR.Top - $tPR.Top - 1, "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf _WinAPI_SelectObject($hMemDC, $hBrush) _WinAPI_ExtFloodFill($hMemDC, $tRect.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hMemDC, $tRect.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) EndIf _WinAPI_DeleteObject($hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" Then If _is24H2Plus() Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>_ModernTabProc Func _is24H2Plus() ; check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme.") EndIf Return $b24H2Plus EndFunc Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall('user32.dll', "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc argumentum 1
Nine Posted May 13 Posted May 13 Thanks for working this out. Sorry I am a bit late. So I can see what was my mistake, by calling again the standard proc. Just messaging WM_PRINTCLIENT into begin/end paint is well enough. Anyway tested with icons into the tabs and it works well, with the transparency and all. WildByDesign 1 “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
WildByDesign Posted May 13 Author Posted May 13 3 minutes ago, Nine said: Thanks for working this out. Sorry I am a bit late. So I can see what was my mistake, by calling again the standard proc. Just messaging WM_PRINTCLIENT into begin/end paint is well enough. Anyway tested with icons into the tabs and it works well, with the transparency and all. You're welcome. I seem to have a problem with being a little bit too persistent. I never give up until my idea somehow works itself out. 🙃 Can you please share your recent testing example with icons? The only problem that I still have is with Groupbox controls. I have to work around that by sending them below the tab control in the Z-order. I tried subclassing the Groupbox with WM_CTLCOLORSTATIC because it sends some of the messages there. But not everything. I was hoping to ensure that the background is transparent.
Nine Posted May 14 Posted May 14 13 hours ago, WildByDesign said: Can you please share your recent testing example with icons? Quote expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WinAPITheme.au3> #include <GuiImageList.au3> Opt("MustDeclareVars", True) Global Const $PRF_CLIENT = 4 Example() Func Example() GUICreate("Icon Tab") GUISetBkColor(0x606060) GUISetFont(9, 300) Local $idTab = GUICtrlCreateTab(10, 10, 200, 100) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("Tab 1") GUICtrlSetImage(-1, "shell32.dll", -128, 0) GUICtrlCreateLabel("Label 1", 20, 40, 250, 17) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlCreateTabItem("Tab 2") GUICtrlSetImage(-1, "shell32.dll", -131, 0) GUICtrlCreateLabel("Label 2", 20, 50, 120, 17) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlCreateTabItem("") _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') Local $hSubclass = DllCallbackRegister(TabProc, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) GUISetState() Do Until GUIGetMsg() = $GUI_EVENT_CLOSE _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func TabProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) If $iMsg = $WM_ERASEBKGND Then Return 1 If $iMsg <> $WM_PAINT Then Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect = _WinAPI_GetClientRect($hWnd) Local $iWidth = $tRect.right, $iHeight = $tRect.bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight) Local $hOld = _WinAPI_SelectObject($hMemDC, $hBitmap) _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, $PRF_CLIENT) Local $hBrush = _WinAPI_CreateSolidBrush(0x404040) Local $tRect2 = _GUICtrlTab_GetItemRectEx($hWnd, _GUICtrlTab_GetItemCount($hWnd) - 1) $tRect.left = $tRect2.right - 2 $tRect.bottom = $tRect2.bottom _WinAPI_FillRect($hMemDC, $tRect, $hBrush) $tRect.left = 0 $tRect.right = 2 _WinAPI_FillRect($hMemDC, $tRect, $hBrush) $tRect.bottom = 2 $tRect.right = $tRect2.right _WinAPI_FillRect($hMemDC, $tRect, $hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iWidth, $iHeight, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOld) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteObject($hBrush) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) EndFunc ;==>TabProc WildByDesign 1 “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
WildByDesign Posted May 14 Author Posted May 14 (edited) Can someone please help figure out what I have done wrong? I added a nice 2 pixel blue strip to the top of each active tab. Everything is working almost perfectly. You can resize the GUI while any of the tabs are selected to trigger the UpDown control to show and hide, except for Tab4. Resizing the GUI while Tab4 is the selected tab causes white to show on the right side of the tabs. I am going to keep trying to figure it out. But if anyone can figure out my mistake, please let me know. Thank you. FIXED: I had two different declarations for $tRect going on which caused the problem. I have updated the (hopefully bug free) script below. expandcollapse popup; From Nine #include <GUIConstants.au3> #include <WinAPI.au3> #include <GuiTab.au3> #include <WindowsSysColorConstants.au3> #include <WinAPITheme.au3> ; initiate System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Opt("MustDeclareVars", True) Global $g_hPenAccent = _WinAPI_CreatePen($PS_SOLID, 2, _ColorToCOLORREF(0x0078D4)) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl", 400, 300, -1, -1, BitOR($GUI_SS_DEFAULT_GUI,$WS_MAXIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_TABSTOP)) GUISetBkColor(0x191919) GUISetFont(10, 300) Local $idTab = GUICtrlCreateTab(20, 20, 360, 260) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKRIGHT+$GUI_DOCKTOP+$GUI_DOCKBOTTOM) Local $hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateLabel("label0", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab1") GUICtrlCreateLabel("label1", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("Resize GUI to trigger UpDown controls.", 30, 80, 300, 100) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab3") GUICtrlCreateLabel("label3", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab4") GUICtrlCreateLabel("label4", 30, 80, 300, 60) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, 0x262626) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("") ; end tabitem definition ; set DarkMode_DarkTheme visual theme on Tab control as long as OSBuild supports it If _is24H2Plus() Then _WinAPI_SetWindowTheme($hTab, 'DarkMode_DarkTheme') ; get handle for UpDown control to apply theme Local $hUpDown = _WinAPI_FindWindowEx($hTab, "msctls_updown32") If _is24H2Plus() And $hUpDown Then _WinAPI_SetWindowTheme($hUpDown, 'DarkMode_DarkTheme') Local $pAddress = _WinAPI_GetWindowLong($hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(_ModernTabProc, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; lower the Z-order of the tab control (helps fix various issues) _WinAPI_SetWindowPos($hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) GUISetState() While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop EndSwitch WEnd _WinAPI_RemoveWindowSubclass($hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _ModernTabProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erase to avoid flicker Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) Local $tRect2 = _WinAPI_GetClientRect($hWnd) Local $iW = $tRect2.Right Local $iH = $tRect2.Bottom Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) Local $iUpDownWidth = 0 Local Const $PRF_CHILDREN = 16 Local Const $PRF_CLIENT = 4 _SendMessage($hWnd, $WM_PRINTCLIENT, $hMemDC, BitOr($PRF_CLIENT, $PRF_CHILDREN)) Local $hBrush = _WinAPI_CreateSolidBrush(0x191919) Local $iTabCount = _GUICtrlTab_GetItemCount($hWnd) If $iTabCount > 0 Then ; determine if tab control contains an UpDown (spin) control Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) Local $tPR = _WinAPI_GetWindowRect($hWnd) ; get width of UpDown control $iUpDownWidth = $tCR.Right - $tCR.Left ; exclude UpDown control from being painted over DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, "int", $tCR.Left - $tPR.Left, "int", _ $tCR.Top - $tPR.Top - 1, "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf ; Draw selection indicator (top border for selected tab) Local $tRect, $iLeft, $iTop, $iRight, $iBottom Local $iTabCount = _SendMessage($hWnd, $TCM_GETITEMCOUNT, 0, 0) Local $iCurSel = _SendMessage($hWnd, $TCM_GETCURSEL, 0, 0) For $i = 0 To $iTabCount - 1 ; Get tab rectangle using TCM_GETITEMRECT $tRect = DllStructCreate($tagRECT) Local $aResult = DllCall('user32.dll', "lresult", "SendMessageW", _ "hwnd", $hWnd, _ "uint", $TCM_GETITEMRECT, _ "wparam", $i, _ "struct*", $tRect) If @error Or Not $aResult[0] Then ContinueLoop $iLeft = $tRect.Left $iTop = $tRect.Top $iRight = $tRect.Right $iBottom = $tRect.Bottom ; Skip if rectangle is invalid If $iLeft >= $iRight Or $iTop >= $iBottom Then ContinueLoop Local $bSelected = ($i = $iCurSel) If $bSelected Then Local $hPen = $g_hPenAccent Local $hOldPen = _WinAPI_SelectObject($hMemDC, $hPen) _WinAPI_MoveTo($hMemDC, $iLeft - 1, $iTop - 1) _WinAPI_LineTo($hMemDC, $iRight, $iTop - 2) _WinAPI_SelectObject($hMemDC, $hOldPen) EndIf _WinAPI_SelectObject($hMemDC, $hBrush) _WinAPI_ExtFloodFill($hMemDC, $tRect2.right - 1 - $iUpDownWidth, 1, 0xf0f0f0, $FLOODFILLSURFACE) _WinAPI_ExtFloodFill($hMemDC, $tRect2.left + 1, 1, 0xf0f0f0, $FLOODFILLSURFACE) Next _WinAPI_DeleteObject($hBrush) _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY) _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) EndIf Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" Then If _is24H2Plus() Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>_ModernTabProc Func _is24H2Plus() ; check if this OS build is Windows 11 24H2/25H2 to support the newer DarkMode_DarkTheme Local $iRevision = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "UBR") Local $b24H2Plus = False If @OSBuild >= 26100 And $iRevision >= 6899 Then $b24H2Plus = True Else ConsoleWrite("Windows 11 24H2/25H2 (build 26100.6899 or higher) is required to use DarkMode_DarkTheme.") EndIf Return $b24H2Plus EndFunc Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall('user32.dll', "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc Func _ColorToCOLORREF($iColor) ;RGB to BGR Local $iR = BitAND(BitShift($iColor, 16), 0xFF) Local $iG = BitAND(BitShift($iColor, 8), 0xFF) Local $iB = BitAND($iColor, 0xFF) Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR) EndFunc ;==>_ColorToCOLORREF Edited May 14 by WildByDesign
WildByDesign Posted May 26 Author Posted May 26 (edited) I have something neat to share. So I wanted to create a tab control that mimics the 24H2/25H2 DarkMode_DarkTheme style, colors and behaviours. I am painting over a light mode tab control so this way it does not require the latest Windows 11 builds. The challenge? It was lacking hover indication on non-selected tabs. @mLipok I used your tab control example from here as a starting point. I liked your usage of _WinAPI_FrameRect. @Nine I used your usage of $WM_MOUSEMOVE / $WM_MOUSELEAVE and _WinAPI_PtInRect($tRECT, $tPoint) from the ListView/Header subclassing topic. And miraculously, it works incredibly well. There is likely some duplication of obtaining the tab rectangle. Lots of things that can be made more efficient. If anyone wants to help make it more efficient, please feel free. expandcollapse popup#include <ColorConstants.au3> #include <GUIConstantsEx.au3> #include <GuiTab.au3> #include <TabConstants.au3> #include <WindowsNotifsConstants.au3> #include <WindowsSysColorConstants.au3> #include <WinAPIGdi.au3> #include <WinAPISysWin.au3> #include <WinAPITheme.au3> #include <WinAPISys.au3> #include <WinAPIShellEx.au3> ; Initialize System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Global $g_hTab Global Const $COLOR_BUTTON_BG = 0x383838, $COLOR_BG_DARK = 0x202020, $COLOR_GUI_BG = 0x101010, $COLOR_BORDER = 0x606060, $COLOR_BORDER_DARK = 0x303030 Global $g_hPenAccent = _WinAPI_CreatePen($PS_SOLID, 2, _WinAPI_SwitchColor(0x0078D4)) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl (24H2/25H2)", 500, 300) GUISetBkColor($COLOR_GUI_BG) GUISetFont(10) Local $idTab = GUICtrlCreateTab(20, 20, 460, 260) $g_hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateTabItem("tab1") GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateTabItem("tab3") GUICtrlCreateTabItem("tab4") ; Remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; Set dark titlebar _WinAPI_DwmSetWindowAttribute($hGUI, $DWMWA_USE_IMMERSIVE_DARK_MODE, True) ; Register Subclassing / Window Procedure Local $pAddress = _WinAPI_GetWindowLong($g_hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(_WinProc, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) GUISetState(@SW_SHOW) Local $idMsg While 1 $idMsg = GUIGetMsg() If $idMsg = $GUI_EVENT_CLOSE Then ExitLoop WEnd ; Cleanup: Restore original Window Procedure _WinAPI_RemoveWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _WinProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) ;coded by UEZ Local Static $bHover, $tPoint Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erasing to avoid flickering Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) If @error Or Not $hDC Then Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Local $tClient = _WinAPI_GetClientRect($hWnd) Local $iWidth = $tClient.Right Local $iHeight = $tClient.Bottom ; Prepare Double Buffering Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) ; --- 1. Clipping (Exclude child controls from drawing) --- Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then $tCR = _WinAPI_GetWindowRect($hTabUpDown) If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf ; 2. Draw main background (Dark color) Local $hBrushBg = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_GUI_BG)) ; _WinAPI_FillRect($hMemDC, $tClient, $hBrushBg) ; fill main area, but don't paint tab area Local $tClient2 = _WinAPI_GetClientRect($hWnd) Local $iWidth2 = $tClient2.Right Local $iHeight2 = $tClient2.Bottom Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, 0, DllStructGetPtr($tRECT)) Local $iTabHeight = $tRECT.bottom - $tRECT.top $tClient2.Top += $iTabHeight + 2 Local $hBrushBg2 = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x262626)) ; _WinAPI_FillRect($hMemDC, $tClient2, $hBrushBg2) Local $iTabCount = _SendMessage($hWnd, $TCM_GETITEMCOUNT, 0, 0) Local $iCurSel = _SendMessage($hWnd, $TCM_GETCURSEL, 0, 0) Local $iCurHot = False ; 3. Prepare the Body Frame (The area beneath the tabs) Local $tFirstTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, 0, DllStructGetPtr($tFirstTabRect)) Local $tBodyRect = DllStructCreate($tagRECT) $tBodyRect.Left = 0 $tBodyRect.Top = $tFirstTabRect.Bottom ; Starts at the bottom edge of the tabs $tBodyRect.Right = $iWidth $tBodyRect.Bottom = $iHeight Local $hBrushBorder = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x4e4e4e)) _WinAPI_FrameRect($hMemDC, $tBodyRect, $hBrushBorder) ; 4. Draw the "Gap" to the right of the tabs in GUI background color If $iTabCount > 0 Then Local $tLastTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $iTabCount - 1, DllStructGetPtr($tLastTabRect)) Local $tGapRect = DllStructCreate($tagRECT) $tGapRect.Left = $tLastTabRect.Right + 2 $tGapRect.Top = 0 $tGapRect.Right = $iWidth $tGapRect.Bottom = $tLastTabRect.Bottom Local $hBrushGui = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_GUI_BG)) _WinAPI_FillRect($hMemDC, $tGapRect, $hBrushGui) _WinAPI_DeleteObject($hBrushGui) EndIf _WinAPI_SetBkMode($hMemDC, 1) ; Transparent background for text ;~ _WinAPI_SetTextColor($hMemDC, _ColorToCOLORREF(0xF0F0F0)) ; 5. Draw individual tabs For $i = 0 To $iTabCount - 1 Local $bSelected = ($i = $iCurSel) Local $bHot = ($i = $iCurHot) Local $iTabColor = $bSelected ? $COLOR_DARKSLATEGRAY : $COLOR_BG_DARK Local $hTabBrush = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x262626)) Local $hBrushHov = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x313131)) Local $hBrushUnSel = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x202020)) If $bSelected Then ; draw tab Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $i, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then ContinueLoop $tRECT.top -= 2 ;$tRECT.Left -= 1 ;$tRECT.Right += 1 ; Fill tab background _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush) _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor(0xFFFFFF)) ; Draw border ONLY for the active tab (Top, Left, Right) Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x525252)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) ; OPEN BOTTOM: Draw a line in tab-color over the body-border to merge them Local $tOpenLine = DllStructCreate($tagRECT) $tOpenLine.Left = $tRECT.Left + 1 $tOpenLine.Top = $tRECT.Bottom - 1 ; Exactly on the border line of the body $tOpenLine.Right = $tRECT.Right - 1 $tOpenLine.Bottom = $tRECT.Bottom + 1 _WinAPI_FillRect($hMemDC, $tOpenLine, $hTabBrush) ; selection indicator Local $iLeft = $tRect.Left Local $iTop = $tRect.Top Local $iRight = $tRect.Right Local $iBottom = $tRect.Bottom Local $hPen = $g_hPenAccent Local $hOldPen = _WinAPI_SelectObject($hMemDC, $hPen) _WinAPI_MoveTo($hMemDC, $iLeft, $iTop) _WinAPI_LineTo($hMemDC, $iRight - 1, $iTop + 1) _WinAPI_SelectObject($hMemDC, $hOldPen) ;_WinAPI_DeleteObject($hPen) Else ; draw tab Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $i, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then ContinueLoop $tRECT.top -= 0 $tRECT.bottom += 1 $tRECT.Left -= 1 $tRECT.Right += 1 $iCurHot = $bHover And _WinAPI_PtInRect($tRECT, $tPoint) ; Fill tab background Local $hTabBrush2 = $iCurHot ? $hBrushHov : $hBrushUnSel _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush2) _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor(0xFFFFFF)) ; Draw rectangle around non active tabs Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x525252)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) _WinAPI_DeleteObject($hBrushTabRecDark) EndIf _WinAPI_DeleteObject($hTabBrush) _WinAPI_DeleteObject($hBrushHov) ; Draw text centered Local $sText = _GUICtrlTab_GetItemText($hWnd, $i) Local $tTextRect = DllStructCreate($tagRECT) With $tTextRect .Left = $tRECT.Left + 6 .Top = $tRECT.Top + ($bSelected ? 1 : 3) .Right = $tRECT.Right - 6 .Bottom = $tRECT.Bottom - 3 EndWith DllCall("user32.dll", "int", "DrawTextW", "handle", $hMemDC, "wstr", $sText, "int", -1, "struct*", $tTextRect, "uint", BitOR($DT_CENTER, $DT_VCENTER, $DT_SINGLELINE, $DT_NOPREFIX)) Next ; 6. Copy memory DC to screen DC (BitBlt) _WinAPI_BitBlt($hDC, 0, 0, $iWidth, $iHeight, $hMemDC, 0, 0, $SRCCOPY) ; Cleanup _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteObject($hBrushBg) _WinAPI_DeleteObject($hBrushBg2) _WinAPI_DeleteObject($hBrushBorder) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_MOUSEMOVE ;If $pData = $hWnd Then $bHover = True $tPoint = _WinAPI_GetMousePos(True, $hWnd) _WinAPI_InvalidateRect($hWnd, 0, False) _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE) ;EndIf Case $WM_MOUSELEAVE ;If $pData = $hWnd Then $bHover = False _WinAPI_InvalidateRect($hWnd, 0, False) ;EndIf EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>_WinProc Func _ColorToCOLORREF($iColor) ; Convert RGB to BGR Local $iR = BitAND(BitShift($iColor, 16), 0xFF) Local $iG = BitAND(BitShift($iColor, 8), 0xFF) Local $iB = BitAND($iColor, 0xFF) Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR) EndFunc ;==>_ColorToCOLORREF Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall("user32.dll", "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc Edited May 26 by WildByDesign updated code
WildByDesign Posted May 26 Author Posted May 26 (edited) Oh man, it looks like there is a problem after 30-60 seconds of blissfully hovering over the tabs. The tab area at the top eventually turns all white. 🙃 EDIT: I tried switching back to the older _WinAPI_SetWindowLong subclassing technique and it still goes all white after 30-60 or maybe more of hovering over the tabs. EDIT2: When I get rid of the hover stuff, $WM_MOUSEMOVE / $WM_MOUSELEAVE and _WinAPI_PtInRect($tRECT, $tPoint), it seems to fix the issue. But I really need that hover to work. I am wondering if this is related to the issue with $WM_MOUSEMOVE / $WM_MOUSELEAVE and _WinAPI_PtInRect($tRECT, $tPoint) in the ListView/Header subclassing topic. Maybe something to do with this triggers it. Not sure yet. EDIT3: One interesting observation, if I comment out the lines regarding invalidating the rect, the issue goes away. But of course, the results are not as desirable. Case $WM_MOUSEMOVE If $pData = $hWnd Then $bHover = True $tPoint = _WinAPI_GetMousePos(True, $hWnd) ;_WinAPI_InvalidateRect($hWnd, 0, False) _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE) EndIf Case $WM_MOUSELEAVE If $pData = $hWnd Then $bHover = False ;_WinAPI_InvalidateRect($hWnd, 0, False) EndIf EDIT4: It is definitely related to _WinAPI_InvalidateRect(). I have spent 10+ minutes hovering over the tabs randomly and I have not been able to trigger the issue with the tab area going all white. The only problem is that some "hot" tabs keep the hot color when they are no longer hot. Edited May 26 by WildByDesign
WildByDesign Posted May 26 Author Posted May 26 Here is my current code with _WinAPI_InvalidateRect() commented out. We avoid the issue with the tab area going all white. However, we have a new issue with the hot tab color remaining randomly on some tabs that are no longer hot. Hopefully someone else can figure this part out because I am out of ideas for now. expandcollapse popup#include <ColorConstants.au3> #include <GUIConstantsEx.au3> #include <GuiTab.au3> #include <TabConstants.au3> #include <WindowsNotifsConstants.au3> #include <WindowsSysColorConstants.au3> #include <WinAPIGdi.au3> #include <WinAPISysWin.au3> #include <WinAPITheme.au3> #include <WinAPISys.au3> #include <WinAPIShellEx.au3> ; Initialize System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Global $g_hTab Global Const $COLOR_BUTTON_BG = 0x383838, $COLOR_BG_DARK = 0x202020, $COLOR_GUI_BG = 0x101010, $COLOR_BORDER = 0x606060, $COLOR_BORDER_DARK = 0x303030 Global $g_hPenAccent = _WinAPI_CreatePen($PS_SOLID, 2, _WinAPI_SwitchColor(0x0078D4)) Example() Func Example() Local $hGUI = GUICreate("DarkTheme TabControl (24H2/25H2)", 500, 300) GUISetBkColor($COLOR_GUI_BG) GUISetFont(10) Local $idTab = GUICtrlCreateTab(20, 20, 460, 260) $g_hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateTabItem("tab1") GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateTabItem("tab3") GUICtrlCreateTabItem("tab4") ; Remove focus rectangle from tab control GUICtrlSendMsg($idTab, $WM_CHANGEUISTATE, 65537, 0) ; Set dark titlebar _WinAPI_DwmSetWindowAttribute($hGUI, $DWMWA_USE_IMMERSIVE_DARK_MODE, True) ; Register Subclassing / Window Procedure Local $pAddress = _WinAPI_GetWindowLong($g_hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(_WinProc, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") ;_WinAPI_SetWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) _WinAPI_SetWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab, $g_hTab) GUISetState(@SW_SHOW) Local $idMsg While 1 $idMsg = GUIGetMsg() If $idMsg = $GUI_EVENT_CLOSE Then ExitLoop WEnd ; Cleanup: Restore original Window Procedure _WinAPI_RemoveWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _WinProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) ;coded by UEZ Local Static $bHover, $tPoint Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erasing to avoid flickering Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) If @error Or Not $hDC Then Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Local $tClient = _WinAPI_GetClientRect($hWnd) Local $iWidth = $tClient.Right Local $iHeight = $tClient.Bottom ; Prepare Double Buffering Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) ; --- 1. Clipping (Exclude child controls from drawing) --- Local $hParent = _WinAPI_GetParent($hWnd) Local $hChild = _WinAPI_GetWindow($hParent, $GW_CHILD) Local $tCR, $tPR = _WinAPI_GetWindowRect($hWnd) Local $left, $top, $right, $bottom While $hChild If $hChild <> $hWnd And _WinAPI_IsWindowVisible($hChild) Then $tCR = _WinAPI_GetWindowRect($hChild) If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf $hChild = _WinAPI_GetWindow($hChild, $GW_HWNDNEXT) WEnd Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then $tCR = _WinAPI_GetWindowRect($hTabUpDown) If Not ($tCR.right < $tPR.left Or $tCR.left > $tPR.right Or $tCR.bottom < $tPR.top Or $tCR.top > $tPR.bottom) Then $left = Max($tCR.left, $tPR.left) - $tPR.left $top = Max($tCR.top, $tPR.top) - $tPR.top $right = Min($tCR.right, $tPR.right) - $tPR.left $bottom = Min($tCR.bottom, $tPR.bottom) - $tPR.top DllCall("gdi32.dll", "int", "ExcludeClipRect", "handle", $hMemDC, "int", $left, "int", $top, "int", $right, "int", $bottom) EndIf EndIf ; 2. Draw main background (Dark color) Local $hBrushBg = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_GUI_BG)) ; _WinAPI_FillRect($hMemDC, $tClient, $hBrushBg) ; fill main area, but don't paint tab area Local $tClient2 = _WinAPI_GetClientRect($hWnd) Local $iWidth2 = $tClient2.Right Local $iHeight2 = $tClient2.Bottom Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, 0, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Local $iTabHeight = $tRECT.bottom - $tRECT.top $tClient2.Top += $iTabHeight + 2 Local $hBrushBg2 = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x262626)) ; _WinAPI_FillRect($hMemDC, $tClient2, $hBrushBg2) Local $iTabCount = _SendMessage($hWnd, $TCM_GETITEMCOUNT, 0, 0) Local $iCurSel = _SendMessage($hWnd, $TCM_GETCURSEL, 0, 0) Local $iCurHot = False ; 3. Prepare the Body Frame (The area beneath the tabs) Local $tFirstTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, 0, DllStructGetPtr($tFirstTabRect)) Local $tBodyRect = DllStructCreate($tagRECT) $tBodyRect.Left = 0 $tBodyRect.Top = $tFirstTabRect.Bottom ; Starts at the bottom edge of the tabs $tBodyRect.Right = $iWidth $tBodyRect.Bottom = $iHeight Local $hBrushBorder = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x4e4e4e)) _WinAPI_FrameRect($hMemDC, $tBodyRect, $hBrushBorder) ; 4. Draw the "Gap" to the right of the tabs in GUI background color If $iTabCount > 0 Then Local $tLastTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $iTabCount - 1, DllStructGetPtr($tLastTabRect)) Local $tGapRect = DllStructCreate($tagRECT) $tGapRect.Left = $tLastTabRect.Right + 2 $tGapRect.Top = 0 $tGapRect.Right = $iWidth $tGapRect.Bottom = $tLastTabRect.Bottom Local $hBrushGui = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_GUI_BG)) _WinAPI_FillRect($hMemDC, $tGapRect, $hBrushGui) _WinAPI_DeleteObject($hBrushGui) EndIf _WinAPI_SetBkMode($hMemDC, 1) ; Transparent background for text ;~ _WinAPI_SetTextColor($hMemDC, _ColorToCOLORREF(0xF0F0F0)) ; 5. Draw individual tabs For $i = 0 To $iTabCount - 1 Local $bSelected = ($i = $iCurSel) Local $bHot = ($i = $iCurHot) Local $iTabColor = $bSelected ? $COLOR_DARKSLATEGRAY : $COLOR_BG_DARK Local $hTabBrush = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x262626)) Local $hBrushHov = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x313131)) Local $hBrushUnSel = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x202020)) If $bSelected Then ; draw tab Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $i, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then ContinueLoop $tRECT.top -= 2 ;$tRECT.Left -= 1 ;$tRECT.Right += 1 ; Fill tab background _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush) _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor(0xFFFFFF)) ; Draw border ONLY for the active tab (Top, Left, Right) Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x525252)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) ; OPEN BOTTOM: Draw a line in tab-color over the body-border to merge them Local $tOpenLine = DllStructCreate($tagRECT) $tOpenLine.Left = $tRECT.Left + 1 $tOpenLine.Top = $tRECT.Bottom - 1 ; Exactly on the border line of the body $tOpenLine.Right = $tRECT.Right - 1 $tOpenLine.Bottom = $tRECT.Bottom + 1 _WinAPI_FillRect($hMemDC, $tOpenLine, $hTabBrush) ; selection indicator Local $iLeft = $tRect.Left Local $iTop = $tRect.Top Local $iRight = $tRect.Right Local $iBottom = $tRect.Bottom Local $hPen = $g_hPenAccent Local $hOldPen = _WinAPI_SelectObject($hMemDC, $hPen) _WinAPI_MoveTo($hMemDC, $iLeft, $iTop) _WinAPI_LineTo($hMemDC, $iRight - 1, $iTop + 1) _WinAPI_SelectObject($hMemDC, $hOldPen) ;_WinAPI_DeleteObject($hPen) Else ; draw tab Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $i, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then ContinueLoop $tRECT.top -= 0 $tRECT.bottom += 1 $tRECT.Left -= 1 $tRECT.Right += 1 $iCurHot = $bHover And _WinAPI_PtInRect($tRECT, $tPoint) ; Fill tab background Local $hTabBrush2 = $iCurHot ? $hBrushHov : $hBrushUnSel _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush2) _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor(0xFFFFFF)) ; Draw rectangle around non active tabs Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF(0x525252)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) _WinAPI_DeleteObject($hBrushTabRecDark) EndIf _WinAPI_DeleteObject($hTabBrush) _WinAPI_DeleteObject($hBrushHov) ; Draw text centered Local $sText = _GUICtrlTab_GetItemText($hWnd, $i) Local $tTextRect = DllStructCreate($tagRECT) With $tTextRect .Left = $tRECT.Left + 6 .Top = $tRECT.Top + ($bSelected ? 1 : 3) .Right = $tRECT.Right - 6 .Bottom = $tRECT.Bottom - 3 EndWith DllCall("user32.dll", "int", "DrawTextW", "handle", $hMemDC, "wstr", $sText, "int", -1, "struct*", $tTextRect, "uint", BitOR($DT_CENTER, $DT_VCENTER, $DT_SINGLELINE, $DT_NOPREFIX)) Next ; 6. Copy memory DC to screen DC (BitBlt) _WinAPI_BitBlt($hDC, 0, 0, $iWidth, $iHeight, $hMemDC, 0, 0, $SRCCOPY) ; Cleanup _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteObject($hBrushBg) _WinAPI_DeleteObject($hBrushBg2) _WinAPI_DeleteObject($hBrushBorder) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_MOUSEMOVE If $pData = $hWnd Then $bHover = True $tPoint = _WinAPI_GetMousePos(True, $hWnd) ;_WinAPI_InvalidateRect($hWnd, 0, False) _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE) EndIf Case $WM_MOUSELEAVE If $pData = $hWnd Then $bHover = False ;_WinAPI_InvalidateRect($hWnd, 0, False) EndIf EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>_WinProc Func _ColorToCOLORREF($iColor) ; Convert RGB to BGR Local $iR = BitAND(BitShift($iColor, 16), 0xFF) Local $iG = BitAND(BitShift($iColor, 8), 0xFF) Local $iB = BitAND($iColor, 0xFF) Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR) EndFunc ;==>_ColorToCOLORREF Func Min($a, $b) Return ($a < $b ? $a : $b) EndFunc ;==>Min Func Max($a, $b) Return ($a > $b ? $a : $b) EndFunc ;==>Max Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall("user32.dll", "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc
WildByDesign Posted May 26 Author Posted May 26 (edited) I’m going to be away from my PC for the majority of the day today, but I have an idea that I just can’t test right now. What if we use SetWindowPos or RedrawWindow in the mouse related messages instead of InvalidateRect? Although the whole thing seems like a timing issue of some sort, so we might still hit the issue regardless. Edited May 26 by WildByDesign
WildByDesign Posted May 27 Author Posted May 27 I ended up having to give up on the hover color for non-selected tabs, unfortunately. The method using $WM_MOUSEMOVE / $WM_MOUSELEAVE and _WinAPI_PtInRect($tRECT, $tPoint) technique seems to fire up the WM_PAINT far faster than AutoIt subclassing is able to handle. Even using SetWindowPos or RedrawWindow instead of InvalidateRect worked but would still eventually cause the same problem. Putting that aside, I've retouched the colors and pixel sizes to what my eyes seemed to absolutely love. I cleaned up a lot of the code and I am pretty confident that this will be going into GUIDarkTheme UDF soon. The good thing is that this will not require Windows 11 24H2/25H2 and therefore can be supported by more operating system builds. Custom Dark TabControl: expandcollapse popup#include <APIGdiConstants.au3> #include <StructureConstants.au3> #include <WindowsStylesConstants.au3> #include <ColorConstants.au3> #include <GUIConstantsEx.au3> #include <GuiTab.au3> #include <TabConstants.au3> #include <WindowsNotifsConstants.au3> #include <WindowsSysColorConstants.au3> #include <WinAPIGdi.au3> #include <WinAPISysWin.au3> #include <WinAPITheme.au3> #include <WinAPISys.au3> #include <WinAPIShellEx.au3> ; Initialize System DPI awareness DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2) Global $g_hTab Global Const $COLOR_BUTTON_BG = 0x383838, $COLOR_BG_DARK = 0x202020, $COLOR_GUI_BG = 0x191919, $COLOR_BORDER = 0x606060, $COLOR_BORDER_DARK = 0x303030 Global $g_hTabBg = 0x202020, $g_hTabUnSel = 0x303030, $g_hTabFrame = 0x525252 Global $g_hPenAccent = _WinAPI_CreatePen($PS_SOLID, 2, _WinAPI_SwitchColor(0x0078D4)) Global $g_hPenGui = _WinAPI_CreatePen($PS_SOLID, 2, _WinAPI_SwitchColor(0x191919)) Example() Func Example() Local $hGUI = GUICreate("Custom Dark TabControl", 500, 300, -1, -1, $WS_OVERLAPPEDWINDOW) GUISetBkColor($COLOR_GUI_BG) GUISetFont(10) Local $idTab = GUICtrlCreateTab(20, 20, 460, 260) GUICtrlSetResizing(-1, $GUI_DOCKLEFT + $GUI_DOCKRIGHT + $GUI_DOCKTOP + $GUI_DOCKBOTTOM) $g_hTab = GUICtrlGetHandle($idTab) GUICtrlCreateTabItem("tab0") GUICtrlCreateTabItem("tab1") GUICtrlCreateTabItem("tab2") GUICtrlSetState(-1, $GUI_SHOW) GUICtrlCreateLabel("Resize GUI to trigger UpDown controls.", 30, 80, 300, 100) GUICtrlSetColor(-1, 0xFFFFFF) GUICtrlSetBkColor(-1, $g_hTabBg) GUICtrlSetResizing(-1, $GUI_DOCKLEFT+$GUI_DOCKTOP) GUICtrlCreateTabItem("tab3") GUICtrlCreateTabItem("tab4") ; Set dark titlebar _WinAPI_DwmSetWindowAttribute($hGUI, $DWMWA_USE_IMMERSIVE_DARK_MODE, True) ; Register Subclassing / Window Procedure Local $pAddress = _WinAPI_GetWindowLong($g_hTab, $GWL_WNDPROC) Local $hSubclass = DllCallbackRegister(_WinProc, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab, $pAddress) ; Send tab control below controls to prevent flickering and overpainting by tab control subclass _WinAPI_SetWindowPos($g_hTab, $HWND_BOTTOM, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOREDRAW, $SWP_NOSIZE)) GUISetState(@SW_SHOW) Local $idMsg While 1 $idMsg = GUIGetMsg() If $idMsg = $GUI_EVENT_CLOSE Then ExitLoop WEnd ; Cleanup: Restore original Window Procedure _WinAPI_RemoveWindowSubclass($g_hTab, DllCallbackGetPtr($hSubclass), $idTab) DllCallbackFree($hSubclass) EndFunc ;==>Example Func _WinProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) ;coded by UEZ Local Static $bHover, $tPoint Switch $iMsg Case $WM_ERASEBKGND Return 1 ; Prevent background erasing to avoid flickering Case $WM_PAINT Local $tPaint = DllStructCreate($tagPAINTSTRUCT) Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint) If @error Or Not $hDC Then Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Local $tClient = _WinAPI_GetClientRect($hWnd) Local $iWidth = $tClient.Right Local $iHeight = $tClient.Bottom ; Prepare Double Buffering Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight) Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap) ; Determine if tab control contains an UpDown (spin) control Local $hTabUpDown = _WinAPI_FindWindowEx($hWnd, "msctls_updown32") If $hTabUpDown And _WinAPI_IsWindowVisible($hTabUpDown) Then Local $tCR = _WinAPI_GetWindowRect($hTabUpDown) Local $tPR = _WinAPI_GetWindowRect($hWnd) ; Exclude UpDown control from being painted over DllCall('gdi32.dll', "int", "ExcludeClipRect", "handle", $hDC, "int", $tCR.Left - $tPR.Left, "int", _ $tCR.Top - $tPR.Top - 100, "int", $tCR.Right - $tPR.Left, "int", $tCR.Bottom - $tPR.Top + 2) EndIf ; Fill entire tab control with GUI background color Local $hBrushBg = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($COLOR_GUI_BG)) _WinAPI_FillRect($hMemDC, $tClient, $hBrushBg) Local $iTabCount = _SendMessage($hWnd, $TCM_GETITEMCOUNT, 0, 0) Local $iCurSel = _SendMessage($hWnd, $TCM_GETCURSEL, 0, 0) Local $iCurHot = False ; Setup font Local $hFont = _SendMessage($hWnd, $WM_GETFONT, 0, 0) If Not $hFont Then $hFont = _WinAPI_GetStockObject($DEFAULT_GUI_FONT) Local $hOldFont = _WinAPI_SelectObject($hMemDC, $hFont) ; Prepare the Body Frame (The area beneath the tabs) Local $tFirstTabRect = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, 0, DllStructGetPtr($tFirstTabRect)) Local $tBodyRect = DllStructCreate($tagRECT) $tBodyRect.Left = 0 $tBodyRect.Top = $tFirstTabRect.Bottom ; Starts at the bottom edge of the tabs $tBodyRect.Right = $iWidth $tBodyRect.Bottom = $iHeight Local $hBrushBorder = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($g_hTabFrame)) Local $hBrushTabBody = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($g_hTabBg)) _WinAPI_FillRect($hMemDC, $tBodyRect, $hBrushTabBody) _WinAPI_FrameRect($hMemDC, $tBodyRect, $hBrushBorder) _WinAPI_DeleteObject($hBrushTabBody) _WinAPI_SetBkMode($hMemDC, $TRANSPARENT) _WinAPI_SetTextColor($hMemDC, _WinAPI_SwitchColor(0xFFFFFF)) ; Draw individual tabs For $i = 0 To $iTabCount - 1 Local $bSelected = ($i = $iCurSel) Local $iTabColor = $bSelected ? $COLOR_DARKSLATEGRAY : $COLOR_BG_DARK Local $hTabBrush = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($g_hTabBg)) Local $hBrushUnSel = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($g_hTabUnSel)) Local $tRECT = DllStructCreate($tagRECT) _SendMessage($hWnd, $TCM_GETITEMRECT, $i, DllStructGetPtr($tRECT)) If $tRECT.Right < 0 Or $tRECT.Left > $iWidth Then ContinueLoop If $bSelected Then ; Draw selected tab $tRECT.top -= 2 ; Fill tab background _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush) ; Draw border ONLY for the active tab (Top, Left, Right) Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($g_hTabFrame)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) ; OPEN BOTTOM: Draw a line in tab-color over the body-border to merge them Local $tOpenLine = DllStructCreate($tagRECT) $tOpenLine.Left = $tRECT.Left + 1 $tOpenLine.Top = $tRECT.Bottom - 1 ; Exactly on the border line of the body $tOpenLine.Right = $tRECT.Right - 1 $tOpenLine.Bottom = $tRECT.Bottom + 1 _WinAPI_FillRect($hMemDC, $tOpenLine, $hTabBrush) ; Draw selection indicator with accent color Local $iLeft = $tRECT.Left Local $iTop = $tRECT.Top Local $iRight = $tRECT.Right Local $iBottom = $tRECT.Bottom Local $hPen = $g_hPenAccent Local $hOldPen = _WinAPI_SelectObject($hMemDC, $hPen) _WinAPI_MoveTo($hMemDC, $iLeft, $iTop) _WinAPI_LineTo($hMemDC, $iRight - 1, $iTop + 1) _WinAPI_SelectObject($hMemDC, $hOldPen) ;_WinAPI_DeleteObject($hPen) Else ; draw tab $tRECT.top -= 0 $tRECT.bottom += 1 $tRECT.Left -= 1 $tRECT.Right += 1 ; Fill tab background Local $hTabBrush2 = $hBrushUnSel _WinAPI_FillRect($hMemDC, $tRECT, $hTabBrush2) ; Draw rectangle around non active tabs Local $hBrushTabRecDark = _WinAPI_CreateSolidBrush(_ColorToCOLORREF($g_hTabFrame)) _WinAPI_FrameRect($hMemDC, $tRECT, $hBrushTabRecDark) _WinAPI_DeleteObject($hBrushTabRecDark) EndIf _WinAPI_DeleteObject($hTabBrush) ; Draw text centered Local $sText = _GUICtrlTab_GetItemText($hWnd, $i) Local $tTextRect = DllStructCreate($tagRECT) With $tTextRect .Left = $tRECT.Left + 6 .Top = $tRECT.Top + ($bSelected ? 1 : 3) .Right = $tRECT.Right - 6 .Bottom = $tRECT.Bottom - 3 EndWith DllCall("user32.dll", "int", "DrawTextW", "handle", $hMemDC, "wstr", $sText, "int", -1, "struct*", $tTextRect, "uint", BitOR($DT_CENTER, $DT_VCENTER, $DT_SINGLELINE, $DT_NOPREFIX, $DT_NOCLIP)) Next ; Copy memory DC to screen DC (BitBlt) _WinAPI_BitBlt($hDC, 0, 0, $iWidth, $iHeight, $hMemDC, 0, 0, $SRCCOPY) ; Cleanup _WinAPI_SelectObject($hMemDC, $hOldBmp) _WinAPI_SelectObject($hMemDC, $hOldFont) _WinAPI_DeleteObject($hBitmap) _WinAPI_DeleteObject($hBrushBg) _WinAPI_DeleteObject($hBrushBorder) _WinAPI_DeleteDC($hMemDC) _WinAPI_EndPaint($hWnd, $tPaint) Return 0 Case $WM_PARENTNOTIFY ; Fired when a child window is created inside the tab control. ; The tab spinner (msctls_updown32) is created lazily by Windows when tabs overflow - ; it doesn't exist at init time, so we theme it here the moment it appears. If _WinAPI_LoWord($wParam) = $WM_CREATE Then Local $hNewChild = HWnd($lParam) ; lParam carries the new child's HWND as integer - must cast! If _WinAPI_GetClassName($hNewChild) = "msctls_updown32" And @OSBuild >= 26100 Then _WinAPI_SetWindowTheme($hNewChild, 'DarkMode_DarkTheme') EndIf EndIf Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>_WinProc Func _ColorToCOLORREF($iColor) ; Convert RGB to BGR Local $iR = BitAND(BitShift($iColor, 16), 0xFF) Local $iG = BitAND(BitShift($iColor, 8), 0xFF) Local $iB = BitAND($iColor, 0xFF) Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR) EndFunc ;==>_ColorToCOLORREF Func _WinAPI_FindWindowEx($hParent, $sClass, $sTitle = "", $hAfter = 0) Local $ret = DllCall("user32.dll", "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle) If @error Or Not IsArray($ret) Then Return 0 Return $ret[0] EndFunc ;==>_WinAPI_FindWindowEx Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _ 'lparam', $lParam)[0] EndFunc ;==>__WinAPI_DefSubclassProc argumentum 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now