SetCtlColors with variables

From NSIS Wiki
Jump to navigationJump to search
Author: Afrow UK (talk, contrib)


Description

This is an alternative to the built-in SetCtlColors which allows variables for the colours. It does not support /BRANDING but it does support "transparent" for the background colour.

The reason the built-in SetCtlColors does not support variables is because it converts the given colours from RGB to BGR (required for COLORREF) at compile time before storing the converted values in the executable header inside a "ctlcolors" structure. My function simulates the code that is built into NSIS by allocating its own ctlcolors structure and passing its memory pointer to the chosen control using SetWindowLong/GWL_USERDATA. For this reason, the instance of the ctlcolors structure must remain in memory until the control is destroyed, or to be on the safe side, until .onGUIEnd is called. If we free the allocated memory any sooner (System::Free), NSIS can't read from the ctlcolors structure and a memory access violation will occur.

The Function

Function SetCtlColors
  Exch $R0
  Exch
  Exch $R1
  Exch
  Exch 2
  Exch $R2
  Push $R3
  Push $R4
 
  !if "${NSIS_PTR_SIZE}" > 4
  !error "TODO: Not 64-bit compatible, 64-bit uses a different ctlcolors struct"
  !endif
  !define ctlcolors (i,i,i,i,i,i)i
  !define BS_SOLID 0
  !define BS_NULL 1
  !define TRANSPARENT 1
  !define OPAQUE 2
  !define CC_TEXT 1
  !define CC_BK 4
  !define CC_BKB 16
  !define GWL_USERDATA -21
 
  !macro RGB2BGR Var
    IntOp $R3 ${Var} & 0xFF
    IntOp $R3 $R3 << 16
    IntOp $R4 ${Var} & 0xFF00
    IntOp $R3 $R3 | $R4
    IntOp $R4 ${Var} & 0xFF0000
    IntOp $R4 $R4 >> 16
    IntOp ${Var} $R3 | $R4
  !macroend
 
  !insertmacro RGB2BGR $R1
  ${If} $R0 == transparent
    System::Call *${ctlcolors}(R1,0,${BS_NULL},0,${TRANSPARENT},${CC_TEXT}|${CC_BKB}).R0
  ${Else}
    !insertmacro RGB2BGR $R0
    System::Call *${ctlcolors}(R1,R0,${BS_SOLID},0,${OPAQUE},${CC_TEXT}|${CC_BK}|${CC_BKB}).R0
  ${EndIf}
  System::Call user32::SetWindowLong(iR2,i${GWL_USERDATA},lR0)
 
  Pop $R4
  Pop $R3
  Pop $R2
  Pop $R1
  Exch $R0
FunctionEnd
!macro SetCtlColors Hwnd Text Background Memory
  Push ${Hwnd}
  Push ${Text}
  Push ${Background}
  Call SetCtlColors
  Pop ${Memory}
!macroend
!define SetCtlColors `!insertmacro SetCtlColors`

Usage

Var SetCtlColorsVar1
 
Section
 
  FindWindow $R0 `#32770` `` $HWNDPARENT
  GetDlgItem $R0 $R0 1006
 
  StrCpy $R1 0xFF0000
  StrCpy $R2 0x00FF00
  ${SetCtlColors} $R0 $R1 $R2 $SetCtlColorsVar1
 
SectionEnd
 
Function .onGUIEnd
  System::Free $SetCtlColorsVar1
FunctionEnd

The above code is the same as SetCtlColors $R0 0xFF0000 0x00FF00. Notice the System::Free call at the end in .onGUIEnd. You will have a memory leak if you don't use it. Also note that for each call to SetCtlColors you will need a new SetCtlColorsVar to store a pointer to the allocated memory. Reusing SetCtlColorsVar1 (in this example) is not an option.