Jump to content

Flat Custom Buttons with NM_CUSTOMDRAW


Go to solution Solved by Nine,

Recommended Posts

Posted

This is a continuation of a script from @Nine from the Round buttons thread by @ioa747.

My intention is to share this script back in that thread but the text color part is not working and therefore I did not want to pollute that thread asking for help. I'd rather get it sorted out and working 100% before sharing it there.

Also, I've seen requests recently for flat, rectangle buttons and this may help for that user. I'm also likely going to use this because it's quite great. Any script from Nine is always great.

So the part that is not working is changing the text color of the buttons with _WinAPI_SetTextColor. Clearly it's an area that I am not familiar with. Anyway, I put a comment line with (not working) in the areas where it is not working.

If someone can help with the text color part, please, that would be fantastic. Thank you. :)

; From Nine
; Modified by WildByDesign

#include <GUIConstants.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>
#include <WinAPI.au3>
#include <FrameConstants.au3>

#include <guibutton.au3>

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

Global Const $tagNMCUSTOMDRAWINFO = $tagNMHDR & ";dword DrawStage;handle hdc;" & $tagRECT & ";dword_ptr ItemSpec;uint ItemState;lparam lItemParam;"
Global $idButton

Global $iBackColorDef = 0x404040
Global $iBackColorHot = 0x808080
Global $iBackColorSel = 0x606060
Global $iBackColorDis = 0x000000

Global $iTextColorDef = 0xFFFFFF
Global $iTextColorDis = 0xA0A0A0

Example()

Func Example()
    Local $hGUI = GUICreate("Example")
    GUISetBkColor(0x202020)
    $idButton = GUICtrlCreateButton("Test", 100, 100, 100, 30)

    GUICtrlSendMsg($idButton, $WM_CHANGEUISTATE, 65537, 0)

    GUIRegisterMsg($WM_NOTIFY, WM_NOTIFY)
    GUISetState()

    While True
        Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $idButton
            ConsoleWrite("Button was pressed" & @CRLF)
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    Local $hBrush
    Local $tInfo = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)
    If $tInfo.IDFrom = $idButton And $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_PREPAINT Then
        Local $tRECT = DllStructCreate($tagRECT, DllStructGetPtr($tInfo, "left"))
        If BitAND($tInfo.ItemState, $CDIS_HOT) Then
            ; set hot track back color
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorHot)
            ; set default text color (not working)
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDef)
        EndIf
        If BitAND($tInfo.ItemState, $CDIS_SELECTED) Then
            ; set selected back color
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorSel)
            ; set default text color (not working)
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDef)
        EndIf
        If BitAND($tInfo.ItemState, $CDIS_DISABLED) Then
            ; set disabled back color
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorDis)
            ; set disabled text color (not working)
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDis)
        EndIf
        If Not BitAND($tInfo.ItemState, $CDIS_HOT) And Not BitAND($tInfo.ItemState, $CDIS_SELECTED) Then
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorDef)
        EndIf
        _WinAPI_FillRect($tInfo.hDC, $tRECT, $hBrush)
        _WinAPI_DeleteObject($hBrush)
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

 

Posted

Sadly you cannot modify the font of a button with NM_CUSTOMDRAW.  As per MSDN, you need to inform the parent with CDRF_NEWFONT (This occurs when dwDrawStage equals CDDS_ITEMPREPAINT).  To tell that you want an item prepaint, you need to use CDRF_NOTIFYITEMDRAW (This occurs when dwDrawStage equals CDDS_PREPAINT).  However the use CDRF_NOTIFYITEMDRAW does not trigger a new drawing stage with a button, as buttons don't have item...

 

Posted

Thanks for confirming. I just tried to combine your NM_CUSTOMDRAW / WM_NOTIFY method with Matty's BS_OWNERDRAW / WM_DRAWITEM method. His works well for coloring the buttons and text, but lacks the ability to color the button on mouse hover. Yours does all of the button states quite well but lacks the text coloring, unfortunately.

So I tried my best to combine the best of both, but it doesn't seem like it can work. I assume that WM_NOTIFY just doesn't trigger for _GUICtrl*_Create UDF functions. I used _WinAPI_GetDlgCtrlID() to obtain the control ID from the button handle but I wasn't able to get it to work properly in the end.

There is so much potential with both methods but it seems that we may out of luck.

  • Solution
Posted (edited)

But you could hack the NM_CUSTOMDRAW.  Not so great but it is working :

; From Nine
#include <GUIConstants.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>
#include <WinAPI.au3>
#include <FrameConstants.au3>

Global Const $tagNMCUSTOMDRAWINFO = $tagNMHDR & ";dword DrawStage;handle hdc;" & $tagRECT & ";dword_ptr ItemSpec;uint ItemState;lparam lItemParam;"
Global $idBut

Example()

Func Example()
  Local $hGUI = GUICreate("Example")
  $idBut = GUICtrlCreateButton("Test", 10, 10, 100, 30)
  GUICtrlCreateButton("Standard", 10, 50, 100, 30)

  GUIRegisterMsg($WM_NOTIFY, WM_NOTIFY)
  GUISetState()

  While True
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
      Case $idBut
        ConsoleWrite("Button was pressed" & @CRLF)
    EndSwitch
  WEnd
EndFunc   ;==>Example

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
  Local $tInfo = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)
  If $tInfo.IDFrom = $idBut And $tInfo.Code = $NM_CUSTOMDRAW Then
    Local $tRECT = DllStructCreate($tagRECT, DllStructGetPtr($tInfo, "left"))
    Switch $tInfo.DrawStage
      Case $CDDS_PREPAINT
        _WinAPI_DrawFrameControl($tInfo.hDC, $tRECT, $DFC_BUTTON, (BitAND($tInfo.ItemState, $CDIS_SELECTED) ? $DFCS_PUSHED : 0) + $DFCS_BUTTONPUSH)
        _WinAPI_InflateRect($tRECT, -3, -3)
        Local $hBrush = _WinAPI_CreateSolidBrush(BitAND($tInfo.ItemState, $CDIS_HOT) ? 0xCDEF : 0xAAAA)
        _WinAPI_FillRect($tInfo.hDC, $tRECT, $hBrush)
        _WinAPI_DeleteObject($hBrush)
        Return $CDRF_NOTIFYPOSTPAINT
      Case $CDDS_POSTPAINT
        _WinAPI_InflateRect($tRECT, -6, -6)
        _WinAPI_SetTextColor($tInfo.hDC, 0xFFFFFF)
        _WinAPI_SetBkColor($tInfo.hDC, BitAND($tInfo.ItemState, $CDIS_HOT) ? 0xCDEF : 0xAAAA)
        _WinAPI_DrawText($tInfo.hDC, GUICtrlRead($tInfo.IDFrom), $tRECT, BitOR($DT_CENTER, $DT_VCENTER))
    EndSwitch
  EndIf
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

 

Edited by Nine
better code
Posted
3 hours ago, Nine said:

But you could hack the NM_CUSTOMDRAW.  Not so great but it is working :

Whether it is a hack or not, it seems to do a fantastic job. Thank you so much for helping.

Posted (edited)

@Nine Sorry to have to ask for more help. I've made some great progress and wondering if you can test the script (below) if you have a moment.

I had to modify the _WinAPI_InflateRect sizing a bit since it (the text) didn't line up correctly after I removed (commented out) the frame parts.

As I've started to add more example buttons, I've had to add more to the lines:

If $tInfo.IDFrom = $idBut Or $tInfo.IDFrom = $idBut2 And $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_PREPAINT Then
...
If $tInfo.IDFrom = $idBut Or $tInfo.IDFrom = $idBut2 And $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_POSTPAINT Then

If I remove the buttons from there it will obviously do all buttons which is nice:

If $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_PREPAINT Then
...
If $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_POSTPAINT Then

However, I assume that WM_NOTIFY will process more than just buttons. So I would assume that it could cause problems for users who have other controls.

Question: Is there a way, near the top of WM_NOTIFY, to tell it not to continue if it's not a button control?

 

#include <GUIConstants.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>
#include <WinAPI.au3>
#include <FrameConstants.au3>

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

Global $iBackColorDef = 0x404040
Global $iBackColorHot = 0x808080
Global $iBackColorSel = 0x606060
Global $iBackColorDis = 0x000000

Global $iTextColorDef = 0xFFFFFF
Global $iTextColorDis = 0xA0A0A0

Global Const $tagNMCUSTOMDRAWINFO = $tagNMHDR & ";dword DrawStage;handle hdc;" & $tagRECT & ";dword_ptr ItemSpec;uint ItemState;lparam lItemParam;"
Global $idBut, $idBut2

Example()

Func Example()
    Local $hGUI = GUICreate("Example")
    GUISetBkColor(0x202020)
    Local $sFont = "Segoe MDL2 Assets"
    GUISetFont(14, $FW_NORMAL, $GUI_FONTNORMAL, $sFont)
    $idBut = GUICtrlCreateButton(ChrW(0xE74D), 100, 100, -1, -1)

    Local $sFont = "Segoe UI"
    GUISetFont(14, $FW_NORMAL, $GUI_FONTNORMAL, $sFont)
    $idBut2 = GUICtrlCreateButton("Button", 100, 160, -1, -1)

    GUICtrlSendMsg($idBut, $WM_CHANGEUISTATE, 65537, 0)
    GUIRegisterMsg($WM_NOTIFY, WM_NOTIFY)
    GUISetState()

    While True
        Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $idBut
            ConsoleWrite("Icon was pressed" & @CRLF)
        Case $idBut2
            ConsoleWrite("Button was pressed" & @CRLF)
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    Local $tInfo = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)
    If $tInfo.IDFrom = $idBut Or $tInfo.IDFrom = $idBut2 And $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_PREPAINT Then
        Local $tRECT = DllStructCreate($tagRECT, DllStructGetPtr($tInfo, "left"))
        ;_WinAPI_DrawFrameControl($tInfo.hDC, $tRECT, $DFC_BUTTON, (BitAND($tInfo.ItemState, $CDIS_SELECTED) ? $DFCS_PUSHED : 0) + $DFCS_BUTTONPUSH)
        ;_WinAPI_InflateRect($tRECT, -3, -3)
        ;Local $hBrush = _WinAPI_CreateSolidBrush(BitAND($tInfo.ItemState, $CDIS_HOT) ? 0xFFFF : 0xAAAA)
        If BitAND($tInfo.ItemState, $CDIS_HOT) Then
            ; set hot track back color
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorHot)
        EndIf
        If BitAND($tInfo.ItemState, $CDIS_SELECTED) Then
            ; set selected back color
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorSel)
        EndIf
        If BitAND($tInfo.ItemState, $CDIS_DISABLED) Then
            ; set disabled back color
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorDis)
        EndIf
        If Not BitAND($tInfo.ItemState, $CDIS_HOT) And Not BitAND($tInfo.ItemState, $CDIS_SELECTED) Then
            $hBrush = _WinAPI_CreateSolidBrush($iBackColorDef)
        EndIf
        _WinAPI_FillRect($tInfo.hDC, $tRECT, $hBrush)
        _WinAPI_DeleteObject($hBrush)
        Return $CDRF_NOTIFYPOSTPAINT
    EndIf
    If $tInfo.IDFrom = $idBut Or $tInfo.IDFrom = $idBut2 And $tInfo.Code = $NM_CUSTOMDRAW And $tInfo.DrawStage = $CDDS_POSTPAINT Then
        Local $tRECT = DllStructCreate($tagRECT, DllStructGetPtr($tInfo, "left"))
        ;_WinAPI_InflateRect($tRECT, -6, -6)
        _WinAPI_InflateRect($tRECT, -4, -6)
        ;_WinAPI_SetTextColor($tInfo.hDC, 0xFFFFFF)
        ;_WinAPI_SetBkColor($tInfo.hDC, BitAND($tInfo.ItemState, $CDIS_HOT) ? 0xFFFF : 0xAAAA)
        If BitAND($tInfo.ItemState, $CDIS_HOT) Then
            ; set hot track back color
            _WinAPI_SetBkColor($tInfo.hDC, $iBackColorHot)
            ; set default text color
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDef)
        EndIf
        If BitAND($tInfo.ItemState, $CDIS_SELECTED) Then
            ; set selected back color
            _WinAPI_SetBkColor($tInfo.hDC, $iBackColorSel)
            ; set default text color
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDef)
        EndIf
        If BitAND($tInfo.ItemState, $CDIS_DISABLED) Then
            ; set disabled back color
            _WinAPI_SetBkColor($tInfo.hDC, $iBackColorDis)
            ; set disabled text color
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDis)
        EndIf
        If Not BitAND($tInfo.ItemState, $CDIS_HOT) And Not BitAND($tInfo.ItemState, $CDIS_SELECTED) Then
            ; set default back color
            _WinAPI_SetBkColor($tInfo.hDC, $iBackColorDef)
            ; set default text color
            _WinAPI_SetTextColor($tInfo.hDC, $iTextColorDef)
        EndIf
        _WinAPI_DrawText($tInfo.hDC, GUICtrlRead($tInfo.idFrom), $tRECT, BitOR($DT_CENTER, $DT_VCENTER))
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

 

Edited by WildByDesign
Posted

Here my take on it :

; From Nine
#include <GUIConstants.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>
#include <WinAPI.au3>
#include <FrameConstants.au3>

Global Const $tagNMCUSTOMDRAWINFO = $tagNMHDR & ";dword DrawStage;handle hdc;" & $tagRECT & ";dword_ptr ItemSpec;uint ItemState;lparam lItemParam;"

Example()

Func Example()
  Local $hGUI = GUICreate("Example")
  Local $idBut = GUICtrlCreateButton("Test1", 10, 10, 100, 30)
  GUICtrlCreateButton("Test2", 10, 50, 100, 30)
  GUICtrlCreateRadio("Radio", 10, 90, 100, 30)
  GUICtrlCreateCheckbox("Check", 10, 130, 100, 30)

  GUIRegisterMsg($WM_NOTIFY, WM_NOTIFY)
  GUISetState()

  While True
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
      Case $idBut
        ConsoleWrite("Button was pressed" & @CRLF)
    EndSwitch
  WEnd
EndFunc   ;==>Example

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
  Local $tInfo = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)
  If _WinAPI_GetClassName($tInfo.hWndFrom) = "Button" And IsString(GUICtrlRead($tInfo.IDFrom)) And $tInfo.Code = $NM_CUSTOMDRAW Then
    Local $tRECT = DllStructCreate($tagRECT, DllStructGetPtr($tInfo, "left"))
    Switch $tInfo.DrawStage
      Case $CDDS_PREPAINT
        _WinAPI_DrawFrameControl($tInfo.hDC, $tRECT, $DFC_BUTTON, (BitAND($tInfo.ItemState, $CDIS_SELECTED) ? $DFCS_PUSHED : 0) + $DFCS_BUTTONPUSH)
        _WinAPI_InflateRect($tRECT, -3, -3)
        Local $hBrush = _WinAPI_CreateSolidBrush(BitAND($tInfo.ItemState, $CDIS_HOT) ? 0xCDEF : 0xAAAA)
        _WinAPI_FillRect($tInfo.hDC, $tRECT, $hBrush)
        _WinAPI_DeleteObject($hBrush)
        Return $CDRF_NOTIFYPOSTPAINT
      Case $CDDS_POSTPAINT
        _WinAPI_InflateRect($tRECT, -6, -6)
        _WinAPI_SetTextColor($tInfo.hDC, 0xFFFFFF)
        _WinAPI_SetBkColor($tInfo.hDC, BitAND($tInfo.ItemState, $CDIS_HOT) ? 0xCDEF : 0xAAAA)
        _WinAPI_DrawText($tInfo.hDC, GUICtrlRead($tInfo.IDFrom), $tRECT, BitOR($DT_CENTER, $DT_VCENTER))
    EndSwitch
  EndIf
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

 

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