Jump to content

Need help with FillRect on Dark Mode statusbar


Go to solution Solved by Andreik,

Recommended Posts

Posted

Dark Mode on Windows has many areas or specific combinations of things where certain controls have incomplete dark mode theming. 25H2 fixes this plus many other dark theme issues but that does not help the majority of users.

Example:

statusbar2.png.e355785f0b249175fb6d017b405a404a.png

 

I kept this example script as small as possible to make it easier to help with the area that needs help.

I already have other parts to the script that complete the proper functioning of the parts as well as the painting of the gripper dots over that bottom right corner courtesy of @pixelsearch and @Andreik.

The functionality that paints the gripper dots also has the ability to paint a background. However, since that is in it's own "Scrollbar" window, it is not painted directly on the statusbar control.

The reason why I need a FillRect directly on the statusbar control over that small square is because that is the only way that it will accept the Windows 11 materials such as Mica, Acrylic, etc.

The size of that square is 16 pixels multiplied by the current DPI scale. Eg. 16 x 1.25 on 125%, 16 x 1.5 on 150% and so on.

On 100% and 125% scaling, it is 2 pixels in from the right and 2 pixels up from the bottom of the window. On 150% scaling it is 3 pixels in from right and 3 pixels up from bottom. So I assume that it is doing 2 x the current DPI scale. Although for 125% scaling it seems to round down from 2.5 to 2 instead of rounding up.

Anyway, if anybody knows how to get the position of that square, we should be able to do the FillRect in the _WM_DRAWITEM function.

Thank you for your time. I appreciate it very much, always. :)

Example script:

#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <GuiStatusBar.au3>
#include <WindowsConstants.au3>
#include <Array.au3>
#include <File.au3>
#include <WinAPI.au3>

; DPI awareness
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext" , "HWND", "DPI_AWARENESS_CONTEXT" -2)

Global $hStatus

$Form1 = GUICreate("Form1", 615, 437, -1, -1, $WS_SYSMENU + $WS_MINIMIZEBOX + $WS_MAXIMIZEBOX + $WS_SIZEBOX)
GUISetBkColor(0x202020)
GUICtrlSetDefColor(0x202020)
$hStatus = _GUICtrlStatusBar_Create($Form1)
GUIRegisterMsg($WM_DRAWITEM, "_WM_DRAWITEM")

; Set parts
Local $aParts[4] = [75, 150, 300, -1]
_GUICtrlStatusBar_SetParts($hStatus, $aParts)
_GUICtrlStatusBar_SetText($hStatus, "Part 0", 0, $SBT_OWNERDRAW)
_GUICtrlStatusBar_SetText($hStatus, "Part 1", 1, $SBT_OWNERDRAW)
_GUICtrlStatusBar_SetText($hStatus, "Part 2", 2, $SBT_OWNERDRAW)
_GUICtrlStatusBar_SetText($hStatus, "Part 3", 3, $SBT_OWNERDRAW)

Global $global_StatusBar_Text = "  Part 1"

; apply dark mode to GUI
DarkMode($Form1, True)
; apply dark theme to statusbar
_WinAPI_SetWindowTheme_unr($hStatus, "DarkMode", "ExplorerStatusBar")

GUISetState(@SW_SHOW)
GUIRegisterMsg($WM_SIZE, "WM_SIZE")

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

; Resize the status bar when GUI size changes
Func WM_SIZE($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam
    _GUICtrlStatusBar_Resize($hStatus)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_SIZE

Func _WM_DRAWITEM($hWnd, $Msg, $wParam, $lParam)
    #forceref $Msg, $wParam, $lParam
    Local $tDRAWITEMSTRUCT = DllStructCreate("uint CtlType;uint CtlID;uint itemID;uint itemAction;uint itemState;HWND hwndItem;HANDLE hDC;long rcItem[4];ULONG_PTR itemData", $lParam)

    If DllStructGetData($tDRAWITEMSTRUCT, "hwndItem") <> $hStatus Then Return $GUI_RUNDEFMSG ; Only process the statusbar

    Local $itemID = DllStructGetData($tDRAWITEMSTRUCT, "itemID") ;part number
    Local $hDC = DllStructGetData($tDRAWITEMSTRUCT, "hDC")
    Local $tRect = DllStructCreate("long left;long top;long right; long bottom", DllStructGetPtr($tDRAWITEMSTRUCT, "rcItem"))
    Local $iTop = DllStructGetData($tRect, "top")
    Local $iLeft = DllStructGetData($tRect, "left")
    Local $hBrush

    $hBrush = _WinAPI_CreateSolidBrush(0x000000) ; Backgound Color
    _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrush)
    _WinAPI_SetTextColor($hDC, 0xFFFFFF) ; Font Color
    _WinAPI_SetBkMode($hDC, $TRANSPARENT)
    DllStructSetData($tRect, "top", $iTop + 1)
    DllStructSetData($tRect, "left", $iLeft + 1)
    _WinAPI_DrawText($hDC, $global_StatusBar_Text, $tRect, $DT_LEFT)
    _WinAPI_DeleteObject($hBrush)

    $tDRAWITEMSTRUCT = 0
    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_DRAWITEM

;--------------------------------------------------------------------------------------------------------------------------------
; https://www.autoitscript.com/forum/topic/211475-darkmode-udf-for-autoits-win32guis/#comment-1530103
;--------------------------------------------------------------------------------------------------------------------------------
Func DarkMode($hGUI, $bDarkMode = True)                               ; DarkMode

    Local Enum $DWMWA_USE_IMMERSIVE_DARK_MODE = (@OSBuild <= 18985) ? 19 : 20
    ;ConsoleWrite("$DWMWA_USE_IMMERSIVE_DARK_MODE=" & $DWMWA_USE_IMMERSIVE_DARK_MODE & @CRLF)
    ;       DWMWA_USE_IMMERSIVE_DARK_MODE ; https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
    ;       Use with DwmSetWindowAttribute. Allows the window frame for this window to be drawn in dark mode colors when the dark mode system setting is enabled.
    ;       For compatibility reasons, all windows default to light mode regardless of the system setting.
    ;       The pvAttribute parameter points to a value of type BOOL. TRUE to honor dark mode for the window, FALSE to always use light mode.
    ;       This value is supported starting with Windows 11 Build 22000.

    Local $iRet = _WinAPI_DwmSetWindowAttribute_unr($hGUI, $DWMWA_USE_IMMERSIVE_DARK_MODE, $bDarkMode)
    If Not $iRet Then Return SetError(1, 0, -1)

    ;_SetCtrlColorMode($Button_OK, $bDarkMode)
    ;_SetCtrlColorMode($Button_copy, $bDarkMode)
    _SetCtrlColorMode($hGUI, $bDarkMode)
    ;_WinAPI_SetWindowTheme_unr(GUICtrlGetHandle($ChbxDrkMode), 0, 0) ; this control needs the theme

EndFunc   ;==>DarkMode
;--------------------------------------------------------------------------------------------------------------------------------
Func _SetCtrlColorMode($hWnd, $bDarkMode = True, $sName = Default)    ; 'Explorer', 'CFD', 'DarkMode_ItemsView', etc.
    If $sName = Default Then $sName = $bDarkMode ? 'DarkMode_Explorer' : 'Explorer'
    $bDarkMode = Not Not $bDarkMode ; https://www.vbforums.com/showthread.php?900444-Windows-10-Dark-Mode-amp-VB6-apps
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    Local Enum $eDefault, $eAllowDark, $eForceDark, $eForceLight, $eMax ; enum PreferredAppMode
    DllCall('uxtheme.dll', 'bool', 133, 'hwnd', $hWnd, 'bool', $bDarkMode) ; fnAllowDarkModeForWindow = 133
    DllCall('uxtheme.dll', 'int', 135, 'int', ($bDarkMode ? $eForceDark : $eForceLight)) ; fnAllowDarkModeForApp = 135
    _WinAPI_SetWindowTheme_unr($hWnd, $sName) ; https://www.autoitscript.com/forum/index.php?showtopic=211475&view=findpost&p=1530103
    DllCall('uxtheme.dll', 'none', 104) ; fnRefreshImmersiveColorPolicyState = 104  ; not needed ?
    _SendMessage($hWnd, $WM_THEMECHANGED, 0, 0) ; not needed ?
EndFunc   ;==>_SetCtrlColorMode
;--------------------------------------------------------------------------------------------------------------------------------
Func _WinAPI_SetWindowTheme_unr($hWnd, $sName = Null, $sList = Null)  ; #include <WinAPITheme.au3> ; unthoughtful unrestricting mod.
    ;Causes a window to use a different set of visual style information than its class normally uses
    Local $sResult = DllCall('UxTheme.dll', 'long', 'SetWindowTheme', 'hwnd', $hWnd, 'wstr', $sName, 'wstr', $sList)
    If @error Then Return SetError(@error, @extended, 0)
    If $sResult[0] Then Return SetError(10, $sResult[0], 0)
    Return 1
EndFunc   ;==>_WinAPI_SetWindowTheme_unr
;--------------------------------------------------------------------------------------------------------------------------------
Func _WinAPI_DwmSetWindowAttribute_unr($hWnd, $iAttribute, $iData)    ; #include <WinAPIGdi.au3> ; unthoughtful unrestricting mod.
    ;Sets the value of the specified attributes for non-client rendering to apply to the window
    Local $aCall = DllCall('dwmapi.dll', 'long', 'DwmSetWindowAttribute', 'hwnd', $hWnd, 'dword', $iAttribute, _
            'dword*', $iData, 'dword', 4)
    If @error Then Return SetError(@error, @extended, 0)
    If $aCall[0] Then Return SetError(10, $aCall[0], 0)
    Return 1
EndFunc   ;==>_WinAPI_DwmSetWindowAttribute_unr
;--------------------------------------------------------------------------------------------------------------------------------

 

Posted
6 minutes ago, Andreik said:

Did you tried a combination of GetThemePosition() and GetThemePartSize() with SP_GRIPPER as theme part?

No, I am not familiar with those functions at all so I haven’t tried them yet. I’m just looking up the details for them on the libfunctions page and those functions look appropriate. I will give them a try. Thank you. :)

 

Posted

There doesn't seem to be any examples for those functions in the help section or forum so I'm not entirely sure how to properly use them.

I noticed that Notepad++ uses it. It's C++ though.

Link: https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/src/WinControls/StatusBar/StatusBar.cpp#L213-L222

			{
				pStatusBarInfo->ensureTheme(hWnd);
				SIZE gripSize{};
				RECT rc{};
				::GetClientRect(hWnd, &rc);
				GetThemePartSize(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rc, TS_DRAW, &gripSize);
				rc.left = rc.right - gripSize.cx;
				rc.top = rc.bottom - gripSize.cy;
				DrawThemeBackground(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rc, nullptr);
			}

 

Posted
20 hours ago, Andreik said:

Did you tried a combination of GetThemePosition() and GetThemePartSize() with SP_GRIPPER as theme part?

I have spent a few hours trying these but not working yet. I have clearly made a mistake somewhere. I can get the theme handle, but any of the API calls result in error.

Here is what I have for testing:

#include <WindowsStylesConstants.au3>
#include <GUIConstantsEx.au3>
#include <GuiStatusBar.au3>
#include <WinAPITheme.au3>

; DPI
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext" , "HWND", "DPI_AWARENESS_CONTEXT" -2)

Global $g_hStatus, $hGUI

Example()

Func Example()
    ; Create GUI
    Local $hGUI = GUICreate("StatusBar Create (v" & @AutoItVersion & ")", 450, 320, 100, 100, $WS_OVERLAPPEDWINDOW)

    ; adjusts array to largest array passed in (this time the text array is the largest)
    Local $aText[3] = ["Part 0", "Part 1", "Part 2"]
    Local $aParts[2] = [100, 175]
    $g_hStatus = _GUICtrlStatusBar_Create($hGUI, $aParts, $aText)

    GUISetState(@SW_SHOW)

    themeData()

    ; Loop until the user exits.
    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete($hGUI)
EndFunc   ;==>Example

Func themeData()
    Local Const $SP_GRIPPERPANE = 2
    Local Const $SP_GRIPPER = 3
    ConsoleWrite("theme data check" & @CRLF)
    Local $hTheme = _WinAPI_OpenThemeData($hGUI, "Status")
    ConsoleWrite("theme handle: " & $hTheme & @CRLF)
    Local $iTest = _WinAPI_GetThemeMetric($hTheme, $SP_GRIPPER, Null, $TMT_WIDTH)
    If @error Then ConsoleWrite("Error on _WinAPI_GetThemeMetric" & @CRLF)
    Local $tPOINT = _WinAPI_GetThemePosition ($hTheme, $SP_GRIPPER, Null, $TMT_DEFAULTPANESIZE)
    If @error Then ConsoleWrite("Error on _WinAPI_GetThemePosition" & @CRLF)
    _WinAPI_CloseThemeData($hTheme)
EndFunc

 

I have tried both Null and 0 for the StateID. Apparently the statusbar theme parts have no states at all according to MS docs and header files.

  • Solution
Posted

Well it seems you can get just the size of the grip and you need to calculate the position. Is that what you need?

#include <WindowsStylesConstants.au3>
#include <GUIConstantsEx.au3>
#include <GuiStatusBar.au3>
#include <WinAPITheme.au3>
#include <GDIPlus.au3>


; DPI
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext" , "HWND", "DPI_AWARENESS_CONTEXT" -2)

Global $g_hStatus, $hGUI

Example()

Func Example()
    ; Create GUI
    Local $hGUI = GUICreate("StatusBar Create (v" & @AutoItVersion & ")", 450, 320, 100, 100, $WS_OVERLAPPEDWINDOW)

    ; adjusts array to largest array passed in (this time the text array is the largest)
    Local $aText[3] = ["Part 0", "Part 1", "Part 2"]
    Local $aParts[2] = [100, 175]
    $g_hStatus = _GUICtrlStatusBar_Create($hGUI, $aParts, $aText)

    GUISetState(@SW_SHOW)

    Sleep(2500)
    themeData()

    ; Loop until the user exits.
    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete($hGUI)
EndFunc   ;==>Example

Func themeData()
    Local Const $tagRECT = "struct; long Left;long Top;long Right;long Bottom; endstruct"
    Local Const $SP_GRIPPERPANE = 2
    Local Const $SP_GRIPPER = 3
    Local $hTheme = _WinAPI_OpenThemeData($hGUI, "Status")
    Local $tSIZE = _WinAPI_GetThemePartSize($hTheme, $SP_GRIPPER, 0, Null, Null, 1)
    _WinAPI_CloseThemeData($hTheme)
    Local $aSBSize = WinGetClientSize($g_hStatus)
    Local $aBorders = _GUICtrlStatusBar_GetBorders($g_hStatus)
    Local $tGripRect = DllStructCreate($tagRECT)
    $tGripRect.Right = $aSBSize[0] - $aBorders[0]
    $tGripRect.Bottom = $aSBSize[1] - $aBorders[1]
    $tGripRect.Left = $tGripRect.Right - $tSIZE.X
    $tGripRect.Top = $tGripRect.Bottom - $tSIZE.Y
    Local $hDC = _WinAPI_GetDC($g_hStatus)
    Local $hBrush = _WinAPI_CreateSolidBrush(0x0000FF)
    _WinAPI_FillRect($hDC, $tGripRect, $hBrush)
    _WinAPI_DeleteObject($hBrush)
    _WinAPI_ReleaseDC($g_hStatus, $hDC)
EndFunc

 

Posted

@WildByDesign btw, it doesn't change anything but you have still make the same mistake.  Declaring a global var, then declaring it local.  Gladly, having a 0 hWnd seems to be equal to active hWnd.  My suggestion, always try to remove all unnecessary global variables as much as possible.  In this case it was quite easy to accomplish...

Posted
1 hour ago, Andreik said:

Well it seems you can get just the size of the grip and you need to calculate the position. Is that what you need?

This is exactly what I was attempting to achieve, although I was quite far away from figuring it out. Your example works perfectly. Thank you so much for your time and help with this. :)

Also, thanks to you there is now a working example of the _WinAPI_GetThemePartSize function in the forum that can help anybody else in the future searching for similar functionality.

44 minutes ago, Nine said:

btw, it doesn't change anything but you have still make the same mistake.  Declaring a global var, then declaring it local.  Gladly, having a 0 hWnd seems to be equal to active hWnd.  My suggestion, always try to remove all unnecessary global variables as much as possible.  In this case it was quite easy to accomplish...

You're right. I copy and pasted the example from the help files and carelessly added Global while forgetting to remove the Local declaration. That seems to be a common mistake that I keep making. Not so much in my own scripts, but when I try to quickly put together a smaller script for the purpose of asking for help in the forum, I try to get it done too quickly and don't look over it well enough.

Limiting unnecessary Global variables is a great idea. I will definitely keep that in mind. I would like to practice making more use of passing variables between functions also to limit Global as much as possible.

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