by Richard Russell, September 2007
You will normally want dialogue box controls to use their default colour scheme, for reasons of uniformity with other Windows applications, but occasionally you may want to change the foreground and/or background colours for special purposes. There are a number of techniques that can be used to achieve this, for example you can create a coloured Edit Box by using a Rich Edit control or you can create a coloured Static Control by drawing into a suitable bitmap and displaying it in a picture box or you can use a custom graphics control.
This article describes an alternative technique, involving subclassing the dialogue box using the SUBCLASS.BBC library (version 1.3 or later). In some ways it is more flexible than the other methods, since it allows you to change the colours of other controls, not just Text Boxes and Static Controls. To illustrate the effect the program listed below creates this rather garish dialogue box:
Firstly we need to install a couple of libraries and define some constants:
INSTALL @lib$+"WINLIB2" INSTALL @lib$+"SUBCLASS" INSTALL @lib$+"NOWAIT" BS_DEFPUSHBUTTON = 1 LB_ADDSTRING = &180 WM_CTLCOLORLISTBOX = 308 WM_CTLCOLORBTN = 309 WM_CTLCOLORDLG = 310 WM_CTLCOLORSTATIC = 312 WS_GROUP = &20000 WS_BORDER = &800000
Note that version 1.3 or later of SUBCLASS.BBC is required.
Next we can create the dialogue box template in the usual way:
dlg% = FN_newdialog("Dialogue box", 20, 20, 160, 128, 8, 560) PROC_listbox(dlg%, "", 101, 10, 8, 140, 80, 0) PROC_static(dlg%, "Static box", 102, 10, 82, 140, 20, WS_BORDER) PROC_pushbutton(dlg%, "OK", 1, 12, 108, 56, 14, WS_GROUP + BS_DEFPUSHBUTTON) PROC_pushbutton(dlg%, "Cancel", 2, 92, 108, 56, 14, 0)
So far everything is conventional, but now we sub-class the necessary Windows messages:
PROC_subclassdlg(dlg%, WM_CTLCOLORLISTBOX, FNctlcolorlistbox()) PROC_subclassdlg(dlg%, WM_CTLCOLORSTATIC, FNctlcolorstatic()) PROC_subclassdlg(dlg%, WM_CTLCOLORDLG, FNctlcolorbackgnd()) PROC_subclassdlg(dlg%, WM_CTLCOLORBTN, FNctlcolorbackgnd())
Note that in this particular case the same function is used for both the CTLCOLORDLG and CTLCOLORBTN messages because in each case all we want to do is change the background colour. If you don't change the background colour of the buttons to match the dialogue box background an ugly border around the buttons will be visible.
Now we can display the dialogue box and initialise any controls that need it:
PROC_showdialog(dlg%) SYS "SendDlgItemMessage", !dlg%, 101, LB_ADDSTRING, 0, "Listbox item 0" SYS "SendDlgItemMessage", !dlg%, 101, LB_ADDSTRING, 0, "Listbox item 1" SYS "SendDlgItemMessage", !dlg%, 101, LB_ADDSTRING, 0, "Listbox item 2" SYS "SendDlgItemMessage", !dlg%, 101, LB_ADDSTRING, 0, "Listbox item 3"
Again, this is conventional.
Finally we run our main loop which, for the purposes of the exercise, does nothing useful:
ON CLOSE PROC_closedialog(dlg%) : QUIT ON ERROR PROC_closedialog(dlg%) : SYS "MessageBox", @hwnd%, REPORT$, 0, 48 : QUIT ON SYS Click% = @wparam% AND &FFFF:RETURN Click% = 0 REPEAT PROCwait(0) click% = 0 SWAP Click%,click% UNTIL click%=1 OR click%=2 OR !dlg%=0 PROC_closedialog(dlg%) END
Note that while subclassing is in effect you must avoid statements which may take a long time to execute, such as GET, INKEY, INPUT and WAIT (WAIT should be avoided even if the delay is short). You should also be careful not to use SOUND when the sound queue is already full. You can find suitable replacements for these functions in the NOWAIT library.
We mustn't forget the vital routines that handle the subclassing:
DEF FNctlcolorbackgnd(M%, W%, L%) PRIVATE B% IF B%=0 SYS "CreateSolidBrush", &8080FF TO B% =B% DEF FNctlcolorlistbox(M%, W%, L%) PRIVATE B% SYS "SetTextColor", W%, &00FFFF SYS "SetBkColor", W%, &336633 IF B%=0 SYS "CreateSolidBrush", &336633 TO B% =B% DEF FNctlcolorstatic(M%, W%, L%) PRIVATE B% SYS "SetTextColor", W%, &FFFFFF SYS "SetBkColor", W%, &663366 IF B%=0 SYS "CreateSolidBrush", &663366 TO B% =B%
The colours are here specified in the usual Windows hexadecimal “BBGGRR” format. For the listbox and the static control both the foreground and background colours are set. You should normally set the text background colour (“SetBkColor”) to be the same as the control's background colour (“CreateSolidBrush”).
Note that in the above example all controls of a particular type are affected. For example, if the WM_CTLCOLORSTATIC message is processed then all Static Controls will change colour. If you want to change the colour of only a subset of the controls of a given type, or give them different colours, then you can test the control's handle and act accordingly:
DEF FNctlcolorstatic(M%, W%, L%) CASE L% OF WHEN hStatic1%: PRIVATE B% SYS "SetTextColor", W%, &FFFFFF SYS "SetBkColor", W%, &663366 IF B%=0 SYS "CreateSolidBrush", &663366 TO B% ENDCASE =B%
Here hStatic1% is a global variable containing the handle of the particular Static Control whose colour needs to be changed. Because that handle isn't known until after you have shown the dialogue box, you will need to force the static control to redraw itself:
SYS "GetDlgItem", !dlg%, 102 TO hStatic1% SYS "InvalidateRect", hStatic1%, 0, 0
Also, remember to initialise hStatic1% (e.g. to zero) so you don't get a 'No such variable' error when FNctlcolorstatic is called for the first time.