Introduction
This article describes how to use the bitmap button template in WTL. You can
use either the CBitmapButton
class, as provided in AtlCtrlx.h, or
our small helper class, CBmpBtn
, which uses the template directly
and fixes an image refresh problem we encountered. The source code for the helper
class is available from the link above and is used in the sample project.
Button Images
There are many fine articles about drawing button images on CodeProject.com
and elsewhere. Except for a discussion later in this article, we refer you to
those other sources.
Button Styles
CBitmapButtonImpl
by default sets only the
BMPBTN_AUTOSIZE
style. Autosize adjusts the size of the button to
fit the image you provide. CBmpBtn
, the helper class in
the sample project adds BMPBTN_AUTO3D_SINGLE
to the extended styles
set in the button constructor so that 3D borders are drawn around button
images.
Also in the sample project is a method that sets the hover button style when
a checkbox is clicked. This allows you to see the effects with and without hover
style. For regular programs, you would just add BMPBTN_HOVER
style
to the button constructor.
Button styles may also be set with SetBitmapButtonExtendedStyle()
.
Other styles, some of which are discussed later in this article, include:
BMPBTN_AUTO3D_DOUBLE
-- draws a double button border to
resemble "classic" Windows buttons
BMPBTN_AUTOFIRE
-- autofire buttons repeat the button clicked
message when they are held down
BMPBTN_SHAREIMAGELISTS
-- indicates that the button's internal
image list is shared with other buttons
Using CBitmapButton
The CBitmapButtonImpl
template is unusual in two ways. First,
it uses SubclassWindow
to attach to a button object instead of just
using a simple assignment like most WTL controls do. Second, it will give multiple
asserts in Debug builds unless you first create and assign an imagelist and at
least one image to the button before it is subclassed.
It is also important to note that CBitmapButtonImpl
is a "pure"
bitmap button and does not do any Windows text handling. You must draw text in
the images you supply. See the section "How To Make Button Images" later in this
article for an example.
In the sample project, the CImageList
member variable of the OK
button is used to hold images for the other buttons. The imagelist is created in
the OnInitDialog
handler like this:
CBmpBtn m_ok;
CBmpBtn m_cancel;
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)
{
DWORD dw = BMPBTN_SHAREIMAGELISTS | BMPBTN_AUTO3D_SINGLE | BMPBTN_AUTOSIZE;
m_ok.SetBitmapButtonExtendedStyle(dw);
m_ok.m_ImageList.Create(IDB_BUTTONS, 64, 0, RGB(192,192,192));
...
After the imagelist is created, you need to assign it to the button. The
following code, a continuation of the initialize dialog handler started above,
shows the steps involved in subclassing the sample dialog's OK button and then
using the shared imagelist for the Cancel button. A button must have at least
one image and may have up to four for handling different button states.
HIMAGELIST hImage = m_ok.GetImageList();
m_ok.SetImages(0, 1, 2, 3);
m_ok.SubclassWindow(GetDlgItem(IDOK));
m_cancel.SetImageList(hImage);
m_cancel.SetImages(4, 5, 6, 7);
m_cancel.SubclassWindow(GetDlgItem(IDCANCEL));
Remember to destroy the imagelist when your program ends. The
OnDestroy()
handler of the dialog is a good place as shown here:
LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL&)
{
m_ok.m_ImageList.Destroy();
return 0; }
Image Artifacts
When using the DoPaint()
message provided by the template,
button images overlaid each other in a most alarming fashion. To correct this,
we added a background fill and call the inherited method to handle the rest:
void DoPaint(CDCHandle dc)
{ RECT rc;
GetClientRect(&rc);
dc.FillRect(&rc, (HBRUSH)(COLOR_BTNFACE+1));
CBitmapButtonImpl<CBmpBtn>::DoPaint(dc); }
Note that we use FillRect()
here to, basically, paint out the
previous button state. You may think to use FloodFill()
instead,
but we discovered that FloodFill()
is many times slower than
FillRect()
and causes sluggish button action.
Autofire Buttons
The bitmap button template provides an autofire style. When an autofire
button is held down beyond a timer interval, it sends multiple
BN_CLICKED
commands, until the button is released. The sample
project shows how to handle the clicked messages and use them to increment a
CProgressBarCtrl
. Autofire buttons are useful in any situation
where users would otherwise have to perform multiple button clicks.
How To Make Button Images
Each bitmap button requires at least one image and may have up to four. The
images represent button Normal, Pressed, Hover, and Disabled states. If you only
provide one image, it will be used for all four states. See the Autofire button
for an example of using a single image.
The following image is used for the sample project's OK button. Each quarter
(1/4) of the image serves one of the four button states. The segments are 64
pixels wide, which is slightly narrower than a "classic" button, and 24 pixels
high.
To make a Normal image, use an image editor to fill the background with the
mask color, add any pictures desired, then place the text. Pressed images are
filled with a crosshatch background, i.e. alternating gray and white dots, atop
which are placed the same picture and text as Normal. CBitmapButton
offsets the image by one pixel over and one pixel down to complete the "pressed"
look.
Making a Hover image is simple. Start with a copy of the Normal image and
change image outline colors and text colors to your preferred hover color. We
use medium blue instead of the traditional navy blue so that the hover (and
focus, in non-hover style) stands out clearly.
The final state, Disabled, also starts with a copy of Normal. However,
you must: 1) gray out any colors in the image; 2) change the picture outline and
text colors to dark gray; 3) draw a white shadow to the right and bottom of each
picture and letter.
Terms Of Use
The sample project and bitmap button class available with this article are
free. Use them however you wish.
THIS SOFTWARE IS DISTRIBUTED AS-IS, WITHOUT WARRANTIES OF ANY KIND.