[CLASS] CtlColors - color your controls (2017-10-30)

Post your working scripts, libraries and tools
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

[CLASS] CtlColors - color your controls (2017-10-30)

15 Feb 2014, 01:39

Primarily released on www.autohotkey.com
Recent Changes wrote:Recent Changes:
  • 1.00.04.00 - 2017-10-30:
    Added transparent background (BkColor = "Trans")
  • 1.00.03.00 - 2015-07-06:
    Fixed Change() to run properly for ComboBoxes.
    Added Class_CtlColors.ahk
    Added CtlColors_sample.ahk
  • 1.00.02.00 - 2014-06-07:
    Fixed __New() to run properly when compiled.
  • 1.00.01.00 - 2014-02-16:
    Changed class initialization.
This is a slightly modified new version.
CtlColors.png
HowTo wrote: How to use:
  • To register a control for coloring call CtlColors.Attach() passing up to three parameters:

    Code: Select all

    HWND    - HWND of the GUI control
    BkColor - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
    ------- Optional
    TxColor - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
    If both BkColor and TxColor are "" the control will not be added and the call returns False.
  • To change the colors for a registered control call CtlColors.Change() passing up to three parameters:

    Code: Select all

    HWND    - see above
    BkColor - see above
    ------- Optional
    TxColor - see above
    Both BkColor and TxColor may be "" to reset them to default colors.
    If the control is not registered yet, CtlColors.Attach() is called internally.
  • To unregister a control from coloring call CtlColors.Detach() passing one parameter:

    Code: Select all

    HWND    - see above
  • To stop all coloring and free the resources call CtlColors.Free().
    It's a good idea to insert this call into the scripts exit-routine.
  • To check if a control is already registered call CtlColors.IsAttached() passing one parameter:

    Code: Select all

    HWND    - see above
  • To get a control's HWND use either the option [c]HwndOutputVar[/c]] with [c]Gui, Add[/c] or the command [c]GuiControlGet[/c] with sub-command [c]Hwnd[/c].
Special features:
  • On the first call for a specific control class the function registers the CtlColors_OnMessage() function as message handler for WM_CTLCOLOR... messages of this class(es).

    Buttons (Checkboxes and Radios) do not use the TxColor to draw the text, instead of that they use it to draw the focus rectangle.

    After displaying the GUI per Gui, Show you have to execute WinSet, Redraw once. It's no bad idea to do it using a GuiSize label, because it avoids rare problems when restoring a minimized window:

    Code: Select all

    GuiSize:
       If (A_EventInfo != 1) {
          Gui, %A_Gui%:+LastFound
          WinSet, ReDraw
       }
    Return
    

Code: Select all

; ======================================================================================================================
; AHK 1.1+
; ======================================================================================================================
; Function:          Auxiliary object to color controls on WM_CTLCOLOR... notifications.
;                    Supported controls are: Checkbox, ComboBox, DropDownList, Edit, ListBox, Radio, Text.
;                    Checkboxes and Radios accept only background colors due to design.
; Namespace:         CtlColors
; Tested with:       1.1.25.02
; Tested on:         Win 10 (x64)
; Change log:        1.0.04.00/2017-10-30/just me  -  added transparent background (BkColor = "Trans").
;                    1.0.03.00/2015-07-06/just me  -  fixed Change() to run properly for ComboBoxes.
;                    1.0.02.00/2014-06-07/just me  -  fixed __New() to run properly with compiled scripts.
;                    1.0.01.00/2014-02-15/just me  -  changed class initialization.
;                    1.0.00.00/2014-02-14/just me  -  initial release.
; ======================================================================================================================
; This software is provided 'as-is', without any express or implied warranty.
; In no event will the authors be held liable for any damages arising from the use of this software.
; ======================================================================================================================
Class CtlColors {
   ; ===================================================================================================================
   ; Class variables
   ; ===================================================================================================================
   ; Registered Controls
   Static Attached := {}
   ; OnMessage Handlers
   Static HandledMessages := {Edit: 0, ListBox: 0, Static: 0}
   ; Message Handler Function
   Static MessageHandler := "CtlColors_OnMessage"
   ; Windows Messages
   Static WM_CTLCOLOR := {Edit: 0x0133, ListBox: 0x134, Static: 0x0138}
   ; HTML Colors (BGR)
   Static HTML := {AQUA: 0xFFFF00, BLACK: 0x000000, BLUE: 0xFF0000, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
                 , LIME: 0x00FF00, MAROON: 0x000080, NAVY: 0x800000, OLIVE: 0x008080, PURPLE: 0x800080, RED: 0x0000FF
                 , SILVER: 0xC0C0C0, TEAL: 0x808000, WHITE: 0xFFFFFF, YELLOW: 0x00FFFF}
   ; Transparent Brush
   Static NullBrush := DllCall("GetStockObject", "Int", 5, "UPtr")
   ; System Colors
   Static SYSCOLORS := {Edit: "", ListBox: "", Static: ""}
   ; Error message in case of errors
   Static ErrorMsg := ""
   ; Class initialization
   Static InitClass := CtlColors.ClassInit()
   ; ===================================================================================================================
   ; Constructor / Destructor
   ; ===================================================================================================================
   __New() { ; You must not instantiate this class!
      If (This.InitClass == "!DONE!") { ; external call after class initialization
         This["!Access_Denied!"] := True
         Return False
      }
   }
   ; ----------------------------------------------------------------------------------------------------------------
   __Delete() {
      If This["!Access_Denied!"]
         Return
      This.Free() ; free GDI resources
   }
   ; ===================================================================================================================
   ; ClassInit       Internal creation of a new instance to ensure that __Delete() will be called.
   ; ===================================================================================================================
   ClassInit() {
      CtlColors := New CtlColors
      Return "!DONE!"
   }
   ; ===================================================================================================================
   ; CheckBkColor    Internal check for parameter BkColor.
   ; ===================================================================================================================
   CheckBkColor(ByRef BkColor, Class) {
      This.ErrorMsg := ""
      If (BkColor != "") && !This.HTML.HasKey(BkColor) && !RegExMatch(BkColor, "^[[:xdigit:]]{6}$") {
         This.ErrorMsg := "Invalid parameter BkColor: " . BkColor
         Return False
      }
      BkColor := BkColor = "" ? This.SYSCOLORS[Class]
              :  This.HTML.HasKey(BkColor) ? This.HTML[BkColor]
              :  "0x" . SubStr(BkColor, 5, 2) . SubStr(BkColor, 3, 2) . SubStr(BkColor, 1, 2)
      Return True
   }
   ; ===================================================================================================================
   ; CheckTxColor    Internal check for parameter TxColor.
   ; ===================================================================================================================
   CheckTxColor(ByRef TxColor) {
      This.ErrorMsg := ""
      If (TxColor != "") && !This.HTML.HasKey(TxColor) && !RegExMatch(TxColor, "i)^[[:xdigit:]]{6}$") {
         This.ErrorMsg := "Invalid parameter TextColor: " . TxColor
         Return False
      }
      TxColor := TxColor = "" ? ""
              :  This.HTML.HasKey(TxColor) ? This.HTML[TxColor]
              :  "0x" . SubStr(TxColor, 5, 2) . SubStr(TxColor, 3, 2) . SubStr(TxColor, 1, 2)
      Return True
   }
   ; ===================================================================================================================
   ; Attach          Registers a control for coloring.
   ; Parameters:     HWND        - HWND of the GUI control                                   
   ;                 BkColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ;                 ----------- Optional 
   ;                 TxColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ; Return values:  On success  - True
   ;                 On failure  - False, CtlColors.ErrorMsg contains additional informations
   ; ===================================================================================================================
   Attach(HWND, BkColor, TxColor := "") {
      ; Names of supported classes
      Static ClassNames := {Button: "", ComboBox: "", Edit: "", ListBox: "", Static: ""}
      ; Button styles
      Static BS_CHECKBOX := 0x2, BS_RADIOBUTTON := 0x8
      ; Editstyles
      Static ES_READONLY := 0x800
      ; Default class background colors
      Static COLOR_3DFACE := 15, COLOR_WINDOW := 5
      ; Initialize default background colors on first call -------------------------------------------------------------
      If (This.SYSCOLORS.Edit = "") {
         This.SYSCOLORS.Static := DllCall("User32.dll\GetSysColor", "Int", COLOR_3DFACE, "UInt")
         This.SYSCOLORS.Edit := DllCall("User32.dll\GetSysColor", "Int", COLOR_WINDOW, "UInt")
         This.SYSCOLORS.ListBox := This.SYSCOLORS.Edit
      }
      This.ErrorMsg := ""
      ; Check colors ---------------------------------------------------------------------------------------------------
      If (BkColor = "") && (TxColor = "") {
         This.ErrorMsg := "Both parameters BkColor and TxColor are empty!"
         Return False
      }
      ; Check HWND -----------------------------------------------------------------------------------------------------
      If !(CtrlHwnd := HWND + 0) || !DllCall("User32.dll\IsWindow", "UPtr", HWND, "UInt") {
         This.ErrorMsg := "Invalid parameter HWND: " . HWND
         Return False
      }
      If This.Attached.HasKey(HWND) {
         This.ErrorMsg := "Control " . HWND . " is already registered!"
         Return False
      }
      Hwnds := [CtrlHwnd]
      ; Check control's class ------------------------------------------------------------------------------------------
      Classes := ""
      WinGetClass, CtrlClass, ahk_id %CtrlHwnd%
      This.ErrorMsg := "Unsupported control class: " . CtrlClass
      If !ClassNames.HasKey(CtrlClass)
         Return False
      ControlGet, CtrlStyle, Style, , , ahk_id %CtrlHwnd%
      If (CtrlClass = "Edit")
         Classes := ["Edit", "Static"]
      Else If (CtrlClass = "Button") {
         IF (CtrlStyle & BS_RADIOBUTTON) || (CtrlStyle & BS_CHECKBOX)
            Classes := ["Static"]
         Else
            Return False
      }
      Else If (CtrlClass = "ComboBox") {
         VarSetCapacity(CBBI, 40 + (A_PtrSize * 3), 0)
         NumPut(40 + (A_PtrSize * 3), CBBI, 0, "UInt")
         DllCall("User32.dll\GetComboBoxInfo", "Ptr", CtrlHwnd, "Ptr", &CBBI)
         Hwnds.Insert(NumGet(CBBI, 40 + (A_PtrSize * 2, "UPtr")) + 0)
         Hwnds.Insert(Numget(CBBI, 40 + A_PtrSize, "UPtr") + 0)
         Classes := ["Edit", "Static", "ListBox"]
      }
      If !IsObject(Classes)
         Classes := [CtrlClass]
      ; Check background color -----------------------------------------------------------------------------------------
      If (BkColor <> "Trans")
         If !This.CheckBkColor(BkColor, Classes[1])
            Return False
      ; Check text color -----------------------------------------------------------------------------------------------
      If !This.CheckTxColor(TxColor)
         Return False
      ; Activate message handling on the first call for a class --------------------------------------------------------
      For I, V In Classes {
         If (This.HandledMessages[V] = 0)
            OnMessage(This.WM_CTLCOLOR[V], This.MessageHandler)
         This.HandledMessages[V] += 1
      }
      ; Store values for HWND ------------------------------------------------------------------------------------------
      If (BkColor = "Trans")
         Brush := This.NullBrush
      Else
         Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BkColor, "UPtr")
      For I, V In Hwnds
         This.Attached[V] := {Brush: Brush, TxColor: TxColor, BkColor: BkColor, Classes: Classes, Hwnds: Hwnds}
      ; Redraw control -------------------------------------------------------------------------------------------------
      DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
      This.ErrorMsg := ""
      Return True
   }
   ; ===================================================================================================================
   ; Change          Change control colors.
   ; Parameters:     HWND        - HWND of the GUI control
   ;                 BkColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ;                 ----------- Optional 
   ;                 TxColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ; Return values:  On success  - True
   ;                 On failure  - False, CtlColors.ErrorMsg contains additional informations
   ; Remarks:        If the control isn't registered yet, Add() is called instead internally.
   ; ===================================================================================================================
   Change(HWND, BkColor, TxColor := "") {
      ; Check HWND -----------------------------------------------------------------------------------------------------
      This.ErrorMsg := ""
      HWND += 0
      If !This.Attached.HasKey(HWND)
         Return This.Attach(HWND, BkColor, TxColor)
      CTL := This.Attached[HWND]
      ; Check BkColor --------------------------------------------------------------------------------------------------
      If (BkColor <> "Trans")
         If !This.CheckBkColor(BkColor, CTL.Classes[1])
            Return False
      ; Check TxColor ------------------------------------------------------------------------------------------------
      If !This.CheckTxColor(TxColor)
         Return False
      ; Store Colors ---------------------------------------------------------------------------------------------------
      If (BkColor <> CTL.BkColor) {
         If (CTL.Brush) {
            If (Ctl.Brush <> This.NullBrush)
               DllCall("Gdi32.dll\DeleteObject", "Prt", CTL.Brush)
            This.Attached[HWND].Brush := 0
         }
         If (BkColor = "Trans")
            Brush := This.NullBrush
         Else
            Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BkColor, "UPtr")
         For I, V In CTL.Hwnds {
            This.Attached[V].Brush := Brush
            This.Attached[V].BkColor := BkColor
         }
      }
      For I, V In Ctl.Hwnds
         This.Attached[V].TxColor := TxColor
      This.ErrorMsg := ""
      DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
      Return True
   }
   ; ===================================================================================================================
   ; Detach          Stop control coloring.
   ; Parameters:     HWND        - HWND of the GUI control
   ; Return values:  On success  - True
   ;                 On failure  - False, CtlColors.ErrorMsg contains additional informations
   ; ===================================================================================================================
   Detach(HWND) {
      This.ErrorMsg := ""
      HWND += 0
      If This.Attached.HasKey(HWND) {
         CTL := This.Attached[HWND].Clone()
         If (CTL.Brush) && (CTL.Brush <> This.NullBrush)
            DllCall("Gdi32.dll\DeleteObject", "Prt", CTL.Brush)
         For I, V In CTL.Classes {
            If This.HandledMessages[V] > 0 {
               This.HandledMessages[V] -= 1
               If This.HandledMessages[V] = 0
                  OnMessage(This.WM_CTLCOLOR[V], "")
         }  }
         For I, V In CTL.Hwnds
            This.Attached.Remove(V, "")
         DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
         CTL := ""
         Return True
      }
      This.ErrorMsg := "Control " . HWND . " is not registered!"
      Return False
   }
   ; ===================================================================================================================
   ; Free            Stop coloring for all controls and free resources.
   ; Return values:  Always True.
   ; ===================================================================================================================
   Free() {
      For K, V In This.Attached
         If (V.Brush) && (V.Brush <> This.NullBrush)
            DllCall("Gdi32.dll\DeleteObject", "Ptr", V.Brush)
      For K, V In This.HandledMessages
         If (V > 0) {
            OnMessage(This.WM_CTLCOLOR[K], "")
            This.HandledMessages[K] := 0
         }
      This.Attached := {}
      Return True
   }
   ; ===================================================================================================================
   ; IsAttached      Check if the control is registered for coloring.
   ; Parameters:     HWND        - HWND of the GUI control
   ; Return values:  On success  - True
   ;                 On failure  - False
   ; ===================================================================================================================
   IsAttached(HWND) {
      Return This.Attached.HasKey(HWND)
   }
}
; ======================================================================================================================
; CtlColors_OnMessage
; This function handles CTLCOLOR messages. There's no reason to call it manually!
; ======================================================================================================================
CtlColors_OnMessage(HDC, HWND) {
   Critical
   If CtlColors.IsAttached(HWND) {
      CTL := CtlColors.Attached[HWND]
      If (CTL.TxColor != "")
         DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", CTL.TxColor)
      If (CTL.BkColor = "Trans")
         DllCall("Gdi32.dll\SetBkMode", "Ptr", HDC, "UInt", 1) ; TRANSPARENT = 1
      Else
         DllCall("Gdi32.dll\SetBkColor", "Ptr", HDC, "UInt", CTL.BkColor)
      Return CTL.Brush
   }
}

Code: Select all

#NoEnv
#Include Class_CtlColors.ahk
OnExit, GuiClose
; ----------------------------------------------------------------------------------------------------------------------
SysGet, SGW, 71 ; SM_CXMENUCHECK
LB_SETCURSEL := 0x186
CB_SETCURSEL := 0x14E
Red   := "FF0000"
Green := "00C000"
Blue  := "0000FF"
Pink  := "FF20FF"
; ----------------------------------------------------------------------------------------------------------------------
Gui, Margin, 10, 10
Gui, Add, Radio, vSTDRB1 gSTDRBG hwndRBID1 Checked, Standard Radio 1
CtlColors.Attach(RBID1, "Lime", "")
Gui, Add, Radio, x+55 ym vSTDRB2 gSTDRBG hwndRBID2, Standard Radio 2
Gui, Add, CheckBox, xm vSTDCB1 gSTDCB1 hwndCBID1, Standard CheckBox
CtlColors.Attach(CBID1, "C0C0C0", "Red")
; ----------------------------------------------------------------------------------------------------------------------
Gui, Add, Text, xm w292 h2 +0x1000
; "Faked" RadioButtons -------------------------------------------------------------------------------------------------
; Note: Minimum width and height are font, font size and OS dependend, if you get below the limit, nothing is shown!!!
Gui, Add, Radio, xm w%SGW% h20 gRBG vRB1 Section Group Checked
Gui, Add, Radio, xm wp hp gRBG vRB2
Gui, Add, Radio, xm wp hp gRBG vRB3
Gui, Add, Text, ys x+5 w50 hp 0x200 cBlue gRBG vRT1 hwndRTID1, % "Radio 1"
Gui, Add, Text, xp y+10 wp hp 0x200 cBlue gRBG vRT2 hwndRTID2, % "Radio 2"
Gui, Add, Text, xp y+10 wp hp 0x200 cBlue gRBG vRT3 hwndRTID3, % "Radio 3"
RBGA := 1
CtlColors.Attach(RTID%RBGA%, "Yellow", "Blue")
; "Faked" CheckBox -----------------------------------------------------------------------------------------------------
; Note: for minimum width see "Faked" RadioButtons
Gui, Add, CheckBox, ys x+80 w%SGW% h20 gCB1 vCB1 Section
Gui, Add, Text, x+5 yp hp 0x200 gCB1 vCT1 hwndCTID1, % " Check me! "
CtlColors.Attach(CTID1, "", "Green")
; ComboBox -------------------------------------------------------------------------------------------------------------
Gui, Add, Combobox, xs y+40 w141 gCBB1 vCBB1 hwndCBBID1
   , ComboBox 1||ComboBox 2|ComboBox 3
CtlColors.Attach(CBBID1, "Aqua", "Red")
; ----------------------------------------------------------------------------------------------------------------------
Gui, Add, Text, xm w292 h2 +0x1000
; ListBox --------------------------------------------------------------------------------------------------------------
Gui, Add, ListBox, xm w292 r4 gLB1 vLB1 hwndLBID1
   , ListBox Red|ListBox Green|ListBox Blue|ListBox Pink
CtlColors.Attach(LBID1, Red, "White")
GuiControl, Choose, LB1, |1
; ----------------------------------------------------------------------------------------------------------------------
Gui, Add, Text, xm w292 h2 +0x1000
; Edit -----------------------------------------------------------------------------------------------------------------
Gui, Font, s10
Gui, Add, Edit, xm w292 r10 vED1 hwndEDID1, I'm an Edit, edit me!
CtlColors.Attach(EDID1, "606060", "Aqua")
Gui, Add, Edit, xm w292 vED2 hwndEDID2 +Disabled, % " I'm disabled!"
CtlColors.Attach(EDID2, "Gray", "Lime")
; ----------------------------------------------------------------------------------------------------------------------
Gui, Show, , Colored Controls
Return
; ----------------------------------------------------------------------------------------------------------------------
GuiClose:
GuiEscape:
   Gui, Destroy
   CtlColors.Free()
ExitApp
; ----------------------------------------------------------------------------------------------------------------------
GuiSize:
   If (A_EventInfo != 1) {
      Gui, %A_Gui%:+LastFound
      WinSet, ReDraw
   }
Return
; ----------------------------------------------------------------------------------------------------------------------
STDRBG:
   GuiControlGet, STDRB1
   CtlColors.Change(RBID1, (STDRB1 ? "Lime" : ""), "006000")
   CtlColors.Change(RBID2, (STDRB1 ? "" : "Lime"), "006000")
Return
; ----------------------------------------------------------------------------------------------------------------------
STDCB1:
   GuiControlGet, STDCB1
   CtlColors.Change(CBID1, (STDCB1 ? "Lime" : "C0C0C0"), "Red")
   Return
; ----------------------------------------------------------------------------------------------------------------------
RBG:
   RBG := SubStr(A_GuiControl, 3)
   If (RBG != RBGA) {
      CtlColors.Detach(RTID%RBGA%)
      CtlColors.Attach(RTID%RBG%, "Yellow", "Blue")
      GuiControl, , RB%RBG%, 1
      RBGA := RBG
   }
Return
; ----------------------------------------------------------------------------------------------------------------------
LB1:
   GuiControlGet, LB1
   StringSplit, LC, LB1, %A_Space%
   If (%LC2%) {
      BG := %LC2%, TX := "White"
      CtlColors.Change(LBID1, BG, TX)
      SendMessage, LB_SETCURSEL, -1, 0, , ahk_id %LBID1%
   }
Return
; ----------------------------------------------------------------------------------------------------------------------
CB1:
   GuiControlGet, CB1
   If (A_GuiControl = "CT1")
      CB1 ^= True
   If (CB1)
      CtlColors.Change(CTID1, "Lime", "406060")
   Else
      CtlColors.Change(CTID1, "", "Green")
   GuiControl, , CB1, %CB1%
Return
; ----------------------------------------------------------------------------------------------------------------------
CBB1:
Return
:arrow: Sources on GitHub.
:arrow: Download from GitHub.
Last edited by just me on 31 Oct 2017, 10:39, edited 5 times in total.
User avatar
tidbit
Posts: 1131
Joined: 29 Sep 2013, 17:15
Location: USA

Re: [CLASS] CtlColors - color your controls

20 Feb 2014, 15:12

very nice :D But it seems Buttons are still uncolorable.
Buttons (Checkboxes and Radios) do not use the TxColor to draw the text, instead of that they use it to draw the focus rectangle.
rawr. fear me.
*poke*
Is it December 21, 2012 yet?
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

21 Feb 2014, 01:37

If you want colored captions, you might try: Old forum -> /board/topic/79315-class-ccbutton-colored-caption-on-themed-button-l/.
I'll soon move it to this forum.

For more options: ImageButton
User avatar
jNizM
Posts: 2435
Joined: 30 Sep 2013, 01:33
GitHub: jNizM
Contact:

Re: [CLASS] CtlColors - color your controls

05 Jun 2014, 05:19

Windows 7 Enterprise - SP 1 - x64
AutoHotkey v1.1.15.00 (Unicode 64-bit)

#Include <Class_CtlColors>
#Include <Class_ImageButton>

Uncompiled (U-x64) --> worked
Compiled (U-x64) --> dont work
Compiled (U-x86) --> dont work

in the same script Class_ImageButton work with everything

any idea?

same with your sample.. if compiled do nothing

edit
your gist version (0.9.03.00/2013-06-27) - works
your github version (1.0.01.00/2014-02-15) - dont work

edit2
i found out it has todo with your ClassInit
[AHK] 1.1.30.03 x64 Unicode | [WIN] 10 Pro (Version 1903) x64 | [GitHub] Profile
Donations are appreciated if I could help you
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

07 Jun 2014, 01:49

jNizM wrote:Windows 7 Enterprise - SP 1 - x64
AutoHotkey v1.1.15.00 (Unicode 64-bit)

#Include <Class_CtlColors>
#Include <Class_ImageButton>

Uncompiled (U-x64) --> worked
Compiled (U-x64) --> dont work
Compiled (U-x86) --> dont work

in the same script Class_ImageButton work with everything

any idea?
Fixed! Thanks for reporting!
User avatar
jNizM
Posts: 2435
Joined: 30 Sep 2013, 01:33
GitHub: jNizM
Contact:

Re: [CLASS] CtlColors - color your controls

23 Jun 2014, 06:35

the 0x prefix is not working. can you add this feature?
[AHK] 1.1.30.03 x64 Unicode | [WIN] 10 Pro (Version 1903) x64 | [GitHub] Profile
Donations are appreciated if I could help you
Jeramy
Posts: 20
Joined: 18 Feb 2015, 05:42

Re: [CLASS] CtlColors - color your controls

06 Jul 2015, 01:15

Resurrecting this from the dead, only because I've come across it now, and want to point a couple bugs out.
1) You can't use this to change the base color of a DropDownList - it will remain the default color. This will change the list entries inside the DDL however.
2) If you need to change the base of ComboBox, you need to Detach, then Attach with the new color.

These behaviors were encountered when trying to use this to flash fields and lists which need to be filled out (flashing red when in an Error state when trying to submit the information).

Great work, though.
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

06 Jul 2015, 02:36

1) That's a feature of the theme. The DDL control is drawn like a pushbutton.
2) That's a bug in the Change() method. For ComboBoxes three HWNDs (ComboBox, Edit, ListBox) need to be updated, but currently only the HWND of the main control is actually updated. It will be fixed.
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

06 Jul 2015, 05:20

* 2) fixed *
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

27 Jul 2015, 07:10

WM_CTLCOLOR... messages do not support borders. Also, they are related to the client area. You have to add own drawing code to the message handler if you want to draw borders.
zotune
Posts: 81
Joined: 17 Nov 2014, 17:57

Re: [CLASS] CtlColors - color your controls

10 Sep 2015, 05:31

This is my favorite ahk library. Thanks for making this.

Any chance you'd be willing to implement highlight colors for Edit, Listbox, and possibly Treeview?
I only know a way to do listbox atm.
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

10 Sep 2015, 06:05

It's not a question of being willing. The class operates on WM_CTLCOLOR... messages. TreeView controls don't send this message. Each time a message is monitored ithe text color is set (if any) and a brush used for the background is returned. That's all what can be done in this context.
zotune
Posts: 81
Joined: 17 Nov 2014, 17:57

Re: [CLASS] CtlColors - color your controls

10 Sep 2015, 06:20

Okay, thanks. Even just the other two would be great. Although I know a way to do listbox.
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

10 Sep 2015, 08:22

Like I said, it isn`t the purpose of the WM_CTLCOLOR... messages to get the hilight colour. But you can change it using the system's colour settings.
micron

Re: [CLASS] CtlColors - color your controls

06 Oct 2015, 20:30

Win XP SP2 32bit
AutoHotkey_L ANSI x86 v.1.1.7.3

CtlColors_Sample.ahk
Error at line 99 in #include file "(...)\Class_CtlColors.ahk"

Line Text: Attach(HWND, BkColor, TxColor :="")
Error: Missing comma

The program will exit.
CtlColors_Sample.ahk and Class_CtlColors.ahk are in the same folder.

Some ideas?
lexikos
Posts: 6445
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: [CLASS] CtlColors - color your controls

06 Oct 2015, 20:37

micron: Update AutoHotkey.
micron

Re: [CLASS] CtlColors - color your controls

07 Oct 2015, 08:34

lexikos wrote:micron: Update AutoHotkey.
Ok, now run ;) , but

Win XP SP2 32bit
AutoHotkey_L ANSI x86 v.1.1.22.07

CtlColors_Sample.ahk
17 Gui, Add, CheckBox, xm vSTDCB1 gSTDCB1 hwndCBID1, Standard CheckBox
18 CtlColors.Attach(CBID1, "C0C0C0", "Red")

(...)

78 STDCB1:
79 GuiControlGet, STDCB1
80 CtlColors.Change(CBID1, (STDCB1 ? "Lime" : "C0C0C0"), "Red")
81 Return
the text is never "Red", is always "standard" (black/dark gray)
Gui, Add, Edit, xm w292 vED2 hwndEDID2 +Disabled, % " I'm disabled!"
CtlColors.Attach(EDID2, "Gray", "Lime")
the text is never "Lime", is always "standard" (gray/light gray)

The colors do not change even using the numeric codes and using different colors.

Is a limit of Win XP platform?
just me
Posts: 6425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [CLASS] CtlColors - color your controls

07 Oct 2015, 11:12

Buttons including Checkboxes and Radios which belong to the button class ignore the text color if a desktop theme is active.
Checkboxes and Radios accept only background colors due to design.
The color of the text in disabled controls is determined by the system and cannot be changed with this function.
User avatar
evilC
Posts: 4716
Joined: 27 Feb 2014, 12:30

Re: [CLASS] CtlColors - color your controls

07 Sep 2016, 07:31

This lib does not seem to take advantage of the changes introduced in v1.1.20 that allow OnMessage to use BoundFunc objects.

Here is an altered version of the lib that encapsulates the OnMesssage handler into the class. It should be compatible with old code that uses this lib.

Sample code - validate that editbox is an integer:

Code: Select all

#SingleInstance force

#NoEnv
;#Include Class_CtlColors.ahk
OnExit, GuiClose
; ----------------------------------------------------------------------------------------------------------------------
SysGet, SGW, 71 ; SM_CXMENUCHECK
LB_SETCURSEL := 0x186
CB_SETCURSEL := 0x14E
Red   := "FF0000"
Green := "00C000"
Blue  := "0000FF"
Pink  := "FF20FF"
; ----------------------------------------------------------------------------------------------------------------------
Gui, Add, Text, , Integers Only
Gui, Add, Edit, x+10 yp-3 w300 vED1 hwndEDID1 gEditChanged

; ----------------------------------------------------------------------------------------------------------------------
Gui, Show, , Colored Controls
Return

EditChanged:
	Gui, Submit, NoHide
	if (ED1 == "" || round(ED1 "") == ED1 ""){
		CtlColors.Detach(EDID1)
	} else {
		CtlColors.Attach(EDID1, "Red")
	}
	return

; ----------------------------------------------------------------------------------------------------------------------
GuiClose:
GuiEscape:
   Gui, Destroy
   CtlColors.Free()
ExitApp
; ----------------------------------------------------------------------------------------------------------------------
GuiSize:
   If (A_EventInfo != 1) {
      Gui, %A_Gui%:+LastFound
      WinSet, ReDraw
   }
Return
Replacement lib:

Code: Select all

======================================================================================================================
; AHK 1.1+
; ======================================================================================================================
; Function:          Auxiliary object to color controls on WM_CTLCOLOR... notifications.
;                    Supported controls are: Checkbox, ComboBox, DropDownList, Edit, ListBox, Radio, Text.
;                    Checkboxes and Radios accept only background colors due to design.
; Namespace:         CtlColors
; Tested with:       1.1.22.02
; Tested on:         Win 8.1 (x64)
; Change log:        1.0.03.00/2015-07-06/just me  -  fixed Change() to run properly for ComboBoxes.
;                    1.0.02.00/2014-06-07/just me  -  fixed __New() to run properly with compiled scripts.
;                    1.0.01.00/2014-02-15/just me  -  changed class initialization.
;                    1.0.00.00/2014-02-14/just me  -  initial release.
; ======================================================================================================================
; This software is provided 'as-is', without any express or implied warranty.
; In no event will the authors be held liable for any damages arising from the use of this software.
; ======================================================================================================================
Class CtlColors {
   ; ===================================================================================================================
   ; Class variables
   ; ===================================================================================================================
   ; Registered Controls
   Static Attached := {}
   ; OnMessage Handlers
   Static HandledMessages := {Edit: 0, ListBox: 0, Static: 0}
   ; Message Handler Function
   MessageHandlerFn := this.OnMessage.Bind(this)
   ; Windows Messages
   Static WM_CTLCOLOR := {Edit: 0x0133, ListBox: 0x134, Static: 0x0138}
   ; HTML Colors (BGR)
   Static HTML := {AQUA: 0xFFFF00, BLACK: 0x000000, BLUE: 0xFF0000, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
                 , LIME: 0x00FF00, MAROON: 0x000080, NAVY: 0x800000, OLIVE: 0x008080, PURPLE: 0x800080, RED: 0x0000FF
                 , SILVER: 0xC0C0C0, TEAL: 0x808000, WHITE: 0xFFFFFF, YELLOW: 0x00FFFF}
   ; System Colors
   Static SYSCOLORS := {Edit: "", ListBox: "", Static: ""}
   ; Error message in case of errors
   Static ErrorMsg := ""
   ; Class initialization
   Static InitClass := CtlColors.ClassInit()
   ; ===================================================================================================================
   ; Constructor / Destructor
   ; ===================================================================================================================
   __New() { ; You must not instantiate this class!
      If (This.InitClass == "!DONE!") { ; external call after class initialization
         This["!Access_Denied!"] := True
         Return False
      }
   }
   ; ----------------------------------------------------------------------------------------------------------------
   __Delete() {
      If This["!Access_Denied!"]
         Return
      This.Free() ; free GDI resources
   }
   ; ===================================================================================================================
   ; ClassInit       Internal creation of a new instance to ensure that __Delete() will be called.
   ; ===================================================================================================================
   ClassInit() {
      CtlColors := New CtlColors
      Return "!DONE!"
   }
   ; ===================================================================================================================
   ; CheckBkColor    Internal check for parameter BkColor.
   ; ===================================================================================================================
   CheckBkColor(ByRef BkColor, Class) {
      This.ErrorMsg := ""
      If (BkColor != "") && !This.HTML.HasKey(BkColor) && !RegExMatch(BkColor, "^[[:xdigit:]]{6}$") {
         This.ErrorMsg := "Invalid parameter BkColor: " . BkColor
         Return False
      }
      BkColor := BkColor = "" ? This.SYSCOLORS[Class]
              :  This.HTML.HasKey(BkColor) ? This.HTML[BkColor]
              :  "0x" . SubStr(BkColor, 5, 2) . SubStr(BkColor, 3, 2) . SubStr(BkColor, 1, 2)
      Return True
   }
   ; ===================================================================================================================
   ; CheckTxColor    Internal check for parameter TxColor.
   ; ===================================================================================================================
   CheckTxColor(ByRef TxColor) {
      This.ErrorMsg := ""
      If (TxColor != "") && !This.HTML.HasKey(TxColor) && !RegExMatch(TxColor, "i)^[[:xdigit:]]{6}$") {
         This.ErrorMsg := "Invalid parameter TextColor: " . TxColor
         Return False
      }
      TxColor := TxColor = "" ? ""
              :  This.HTML.HasKey(TxColor) ? This.HTML[TxColor]
              :  "0x" . SubStr(TxColor, 5, 2) . SubStr(TxColor, 3, 2) . SubStr(TxColor, 1, 2)
      Return True
   }
   ; ===================================================================================================================
   ; Attach          Registers a control for coloring.
   ; Parameters:     HWND        - HWND of the GUI control                                   
   ;                 BkColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ;                 ----------- Optional 
   ;                 TxColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ; Return values:  On success  - True
   ;                 On failure  - False, CtlColors.ErrorMsg contains additional informations
   ; ===================================================================================================================
   Attach(HWND, BkColor, TxColor := "") {
      ; Names of supported classes
      Static ClassNames := {Button: "", ComboBox: "", Edit: "", ListBox: "", Static: ""}
      ; Button styles
      Static BS_CHECKBOX := 0x2, BS_RADIOBUTTON := 0x8
      ; Editstyles
      Static ES_READONLY := 0x800
      ; Default class background colors
      Static COLOR_3DFACE := 15, COLOR_WINDOW := 5
      ; Initialize default background colors on first call -------------------------------------------------------------
      If (This.SYSCOLORS.Edit = "") {
         This.SYSCOLORS.Static := DllCall("User32.dll\GetSysColor", "Int", COLOR_3DFACE, "UInt")
         This.SYSCOLORS.Edit := DllCall("User32.dll\GetSysColor", "Int", COLOR_WINDOW, "UInt")
         This.SYSCOLORS.ListBox := This.SYSCOLORS.Edit
      }
      This.ErrorMsg := ""
      ; Check colors ---------------------------------------------------------------------------------------------------
      If (BkColor = "") && (TxColor = "") {
         This.ErrorMsg := "Both parameters BkColor and TxColor are empty!"
         Return False
      }
      ; Check HWND -----------------------------------------------------------------------------------------------------
      If !(CtrlHwnd := HWND + 0) || !DllCall("User32.dll\IsWindow", "UPtr", HWND, "UInt") {
         This.ErrorMsg := "Invalid parameter HWND: " . HWND
         Return False
      }
      If This.Attached.HasKey(HWND) {
         This.ErrorMsg := "Control " . HWND . " is already registered!"
         Return False
      }
      Hwnds := [CtrlHwnd]
      ; Check control's class ------------------------------------------------------------------------------------------
      Classes := ""
      WinGetClass, CtrlClass, ahk_id %CtrlHwnd%
      This.ErrorMsg := "Unsupported control class: " . CtrlClass
      If !ClassNames.HasKey(CtrlClass)
         Return False
      ControlGet, CtrlStyle, Style, , , ahk_id %CtrlHwnd%
      If (CtrlClass = "Edit")
         Classes := ["Edit", "Static"]
      Else If (CtrlClass = "Button") {
         IF (CtrlStyle & BS_RADIOBUTTON) || (CtrlStyle & BS_CHECKBOX)
            Classes := ["Static"]
         Else
            Return False
      }
      Else If (CtrlClass = "ComboBox") {
         VarSetCapacity(CBBI, 40 + (A_PtrSize * 3), 0)
         NumPut(40 + (A_PtrSize * 3), CBBI, 0, "UInt")
         DllCall("User32.dll\GetComboBoxInfo", "Ptr", CtrlHwnd, "Ptr", &CBBI)
         Hwnds.Insert(NumGet(CBBI, 40 + (A_PtrSize * 2, "UPtr")) + 0)
         Hwnds.Insert(Numget(CBBI, 40 + A_PtrSize, "UPtr") + 0)
         Classes := ["Edit", "Static", "ListBox"]
      }
      If !IsObject(Classes)
         Classes := [CtrlClass]
      ; Check background color -----------------------------------------------------------------------------------------
      If !This.CheckBkColor(BkColor, Classes[1])
         Return False
      ; Check text color -----------------------------------------------------------------------------------------------
      If !This.CheckTxColor(TxColor)
         Return False
      ; Activate message handling on the first call for a class --------------------------------------------------------
      For I, V In Classes {
         If (This.HandledMessages[V] = 0)
            OnMessage(This.WM_CTLCOLOR[V], This.MessageHandlerFn)
         This.HandledMessages[V] += 1
      }
      ; Store values for HWND ------------------------------------------------------------------------------------------
      Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BkColor, "UPtr")
      For I, V In Hwnds
         This.Attached[V] := {Brush: Brush, TxColor: TxColor, BkColor: BkColor, Classes: Classes, Hwnds: Hwnds}
      ; Redraw control -------------------------------------------------------------------------------------------------
      DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
      This.ErrorMsg := ""
      Return True
   }
   ; ===================================================================================================================
   ; Change          Change control colors.
   ; Parameters:     HWND        - HWND of the GUI control
   ;                 BkColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ;                 ----------- Optional 
   ;                 TxColor     - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
   ; Return values:  On success  - True
   ;                 On failure  - False, CtlColors.ErrorMsg contains additional informations
   ; Remarks:        If the control isn't registered yet, Add() is called instead internally.
   ; ===================================================================================================================
   Change(HWND, BkColor, TxColor := "") {
      ; Check HWND -----------------------------------------------------------------------------------------------------
      This.ErrorMsg := ""
      HWND += 0
      If !This.Attached.HasKey(HWND)
         Return This.Attach(HWND, BkColor, TxColor)
      CTL := This.Attached[HWND]
      ; Check BkColor --------------------------------------------------------------------------------------------------
      If !This.CheckBkColor(BkColor, CTL.Classes[1])
         Return False
      ; Check TxColor ------------------------------------------------------------------------------------------------
      If !This.CheckTxColor(TxColor)
         Return False
      ; Store Colors ---------------------------------------------------------------------------------------------------
      If (BkColor <> CTL.BkColor) {
         If (CTL.Brush) {
            DllCall("Gdi32.dll\DeleteObject", "Prt", CTL.Brush)
            This.Attached[HWND].Brush := 0
         }
         Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BkColor, "UPtr")
         For I, V In CTL.Hwnds {
            This.Attached[V].Brush := Brush
            This.Attached[V].BkColor := BkColor
         }
      }
      For I, V In Ctl.Hwnds
         This.Attached[V].TxColor := TxColor
      This.ErrorMsg := ""
      DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
      Return True
   }
   ; ===================================================================================================================
   ; Detach          Stop control coloring.
   ; Parameters:     HWND        - HWND of the GUI control
   ; Return values:  On success  - True
   ;                 On failure  - False, CtlColors.ErrorMsg contains additional informations
   ; ===================================================================================================================
   Detach(HWND) {
      This.ErrorMsg := ""
      HWND += 0
      If This.Attached.HasKey(HWND) {
         CTL := This.Attached[HWND].Clone()
         If (CTL.Brush)
            DllCall("Gdi32.dll\DeleteObject", "Prt", CTL.Brush)
         For I, V In CTL.Classes {
            If This.HandledMessages[V] > 0 {
               This.HandledMessages[V] -= 1
               If This.HandledMessages[V] = 0
                  OnMessage(This.WM_CTLCOLOR[V], this.MessageHandlerFn, 0)
         }  }
         For I, V In CTL.Hwnds
            This.Attached.Remove(V, "")
         DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
         CTL := ""
         Return True
      }
      This.ErrorMsg := "Control " . HWND . " is not registered!"
      Return False
   }
   ; ===================================================================================================================
   ; Free            Stop coloring for all controls and free resources.
   ; Return values:  Always True.
   ; ===================================================================================================================
   Free() {
      For K, V In This.Attached
         DllCall("Gdi32.dll\DeleteObject", "Ptr", V.Brush)
      For K, V In This.HandledMessages
         If (V > 0) {
            OnMessage(This.WM_CTLCOLOR[K], this.MessageHandlerFn, 0)
            This.HandledMessages[K] := 0
         }
      This.Attached := {}
      Return True
   }
   ; ===================================================================================================================
   ; IsAttached      Check if the control is registered for coloring.
   ; Parameters:     HWND        - HWND of the GUI control
   ; Return values:  On success  - True
   ;                 On failure  - False
   ; ===================================================================================================================
   IsAttached(HWND) {
      Return This.Attached.HasKey(HWND)
   }
   
	; ======================================================================================================================
	; OnMessage
	; This function handles CTLCOLOR messages. There's no reason to call it manually!
	; ======================================================================================================================
	OnMessage(HDC, HWND) {
	   Critical
	   If this.IsAttached(HWND) {
		  CTL := this.Attached[HWND]
		  If (CTL.TxColor != "")
			 DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", CTL.TxColor)
		  DllCall("Gdi32.dll\SetBkColor", "Ptr", HDC, "UInt", CTL.BkColor)
		  Return CTL.Brush
	   }
	}

}
By the way, is anyone else interested in trying to build a form validation library using this code to indicate when the GuiControl does not meet the validation requirements?

Return to “Scripts and Functions”

Who is online

Users browsing this forum: TAC109 and 46 guests