Environment: Windows XP, VC++
Copyright © 2002 by Ewan Ward
With the advent of Windows XP, Microsoft has attempted to ensure that a lot of the responsibility for rendering the user interface now lies with the operating system rather than the programmer. That's fine in most cases, but there are still some situations when you want to be able to draw the control yourself. One of the most common is a button with an image on it, and in this article we discuss implementing such a control using the native Win32 API. Although the discussion concerns owner-draw buttons specifically, the topics covered here will apply to all owner-draw controls.
In this article, we discuss how to implement a simple native Win32 application that contains a theme-aware, owner-draw button that will run on both themed and non-themed Microsoft Windows 32-bit operating systems.
2.1. The Dialog Template
Open up the sample project (OwnerDraw.dsp) and look at IDD_MAIN_DLG. You will see that the UI comprises a dialog box with two buttons, one an owner-draw control button and one a normal push button (for reference). Open up the resource script (OwnerDraw.rc) as text and see that our owner-draw button is a normal system button but with the BS_OWNERDRAW button style flag set.
////////////////////////////////////////////////////////////////// // // Dialog // IDD_MAIN_DLG DIALOG DISCARDABLE 0, 0, 307, 122 STYLE DS_SETFOREGROUND | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Owner Draw Sample" FONT 8, "MS Sans Serif" BEGIN CONTROL "Owner Draw",IDC_OWNERDRAW_BTN,"Button", BS_OWNERDRAW | WS_TABSTOP,80,20,105,35 PUSHBUTTON "Normal",IDC_NORMAL_BTN,80,60,105,35 PUSHBUTTON "OK",IDOK,250,5,50,14 PUSHBUTTON "Cancel",IDCANCEL,250,22,50,14 END
With this flag set, we are telling Windows that it is the responsibility of the button's owner (the dialog) to draw the button. You will find the implementation for the dialog in MainDlg.cpp.
2.2. What A State!
What a control looks like on the screen is a reflection of its state. A button, for example, could be pressed or focused. The pre-defined states that Windows can tell us about are...
|ODS_CHECKED||The menu item is to be checked. This bit is used only in a menu.|
|ODS_COMBOBOXEDIT||The drawing takes place in the selection field (edit control) of an owner-drawn combo box.|
|ODS_DEFAULT||The item is the default item.|
|ODS_DISABLED||The item is to be drawn as disabled.|
|ODS_FOCUS||The item has the keyboard focus.|
|ODS_GRAYED||The item is to be grayed. This bit is used only in a menu.|
|ODS_HOTLIGHT||Windows 98/Me, Windows 2000/XP: The item is being hot-tracked; that is, the item will be highlighted when the mouse is on the item.|
|ODS_INACTIVE||Windows 98/Me, Windows 2000/XP: The item is inactive and the window associated with the menu is inactive.|
|ODS_NOACCEL||Windows 2000/XP: The control is drawn without the keyboard accelerator cues.|
|ODS_NOFOCUSRECT||Windows 2000/XP: The control is drawn without focus indicator cues.|
|ODS_SELECTED||The menu item's status is selected.|
...but obviously, not all of these apply to our button.
Although Windows will notify us if any of the relevant states listed above change, in our example we only redraw the button in the following situations:
- It gains the focus
- It loses the focus
- It is pressed
- It is released
- (especially for XP) If it is "hot"
2.2.1. Hot Tracking
XP users may have noticed that system buttons are highlighted (or tracked) when the mouse is over the button. However, we don't receive any special notification from Windows when a system button is being hot-tracked. For that, we need to determine whether the mouse has moved over the button. In our example, we have used the old 16-bit technique of subclassing—basically, we have replaced the Windows procedure of the owner-draw button with our own procedure so that we can listen out for WM_MOUSEMOVE messages. When the mouse moves over the button, the procedure will receive the WM_MOUSEMOVE message and then we can redraw the button highlighted.
2.3. Getting The Message
It is the WM_DRAWITEM notification from the operating system that will prompt us to redraw the button. You can see that provided with the notification is information on the current state of the button according to Windows. Coupled with the extra state information gleaned from subclassing, we now have enough information at hand in order to render the button.
2.3.1. Double Clicks
When the owner-draw button is double-clicked, it sends out a BN_DBLCLK notification rather than responding as if it had been pressed and released twice over. To change this behaviour, we can make our owner-draw button post the WM_LBUTTONDOWN notification back to itself when it receives a WM_LBUTTONDBLCLK message.
So far, so good. The next problem is deciding what the button should look like. The first step is to determine whether the operating system supports themes. The function InitThemes() attempts to load UXTHEME.DLL dynamically. Don't try and statically link to the UXTHEME library; the application will fail to run on systems that don't ship with the DLL. We load the following functions:
2.5. Drawing The Button
Drawing the button is then a four-stage process:
- Stage 1—draw the background (including the frame).
- Stage 2—draw the icon.
- Stage 3—write on the text.
- Stage 4—draw the focus rectangle.
3. Reusing The Sample Code
To add basic support of themes to your application, copy lines 27 to 82 (inclusive) from MainDlg.cpp. You must include the headers UXTHEME.H and TMSCHEMA.H. Remember also to include a manifest and to call InitCommonControls when your application is first launched.
The functions PrepareImageRect and DrawTheIcon are used to put the icon on the button. The WM_DRAWITEM handler demonstrates how to draw a themed and non-themed button, depending on the operating system.
The functions PrepareImageRect() and DrawTheIcon() are taken from the CButtonST class by Davide Calabró.
DownloadsDownload source - 12 Kb
Download demo - 13 Kb