Subclassed Flat Coloured pushButtons - focus rectangle
Posted: 01 Jun 2017, 13:56
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)
}