Jump to content

Recommended Posts

Posted (edited)

Starting point was that I wanted to determine the background color of a label. I did some research, read many articles and sample scripts and tried it out. In the end I found some possibilities, which I summarized in a demo and divided it into 4 columns. See screenshot.

Screenshots: On the left the initial position, on the right after resetting the colors of the controls.

1523976441_DemoGUICtrlGetBKColor.png.892f70b2def749bf1f8261993414e2ea.png.999893163_Afterresetthecolorofthecontrols.png.01c4af39538c3c17ccf0a75194f23f85.png

In each column there is a test button (green) and a test label (yellow), plus a button to change the color of the test controls, and a button to reset to the original colors.

Unfortunately, there is no example of _WinAPI_GetBkColor() in the AutoIt help, so I tried to find out how it works myself. But that only worked when I implemented a _SendMessage() function.

In column 1 "WinAPI 1" is a function that tries to use _WinAPI_GetBkColor(). This is the crucial function for this thread. But it does not work for the button or the label.

In column 2 "WinAPI 2" is the same function, but with a _SendMessage() function. With this it works wonderfully, but I don't know if I'm doing it right!

In column 3 "guinness" is a function of @guinness , which works with _WinAPI_GetPixel(). It does not work reliably with controls with frames, like buttons.

In column 4 "Bilgus" is a function plus an additional function "_GetBrushColor()" from @Bilgus . It does not work reliably with controls with transparent backgrounds, like labels.

In principle, I just combined different commands from the other functions in the function for column 2 "WinAPI 2". As I said, it seems to be the only way that works reliably. The question is, what is the right way to use _WinAPI_GetBkColor() to determine the background color of controls? Maybe you could include an example in the AutoIt help!?

Prof Bernd.

 

Demo GUICtrlGetBKColor.au3

Edited by Professor_Bernd
Posted (edited)

For the Bilgus way, be careful with "symetric" colors like 0x00FF00 when doing tests. The function works if is returned

_WinAPI_SwitchColor($iColor)

For the guinness way, I suspect the border of the button to cause trouble because on my machine if I shift the pixels to check then it also works

Local $iColor = _WinAPI_GetPixel($hDC, 3, 3)

Good luck to make your choice  :)

Edited by mikell
Posted

Hi mikell.

The guinnes method has other problems with _WinAPI_GetPixel, for example with an Input control.

The Bilgus method seems to work when using your change, but the method is too big. In 2020 you should not need so much additional code to get the background color of a control.

Spoiler
; ==============================================================================
; GUICtrlGetBkColor (Bilgus)
;
; Description: Retrieves the RGB value of the control background WITHOUT GetPixel.
;              ---- Does NOT work e.g. with transparent labels. ----
;
; Author:      Bilgus, 2020-04-30.
; https://www.autoitscript.com/forum/topic/202574-get-color-of-a-solid-brush/?do=findComment&comment=1453833
; Modified by: Professor Bernd, 2020-08-22.
; ------------------------------------------------------------------------------
Func GUICtrlGetBkColor_Bilgus($hWnd)
  If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
  Local $hWnd_Main = _WinAPI_GetParent($hWnd)
  Local $hDC = _WinAPI_GetDC($hWnd)
  Local $hBrush = _SendMessage($hWnd_Main, $WM_CTLCOLORSTATIC, $hDC, $hWnd)
  Local $iColor = _GetBrushColor($hBrush)
  _WinAPI_ReleaseDC($hWnd, $hDC)
  ; _WinAPI_SwitchColor: Tip from mikell, 2020-08-23. Seems to work. :-)
  ; https://www.autoitscript.com/forum/topic/203675-_winapi_getbkcolor-how-to-use-it-the-right-way/?do=findComment&comment=1462530
  Return _WinAPI_SwitchColor($iColor)
EndFunc ; ==> GUICtrlGetBkColor (Bilgus)
;
; ==============================================================================
; _GetBrushColor (Bilgus)
;
; Description: Get the color of a SOLID brush. Does NOT work e.g. with transparent labels.
;
; Author:      Bilgus, 2020-04-30.
; https://www.autoitscript.com/forum/topic/202574-get-color-of-a-solid-brush/
; ------------------------------------------------------------------------------
Func _GetBrushColor($hBrush)
    ;https://devblogs.microsoft.com/oldnewthing/20190802-00/?p=102747
    Local $iErr
    Local Const $BS_SOLID = 0x00000000
    Local Const $tagLogBrush = "uint lbStyle; dword lbColor; ulong_ptr lbHatch"
    Local $tLogBrush = DllStructCreate($tagLogBrush)
    Local $iSzLogBrush = DllStructGetSize($tLogBrush)
    If _WinAPI_GetObject($hBrush, $iSzLogBrush, DllStructGetPtr($tLogBrush)) <> $iSzLogBrush Then
        $iErr = 0x1
    ElseIf DllStructGetData($tLogBrush, "lbStyle") <> $BS_SOLID Then
        $iErr = 0x2
    Else
        Return DllStructGetData($tLogBrush, "lbColor")
    EndIf
    Return SetError($iErr, 0, 0xFFFFFFFF) ;CLR_NONE
EndFunc ; ==> _GetBrushColor

 

So it should be done with 1 command, for example if there was a function  _WinAPI_GetCtrlBkColor(HWND/ID). :)

But this is not bad if it is done with a few extra lines to _WinAPI_GetBkColor($hDC). Hence my question if my way with SendMessage is the right way to use it, because I don't really know what it does. (_WinAPI_SetBkMode($hDC, $OPAQUE) does not work.)

Spoiler
; ==============================================================================
; GUICtrlGetBkColor (WinAPI 2)
;
; Description: Retrieves the RGB value of the control background WITHOUT GetPixel.
;
; Author:      Bilgus, 2020-04-30.
; https://www.autoitscript.com/forum/topic/202574-get-color-of-a-solid-brush/?do=findComment&comment=1453833
; Modified by: Professor Bernd, 2020-08-22.
; ------------------------------------------------------------------------------
Func GUICtrlGetBkColor_WinAPI_2($hWnd)
  If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
  
  Local $hDC = _WinAPI_GetDC($hWnd) ; <== Get the DC before you send the message!
;   _WinAPI_SetBkMode($hDC, $OPAQUE) ; <== Does not work.
  _SendMessage(_WinAPI_GetParent($hWnd), $WM_CTLCOLORSTATIC, $hDC, $hWnd) ; <== What exactly does it do?
  
  Local $iColor = _WinAPI_GetBkColor($hDC)
  _WinAPI_ReleaseDC($hWnd, $hDC)
  Return $iColor
EndFunc ; ==> GUICtrlGetBkColor (WinAPI 2)

 

Could someone post an example of the correct use of "_WinAPI_GetBkColor"? This could then be included as an example in the AutoIt help.

Thanks a lot mikell, for your info! :)

Posted

I think that the explanation lies somewhere under this statement from MSDN :

Quote

The WM_CTLCOLORSTATIC message is sent to the parent window of a static control when the control is about to be drawn. By responding to this message, the parent window can use the given device context handle to set the text and background colors of the static control.

In case of an inquiry, the parent also needs to know the given DC of the control, which makes sense.  But the window itself doesn't need its own DC to get its bk color.

#include <WinAPI.au3>
#include <WindowsConstants.au3>

Global $hGui = GUICreate("Demo for GUICtrlGetBKColor", 578, 323)
GUISetBkColor (0xFFFF00)

Global $idBtn = GUICtrlCreateButton("Test", 64, 64, 89, 33)
GUICtrlSetBkColor(-1, 0x00FFFF)

GUISetState ()

ConsoleWrite ("control = " & Hex(_GUICtrlGetBkColor($idBtn)) & @CRLF)

Local $hGuiDC = _WinAPI_GetDC($hGUI)
Local $iGetColor = _WinAPI_GetBkColor($hGuiDC)
_WinAPI_ReleaseDC($hGUI, $hGuiDC)

ConsoleWrite ("GUI = " & Hex($iGetColor) & @CRLF)

Sleep (1000)

Func _GUICtrlGetBkColor($hWnd)
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    Local $hParent = _WinAPI_GetParent($hWnd)
    Local $hDC = _WinAPI_GetDC($hWnd)
    _SendMessage($hParent, $WM_CTLCOLORSTATIC, $hDC, $hWnd)
    Local $RGB = _WinAPI_GetBkColor($hDC)
    _WinAPI_ReleaseDC($hWnd, $hDC)
    Return $RGB
EndFunc   ;==>_GUICtrlGetBkColor

 

Posted

Did you post the same code I already did? :huh2: I can't see any functional difference between your code in _GUICtrlGetBkColor($hWnd) and my code in GUICtrlGetBkColor_WinAPI_2($hWnd). Or am I missing something?

I had already read the explanation in MSDN, but didn't understand it. I do not understand it now.:(

    Local $hDC = _WinAPI_GetDC($hWnd) ; <= Here you get the DC of the control, right?
    _SendMessage($hParent, $WM_CTLCOLORSTATIC, $hDC, $hWnd) ; <== *
    Local $RGB = _WinAPI_GetBkColor($hDC) ; <== Here I hand over the DC of the control and get the BK Color.

* But what's the use of the SendMessage where I use the DC of the control? What is wrong with the DC that it does not work in _WinAPI_GetBkColor($hDC) without SendMessage?

What I mean is, _WinAPI_GetBkColor($hDC) needs the DC of the control and I give it the DC of the control. Why do I need SendMessage in addition? Sorry, but this is what confuses me. It doesn't really make sense to me and I only found it out by accident.

  • 2 weeks later...
Posted (edited)

@Professor_Bernd

_SendMessage($hParent, $WM_CTLCOLORSTATIC, $hDC, $hWnd)

This actually selects the background brush into the hdc

It starts getting deep quick but its basically this:

Windows works on the mantra of 'don't store anything that you can recalculate later'

So windows doesn't know what background the hdc has until it is time to redraw the hdc

you request that it does that calculation and hold on to the brush

Warning:

Non system brushes need to be deselected from the DC

* save the hbrush returned by sendmessage() and reselect it in to the DC (IIRC its NULL_BRUSH)

<in the case of a system brush (the SOLID ones) you don't need to deselect the brush when you are done but you still can.. >

 

Edited by Bilgus
Posted

Wow, that helps me a lot! I think I understand now how this works. :)

How do I do the deselect? :think: Can you help me?

Func GUICtrlGetBkColor_WinAPI_2($hWnd)
  If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
  Local $hDC = _WinAPI_GetDC($hWnd) ; <== Get the DC before you send the message!
  Local $hBrush = _SendMessage(_WinAPI_GetParent($hWnd), $WM_CTLCOLORSTATIC, $hDC, $hWnd) ; <= save hBrush
  Local $iColor = _WinAPI_GetBkColor($hDC)
  _WinAPI_ReleaseDC($hWnd, $hDC)
  Return $iColor
EndFunc ; ==> GUICtrlGetBkColor (WinAPI 2)

 

Posted

to deselect you select the original object back in

then you clean up that object ^^note here the static objects do not need to be deselected

Func GUICtrlGetBkColor_WinAPI_2($hWnd)
  If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
  Local $hDC = _WinAPI_GetDC($hWnd) ; <== Get the DC before you send the message!
  Local $hBrushOLD = _SendMessage(_WinAPI_GetParent($hWnd), $WM_CTLCOLORSTATIC, $hDC, $hWnd) ; <= Select background hBrush

  Local $iColor = _WinAPI_GetBkColor($hDC)
  
  $hBrushOLD = _WinAPI_SelectObject($hDC, $hBrushOLD) ;put the original object back
  ;hBrushOLD now contains the brush selected in with _sendMessage above^^
  _WinAPI_DeleteObject($hBrushOLD) ;^ Not needed with the static brushes***
  
  _WinAPI_ReleaseDC($hWnd, $hDC)
  Return $iColor
EndFunc ; ==> GUICtrlGetBkColor (WinAPI 2)

 

now as far as the function you referenced here:

 

ElseIf DllStructGetData($tLogBrush, "lbStyle") <> $BS_SOLID Then

this only handles the case of a solid brush

you would need to expand it to handle other brush types (listed here)

https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logbrush

you can choose as many or as few as you care to implement

Posted (edited)
  $hBrushOLD = _WinAPI_SelectObject($hDC, $hBrushOLD) ;put the original object back
  ;hBrushOLD now contains the brush selected in with _sendMessage above^^
  _WinAPI_DeleteObject($hBrushOLD) ;^ Not needed with the static brushes***

  I have thought about it. Can it be right or did I think wrong? (see the $hBrushNew variable.)

  ;hBrushOLD contains the brush selected in with _sendMessage above^^
  $Local $hBrushNew = _WinAPI_SelectObject($hDC, $hBrushOLD) ;put the original object back, return the current object
  _WinAPI_DeleteObject($hBrushNew) ;^ Not needed with the static brushes***

 

 

 

Edited by Professor_Bernd
Posted

doing it that way would be bad because someone else could change brush in the meantime and now you have a leak

delete the object returned by selectobj when you select its original brush back

Posted

If you can find it in print anywhere I'd recommend a read of:

 

Windows Graphics Programming Win32 GDI and DirectDraw

Feng Yuan
Publisher: Prentice Hall PTR
ISBN: 0-13-086985-6, 1234 pages

Posted (edited)

My mistake. I guess I described it badly.

For more clarity here the two variations. The first one is your unchanged code from the posting above, the second one is the same code, but with changed variable name ($hBrushNew) and comments.

Func GUICtrlGetBkColor_WinAPI_2($hWnd)
  If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
  Local $hDC = _WinAPI_GetDC($hWnd) ; <== Get the DC before you send the message!
  Local $hBrushOLD = _SendMessage(_WinAPI_GetParent($hWnd), $WM_CTLCOLORSTATIC, $hDC, $hWnd) ; <= Select background hBrush

  Local $iColor = _WinAPI_GetBkColor($hDC)
  
  $hBrushOLD = _WinAPI_SelectObject($hDC, $hBrushOLD) ;put the original object back
  ;hBrushOLD now contains the brush selected in with _sendMessage above^^
  _WinAPI_DeleteObject($hBrushOLD) ;^ Not needed with the static brushes***
  
  _WinAPI_ReleaseDC($hWnd, $hDC)
  Return $iColor
EndFunc ; ==> GUICtrlGetBkColor (WinAPI 2)
Func GUICtrlGetBkColor_WinAPI_2($hWnd)
  If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
  Local $hDC = _WinAPI_GetDC($hWnd) ; <== Get the DC before you send the message!
  Local $hBrushOLD = _SendMessage(_WinAPI_GetParent($hWnd), $WM_CTLCOLORSTATIC, $hDC, $hWnd) ; <= Select background hBrush

  Local $iColor = _WinAPI_GetBkColor($hDC)
  
  Local $hBrushNew = _WinAPI_SelectObject($hDC, $hBrushOLD) ;put the original object back, return the current object
  ;hBrushNew contains the brush selected in with _sendMessage above^^
  _WinAPI_DeleteObject($hBrushNew) ;^ Not needed with the static brushes***
  
  _WinAPI_ReleaseDC($hWnd, $hDC)
  Return $iColor
EndFunc ; ==> GUICtrlGetBkColor (WinAPI 2)

Actually I wanted to know what _WinAPI_SelectObject returns at this point, the old brush or the new one. But I did not read attentively, because in the comment you already gave the answer. To clarify this, I changed the variable names. Did I understand that correctly?

 

 

Edited by Professor_Bernd
  • 3 years later...
Posted

Both of your codes seem to rely on the user previously using GUISetBkColorGUICtrlSetBkColor. But if the user did that, then the user could have stored the colors anyway. I wonder how to make your functions also work on default colors.

 

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
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...