The new vbAccelerator Site - more VB and .NET Code and Controls
Source Code
3 Code Libraries &nbsp
Drawing Buttons, Option Boxes and Check Boxes In Your Own Style
An nice way to create buttons with your own style

NOTE: this code has been superceded by the version at the new site.


Sample Buttons you can draw with the demonstration project

Download the Owner-Draw buttons sample (34kb)

&nbsp Before you Begin &nbsp
&nbsp This project requires the SSubTmr.DLL component. Make sure you have loaded and registered this before trying the project. &nbsp

If you set the Style property VB CommandButton, OptionBox or CheckBox to Graphical, VB turns the control into an Owner-Draw control. By default VB allows you to associate three pictures with these controls - one which is drawn normally (Picture), one for when it is down (DownPicture) and one for when it is disabled (DisabledPicture). 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:

&nbsp &nbsp CtlType As Long &nbsp &nbsp ' -The Type of control firing the WM_DRAWITEM message
&nbsp &nbsp CtlID As Long &nbsp &nbsp ' -The ID of the control when created
&nbsp &nbsp ItemId As Long &nbsp &nbsp ' -The ListIndex of the item to draw
&nbsp &nbsp ItemAction As Long &nbsp &nbsp ' -What action is causing the draw call
&nbsp &nbsp ItemState As Long &nbsp &nbsp ' -The state of the item to be drawn
&nbsp &nbsp hwndItem As Long &nbsp &nbsp ' -The hWnd of the window to draw in
&nbsp &nbsp hDC As Long &nbsp &nbsp ' -The DC of the window to draw in
&nbsp &nbsp rcItem As RECT &nbsp &nbsp ' -The bounding rectangle of the item to be drawn
&nbsp &nbsp itemData As Long &nbsp &nbsp ' -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)


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 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)

In Use
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 message on and an interface class IOwnerDrawButton which your code should implements to perform the 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 has to 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:

Method Description
Attach Connects the class to an object which implements the IOwnerDrawButton object.
AddhWnd Adds another window handle to check for WM_DRAWITEM messages on to the class.
Detach Clears up the subclassing code in the class. Note this method is called automatically when a cOwnerDrawButton object terminates.
RedrawButton This method forces a control to repaint itself. Use it if you have changed the style of a button to be drawn at run-time and the button is visible.
SetBorderStyle If you are not perfoming any drawing code yourself, then setting this property for a graphical button will cause it to draw with a thin border rather than the standard two-pixel one.
SetStandardButtonStyle If you are not performing any drawing code yourself, then setting this property for a graphical button will cause it to draw in one of the enumerated standard styles (such as Close, Left, Right etc).

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:

Method Description
ButtonContainerhWnd This property is called when you first Attach to the class. Return the hWnd value of the container of the buttons you want to set to Owner-Drawn. If you have more than one container on your form which includes owner-draw buttons, then return any one of them and subsequently use the cOwnerDrawButton class' AddhWnd method to add futher containers.
DoOwnerDraw Called to request whether a given hWnd should be owner-drawn or not. Return True to continue with Owner-Draw processing, or False if you want the default button method to be called.
DrawItem Called when a button is about to be drawn. The parameters pass in the window handle of the item to drawn, the Device context to draw on, the Left, Top, Right and Bottom positions as well as a series of flags indicating whether the button is checked, pushed, enabled or in focus. The last parameter, bDoDefault allows you to instruct the class to do default button drawing regardless of whether you do some drawing yourself.

TopBack to top
Source Code LibraryBack to Code Libraries
Source Code - What We're About!Back to Source Code


AboutContributeSend FeedbackPrivacy

Copyright 1998-1999, Steve McMahon ( All Rights Reserved.
Last updated: 27 January 1999