Drawing Buttons, Option Boxes and Check Boxes In Your Own Style
Subclass the standard VB Buttons to draw anything on their surface.
If you set the Style property for VB's CommandButton, OptionBox or CheckBox controls to Graphical, VB turns the control into an Owner-Draw control. By default VB allows you to associate three pictures with these controls. However, if you've ever tried this you will know that the button draws like a turd when you set these properties.
The code with this sample shows you how to intercept the WM_DRAWITEM messages an Owner-Draw button sends to its parent whenever it is about to draw itself and replace VB's implementation with something a bit nicer.
How it Works
The basic function of the class is to intercept WM_DRAWITEM messages send to a window handle. When it gets these, it determines whether the message was sent by a button control; if it was it asks your code if you want to draw the item yourself, passing the information you need to draw using GDI calls. If you do choose to draw it, the class eats the WM_DRAWITEM message so the button does not receive it. If you don't choose to draw the object yourself, then the class passes the message on to the old Window Procedure which performs the standard drawing built into the button.
About the WM_DRAWITEM message
The WM_DRAWITEM message passes a pointer to a structure, DRAWITEMSTRUCT as the lParam of the message:
Type DRAWITEMSTRUCT CtlType As Long ' -The Type of control firing the message CtlID As Long ' -The ID of the control when created ItemId As Long ' -The ListIndex of the item to draw ItemAction As Long ' -What action is causing the draw call ItemState As Long ' -The state of the item to be drawn hwndItem As Long ' -The hWnd of the window to draw in hDC As Long ' -The DC of the window to draw in rcItem As RECT ' -The bounding rectangle of the item to be drawn itemData As Long ' -The item data of the item to be drawn End Type
Tip Getting at Data From a Memory Pointer
In Win32 API calls, often you get a pointer to data rather than the data itself. To get at data pointed to by a Long, you can use the ubiqutous CopyMemory API alias:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _ lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long) ... Dim tDIS As DRAWITEMSTRUCT CopyMemory tDis, ByVal lParam, Len(tDis)
Note the use of ByVal in the second parameter of CopyMemory. You need to do this because the CopyMemory declare uses the As Any type to allow it to work for all types of structures and pointers. If you pass a Long parameter without ByVal to lpvSource, CopyMemory will attempt to copy the memory holding the long value (i.e. 4 bytes) because VB has actually passed a pointer to the long value (lParam) itself rather than passing its value as a pointer. This leads very quickly to a crash if the data you are copying is more than 4 bytes long! So in this case specifying ByVal VB passes the value of lParam across rather than a pointer to lParam and so the copy works.
If the data pointed to be lParam is a string, you can use the API call lstrlen (lstrlenw if it's a Unicode string) to find out the size of the string in the memory being pointed to. You just have to modify the standard VB declare to accept As Any and call it like this:
Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" ( _ lpString As Any) As Long ... lLen = lstrlen(ByVal lParam)
The Owner-Draw button code supplied with this project comes in two classes: a cOwnerDrawButton class which handles registering window handles to detect the WM_DRAWITEM messages and an interface class IOwnerDrawButton which your code should implements to perform the actual drawing of the buttons. Whilst it wasn't strictly necessary to code an interface for this class (the cOwnerDrawButton class could just raise an event to the calling form), this class should perform at maximum speed to ensure the buttons draw nicely in a user interface. If you use an event interface between a class and the owner, you are using a form of late-bound interface. Late-bound interfaces are always slower in operation than early-bound ones, and you can prevent late-binding by raising your "events" through an implemented interface.
The cOwnerDrawButton class presents these public interfaces:
In order to use the class, the form containing the graphical buttons must implement the IOwnerDrawButton interface. This interface contains the following methods and properties: