Jump to content

Recommended Posts

Posted
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.

#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

 

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 Codefor 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 APIErrorLog.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 TaskSchedulerIE 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 stuffOnHungApp handlerAvoid "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"
:naughty:  :ranting:, 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

Posted (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.

ActiveTab1.png.347e397eff9b8862f3eaec7f421747ad.png

ActiveTab2.png.039d171bfe734fd10a033a4656b97608.png

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. 

; 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 by WildByDesign
Posted (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.

; 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 by WildByDesign
Posted
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.

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Posted
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.

Posted (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:

; 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 by WildByDesign
Posted (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 by WildByDesign
Posted
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:

; 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!

 

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!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Posted

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. :)

; 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

 

Posted

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.  

Posted
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.

Posted
13 hours ago, WildByDesign said:

Can you please share your recent testing example with icons?

Quote
; 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

 

 

Posted (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.

; 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 by WildByDesign
  • 2 weeks later...
Posted (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.

#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 by WildByDesign
updated code
Posted (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 by WildByDesign
Posted

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.

#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

 

Posted (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 by WildByDesign
Posted

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:

#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

 

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...