Change the Style of a Control / Owner Drawn Controls

Often it would be nice to modify the behavior or style of Visual Basic’s many controls. For example, changing the font of individual ListBox items. Well, you can by changing the Style of the control.

Change the style of VB's controls using subclassing and owner drawn controls.
Download Source Code

Applies To

This sample was written in VB6 but uses the VB5 version of the ProgressBar and TabStrip controls to provide backward compatibility. If you use the VB6 version of these controls or compile this program you must change the class names in procedure fAppHook from ProgressBarWndClass and TabStripWndClass to ProgressBar20WndClass and TabStrip20WndClass respectively.

Controls Have Style

A control is defined by its window Class and Style. For example, a CommandButton, CheckBox and OptionButton are all created from the same window class but use different style attributes. Because a style setting is not available natively through VB doesn’t mean it is not accessible.

Hooks

You can change the style of a control before it is created. This means hooking into the stream of messages Windows sends to your application and watching to see when the control is created. Although Visual Basic hooks cannot monitor system events like when another application is started or receives a keystroke (system hooks must reside in a standard dll and not VB’s ActiveX dlls) VB hooks can watch thread level events and that's exactly what's needed here.

Just what is a hook? A hook lets you intercept messages that Windows sends to a thread. Whenever an action such as pressing a key or moving a mouse occurs, Windows generates a message that it passes through a chain of hook procedures before sending the message to the target window.

This process is similar to subclassing which consists of replacing the window's original message handling procedure with your own. For more info on subclassing, see my Subclass a Form program. The difference is that with a hook you do not replace the original procedure. Instead you insert a new procedure at the top of a chain. Each thread includes several hook chains of various types and each hook type handles a particular category of messages, such as keyboard or mouse.

Each hook procedure is responsible for calling the next hook procedure in the chain. Failure to do so does not cancel the message but merely prevents the subsequent procedures in the chain from seeing the message. The advantage of hooking over subclassing is that a hook can see all messages sent to an entire thread, regardless of the active window.

Sample Program Discussion

This program shows how to change the window style of several controls.

A hook is set in Sub Main by passing the application's instance handle and thread ID, both properties of VB's App object, to the SetWindowHookEx API. Using the AddressOf operator we also pass the address of our hook procedure which is called fAppHook. Now, Windows will call fAppHook before it sends any messages to the controls in our application.

fAppHook takes three parameters. Its lParam parameter receives a pointer to a CWPSTRUCT structure that contains details about the message that was sent. Using this address and the CopyMemory API a local copy of CWPSTRUCT is made so we can access it.

If the message sent, indicated by CWPSTRUCT's message element, is WM_CREATE we know a control is about to be created. Using the GetClassName API we can see if its class name matches that of the control we want to change.

Visual Basic uses its own class names for most of its controls. Typically these names begin with the word "Thunder". To find out what the class name is for a control we can insert a Debug.Print statement in the hook procedure or use a tool like Spy++. Once we know a control of the proper class is being created we must be sure that it is the correct instance of the control. For example, we have 2 comboboxes and only want to modify one of them. In our case a flag is set denoting whether it is the first combobox being created or not. More about this in a moment.

Once we have the correct control, the existing style attributes are retrieved via the GetWindowLong API with the GWL_STYLE parameter and the style is modified to our liking. To set the new style we must do so when the control receives the WM_CREATE message. This means subclassing the control so we can catch that message. The SetWindowLong API is called and passed the handle of the new control and the address of our new window procedure (named fSetStyle).

It is important to note that when setting the new style on some controls, a variation of the OwnerDrawn flag is used.

fSetStyle watches for a create message. When one arrives, its lParam argument gets the address of the CreateStruct structure which accompanies the message. This structure contains information on how to create the new control including its location and style information. As hinted at earlier, the location can be used to determine if we have the correct control instance to modify. Because CreateStruct cannot be modified, a local copy is made. The local copy's style is set to the desired value and the structure is copied back.

A call to SetWindowLong with the GWL_STYLE flag sets the new control's style. A second call to SetWindowLong, this time with the GWL_WNDPROC flag and the original window procedure's address, removes the subclassing from the control. Finally, CallWindowProc is issued to call the original window procedure associated with the control.

Owner Drawn Controls

Owner Drawn controls have no predefined appearance. It is up to the application, not the system, to draw them. Messages, such as WM_DRAWITEM, which are normally sent to a control are instead sent to the control’s parent. When the parent receives the message it is up to the parent to draw the control however it wants.

Such controls let you create custom controls that look however you want. Like using bitmaps for the captions on the TabStrip, tab stops in a ComboBox or different fonts inside a listbox as shown in the sample project.

In the form load event the parent of the TabStrip, ComboBox and ListBox controls are subclassed in order to trap the WM_DRAWITEM message. The subclassing is accomplished as described above using the SetWindowLong function and passing it the address of our new window procedure -- named fAppWndProc.

fAppWndProc watches for the WM_DRAWITEM message. When one is sent the accompanying DRAWITEMSTRUCT structure is copied to local storage. If its CtlType element specifies one of our owner drawn controls the appropriate code is executed to draw the control. See the commented source code for further details.

Credits

This sample is a regurgitated version of the project written by Matt Hart that appeared in the July, 1999 edition of Visual Basic Programmers Journal. I have included it because it illustrates a number of very useful topics.

Instructions

Download the source code and run it. As with any project that employs subclassing, you should always start with a full compile by pressing Ctrl-F5 to run it.




About TheScarms
About TheScarms


Sample code
version info

If you use this code, please mention "www.TheScarms.com"

Email this page


© Copyright 2024 TheScarms
Goto top of page