Page 1 of 1

Subclassed Flat Coloured pushButtons - focus rectangle

Posted: 01 Jun 2017, 13:56
by zcooler
I dont think just me has published this ImageButton based function. Not having much hope getting any explanation or pointers solving the problem with the focus rectangle can appear "twice" (on multiple buttons simultaneously) Im reluctant to produce a working script displaying the issue. That would simply take too much time. So I will just ask the question has anyone experienced this before (with ImageButtons that is)?

Code: Select all

; ==================================================================================================================================
; Namespace:         SFCB
; Function:          Subclassed Flat Coloured pushButtons.
; Tested on:         Win 10 Pro x64
; Tested with:       AHK 1.1.22.04
; Change history:    1.0.00.00/2015-09-01/just me        -  initial release.
; License:           The Unlicence - http://unlicense.org/
; ==================================================================================================================================
; How to use:     To register a GUI pushbutton call SFCB() passing 3 up to 5 parameters:
;                    Hwnd        -  The handle (HWND) to the button.
;                    Message     -  The string "**Attach".
;                    wParam      -  An array containing one or two colors to be used as background colors (see Remarks).
;                    Optional --------------------------------
;                    lParam      -  An array containing one or two colors to be used as text colors (see Remarks).
;                                   If omitted, the system's default button text color is used (usually black).
;                                   The first value is also used to draw a 'focus rectangle' whenever the button is
;                                   defined as the default button or has the keyboard focus and the mouse cursor is not
;                                   over the button.
;                    IdSubclass  -  An array containing one or two colors to be used as border colors (see Remarks).
;                                   If omitted, no border are drawn.
;                    RefData     -  Pass 0/False to suppress the focus rectangle.
;                                   Default: 1 - draw it
;                 To unregister a button call SFCB() passing 2 parameters:
;                    Hwnd        -  The handle (HWND) to the button.
;                    Message     -  The string "**Detach".
; Remarks:        For all color arrays mentioned above the first value determines the color used for all button states.
;                 If specified, the second value will be used for the BST_HOT state (i.e. the mouse is hovering over the button).
;                 All color values can be passed  either as a HTML color name (e.g. "Navy") or a RGB integer value (e.g. 0xRRGGBB).
;                 All attached buttons will be subclassed.
; ==================================================================================================================================
SFCB(Hwnd, Message, wParam := 0, lParam := 0, IdSubclass := 0, RefData := 1) {
   ; Attached controls
   Static Attached := {}
   ; Subclass callback
   Static SubClassProc := RegisterCallback("SFCB", , 6)
   ; HTML colors
   Static HTML := {BLACK:  0x000000, GRAY:    0x808080, SILVER: 0xC0C0C0, WHITE: 0xFFFFFF, MAROON: 0x800000
                 , PURPLE: 0x800080, FUCHSIA: 0xFF00FF, RED:    0xFF0000, GREEN: 0x008000, OLIVE:  0x808000
                 , YELLOW: 0xFFFF00, LIME:    0x00FF00, NAVY:   0x000080, TEAL:  0x008080, AQUA:   0x00FFFF
                 , BLUE:   0x0000FF}
   ; System colors
   Static GrayText := DllCall("GetSysColor", "Int", 17, "UInt")
        , BtnText  := DllCall("GetSysColor", "Int", 18, "UInt")
   ; Stock objects
   Static DCB := DllCall("GetStockObject", "Int", 18, "UPtr") ; DC_BRUSH
        , DCP := DllCall("GetStockObject", "Int", 19, "UPtr") ; DC_PEN
        , LGB := DllCall("GetStockObject", "Int",  1, "UPtr") ; LTGRAY_BRUSH
   ; Messages
   Static WM_GETFONT := 0x31, BM_GETSTATE := 0xF2
   ; States
   Static BST_FOCUS := 0x08, BST_HOT := 0x0200, BST_PUSHED := 0x04
   ; Styles
   Static BS_DEFPUSHBUTTON := 0x01, BS_LEFT := 0x0100, BS_RIGHT := 0x0200, BS_CENTER := 0x0300, BS_TOP := 0x0400
        , BS_BOTTOM := 0x0800, BS_VCENTER := 0x0C00, BS_MULTILINE := 0x2000, WS_DISABLED := 0x08000000
   ; DrawText format flags
   Static DT_LEFT := 0x00, DT_TOP := 0x00, DT_CENTER := 0x01, DT_RIGHT := 0x02, DT_VCENTER := 0x04
        , DT_BOTTOM := 0x08, DT_WORDBREAK := 0x10, DT_SINGLELINE := 0x20, DT_NOCLIP := 0x0100, DT_CALCRECT := 0x0400
        , DT_END_ELLIPSIS := 0x8000, DT_PATH_ELLIPSIS := 0x4000
   ; Critical ; try to uncomment in case of issues
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; User calls
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Attach the button
   If (Message = "**Attach") {
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Check the control parameter
      If !DllCall("IsWindow", "Ptr", Hwnd, "UPtr")
         Return False
      BtnAttr := {}
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Check and convert the color parameters
      If !IsObject(BkgArray := wParam)
         Return False
      BkgColors := []
      Loop, 2 {
         BkgColor := BkgArray[A_Index]
         If (BkgColor = "") {
            If (A_Index = 1)
               Return False
            Else
               BkgColor := BkgColors[1]
         }
         Else {
            BkgColor := (HTML.HasKey(BkgColor) ? HTML[BkgColor] : BkgColor) & 0xFFFFFF
            BkgColor := ((BkgColor & 0xFF0000) >> 16) | (BkgColor & 0x00FF00) | ((BkgColor & 0x0000FF) << 16)
         }
         If (BkgColor = "")
            Return False
         BkgColors[A_Index] := BkgColor
      }
      BtnAttr.BkgColors := BkgColors
      If !IsObject(TxtArray := lParam)
         BtnAttr.TxtColors := [BtnText, BtnText]
      Else {
         TxtColors := []
         Loop, 2 {
            TxtColor := TxtArray[A_Index]
            If (TxtColor = "") {
               If (A_Index = 1)
                  Return False
               Else
                  TxtColor := TxtColors[1]
            }
            Else {
               TxtColor := (HTML.HasKey(TxtColor) ? HTML[TxtColor] : TxtColor) & 0xFFFFFF
               TxtColor := ((TxtColor & 0xFF0000) >> 16) | (TxtColor & 0x00FF00) | ((TxtColor & 0x0000FF) << 16)
            }
            If (TxtColor = "")
               Return False
            TxtColors[A_Index] := TxtColor
         }
         BtnAttr.TxtColors := TxtColors
      }
      If !IsObject(BdrArray := IdSubclass)
         BtnAttr.BdrColors := [BkgColors[1], BkgColors[2]]
      Else {
         BdrColors := []
         Loop, 2 {
            BdrColor := BdrArray[A_Index]
            If (BdrColor = "") {
               If (A_Index = 1)
                  Return False
               Else
                  BdrColor := BdrColors[1]
            }
            Else {
               BdrColor := (HTML.HasKey(BdrColor) ? HTML[BdrColor] : BdrColor) & 0xFFFFFF
               BdrColor := ((BdrColor & 0xFF0000) >> 16) | (BdrColor & 0x00FF00) | ((BdrColor & 0x0000FF) << 16)
            }
            If (BdrColor = "")
               Return False
            BdrColors[A_Index] := BdrColor
         }
         BtnAttr.BdrColors := BdrColors
      }
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Get the button's properties.
      HFONT := DllCall("SendMessage", "Ptr", Hwnd, "Int", WM_GETFONT, "Ptr", 0, "Ptr", 0)
      BtnAttr.Font := HFONT
      ControlGetText, BtnCaption, , ahk_id %Hwnd%
      BtnAttr.Caption := BtnCaption
      ControlGet, BtnStyles, Style, , , ahk_id %Hwnd%
      VarSetCapacity(Rect, 16, 0)
      DllCall("GetClientRect", "Ptr", Hwnd, "Ptr", &Rect)
      BtnAttr.CtlW := NumGet(Rect, 8, "Int")
      BtnAttr.CtlH := NumGet(Rect, 12, "Int")
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Set the focus rectangle
      DllCall("InflateRect", "Ptr", &Rect, "Int", -1, "Int", -1)
      BtnAttr.DrawFocus := !!RefData
      If (RefData) {
         BtnAttr.SetCapacity("FORC", 16)
         Addr := BtnAttr.GetAddress("FORC")
         NumPut(NumGet(Rect, 0, "Int64"), Addr + 0, "Int64")
         NumPut(NumGet(Rect, 8, "Int64"), Addr + 8, "Int64")
      }
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Calculate the text rectangle
      DllCall("InflateRect", "Ptr", &Rect, "Int", -2, "Int", -2)
      VarSetCapacity(TXRC, 16, 0)
      NumPut(NumGet(Rect, 0, "Int64"), TXRC, 0, "Int64")
      NumPut(NumGet(Rect, 8, "Int64"), TXRC, 8, "Int64")
      TxtT := NumGet(Rect, 4, "Int")
      TxtB := NumGet(Rect, 12, "Int")
      HDC := DllCall("GetDC", "Ptr", Hwnd, "UPtr")
      DllCall("SelectObject", "Ptr", HDC, "Ptr", HFONT)
      DT_ALIGN := (BtnStyles & BS_CENTER) = BS_CENTER ? DT_CENTER
                : (BtnStyles & BS_CENTER) = BS_RIGHT ? DT_RIGHT
                : (BtnStyles & BS_CENTER) = BS_LEFT ? DT_LEFT
                : DT_CENTER
      DT_ALIGN |= (BtnStyles & BS_MULTILINE) ? DT_WORDBREAK : DT_END_ELLIPSIS
      VC := BtnStyles & BS_VCENTER
      If (VC = BS_VCENTER) Or (VC = BS_BOTTOM) OR (VC = 0) {
         DllCall("DrawText", "Ptr", HDC, "Str", BtnCaption, "Int", -1, "Ptr", &TXRC, "UInt", DT_ALIGN | DT_CALCRECT)
         D := TxtB - NumGet(TxRc, 12, "Int")
         TxtT += (VC = BS_BOTTOM) ? D : D // 2
         NumPut(TxtT, Rect, 4, "Int")
         NumPut(TxtB, Rect, 12, "Int")
      }
      BtnAttr.Align := DT_ALIGN
      BtnAttr.SetCapacity("TXRC", 16)
      Addr := BtnAttr.GetAddress("TXRC")
      NumPut(NumGet(Rect, 0, "Int64"), Addr + 0, "Int64")
      NumPut(NumGet(Rect, 8, "Int64"), Addr + 8, "Int64")
      DllCall("ReleaseDC", "Ptr", Hwnd, "Ptr", HDC)
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Store the button's properties and subclass the button
      Attached[Hwnd] := BtnAttr
      DllCall("SetWindowSubclass", "Ptr", Hwnd, "Ptr", SubclassProc, "Ptr", Hwnd, "Ptr", 0)
      WinSet, Redraw, , ahk_id %Hwnd%
      Return True
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Detach the button
   If (Message = "**Detach") {
      If Attached.HasKey(Hwnd) {
         Attached.Delete(Hwnd)
         DllCall("RemoveWindowSubclass", "Ptr", Hwnd, "Ptr", SubclassProc, "Ptr", Hwnd)
         WinSet, Redraw, , ahk_id %Hwnd%
         Return True
      }
      Return False
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; System calls
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; WM_PAINT -> paint the button
   If (Message = 0x0F) {
      If (Btn := Attached[Hwnd]) {
         ControlGet, BS, Style, , , ahk_id %Hwnd%
         VarSetCapacity(PAINTSTRUCT, A_PtrSize + A_PtrSize + 56, 0)
         , HDC := DllCall("BeginPaint", "Ptr", Hwnd, "Ptr", &PAINTSTRUCT, "UPtr")
         If (BS & WS_DISABLED)
            TxtColor := GrayText
            , BdrColor := GrayText
            , DllCall("SelectObject", "Ptr", HDC, "Ptr", LGB)
            , DllCall("SelectObject", "Ptr", HDC, "Ptr", DCP)
            , DllCall("SetDCPenColor", "Ptr", HDC, "UInt", BdrColor)
         Else
            BST := DllCall("SendMessage", "Ptr", Hwnd, "Int", 0xF2, "Ptr", 0, "Ptr", 0) ; BM_GETSTATE
            , ClrIndex := (BST & BST_PUSHED) ? 1 : (BST & BST_HOT) ? 2 : 1
            , BkgColor := Btn.BkgColors[ClrIndex]
            , TxtColor := Btn.TxtColors[ClrIndex]
            , BdrColor := Btn.BdrColors[ClrIndex]
            , DllCall("SelectObject", "Ptr", HDC, "Ptr", DCB)
            , DllCall("SelectObject", "Ptr", HDC, "Ptr", DCP)
            , DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", BkgColor)
            , DllCall("SetDCPenColor", "Ptr", HDC, "UInt", BdrColor)
         DllCall("Rectangle", "Ptr", HDC, "Int", 0, "Int", 0, "Int", Btn.CtlW, "Int", Btn.CtlH)
         If (Btn.DrawFocus) && (((BST & 0x020C) = BST_FOCUS) || ((BS & BS_DEFPUSHBUTTON) && !(BST & 0x0204)))
            HBRUSH := DllCall("CreateSolidBrush", "UInt", TxtColor, "UPtr")
            , DllCall("FrameRect", "Ptr", HDC, "Ptr", Btn.GetAddress("FORC"), "Ptr", HBRUSH)
            , DllCall("DeleteObject", "Ptr", HBRUSH)
         DllCall("SelectObject", "Ptr", HDC, "Ptr", Btn.Font)
         , DllCall("SetBkMode", "Ptr", HDC, "Int", 1)
         , DllCall("SetTextColor", "Ptr", HDC, "UInt", TxtColor)
         , DllCall("DrawText", "Ptr", HDC, "Str", Btn.Caption, "Int", -1, "Ptr", Btn.GetAddress("TXRC"), "UInt", Btn.Align)
         , DllCall("EndPaint", "Ptr", Hwnd, "Ptr", &PAINTSTRUCT)
         Return 0
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; WM_DESTROY -> the button is about to be destroyed
   If (Message = 0x02) {
      If Attached.HasKey(Hwnd) {
         Attached.Delete(Hwnd)
         DllCall("RemoveWindowSubclass", "Ptr", Hwnd, "Ptr", SubclassProc, "Ptr", Hwnd)
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Pass the message to the default subclass proc
   Return DllCall("DefSubclassProc", "Ptr", Hwnd, "UInt", Message, "Ptr", wParam, "Ptr", lParam)
}