Introduction
There are several owner draw buttons on this site, but I couldn’t find one that easily supports PNG files with transparency, so I created this class. Since this class uses GDI+, it actually supports many image formats, but the better quality buttons available are now PNG instead of ICO, so that is the one highlighted here.
Update: I have an extended version of this class in my Style Toolkit. If all you want is to use an image on a button, this class is probably simpler to use.
Background
GDI+ is part of the Microsoft Windows SDK, and it needs to be initialized when the application starts up. If you haven’t used GDI+ before, look at the source code of the demo project and read this article:
Features
- Grayscale Image
- The class will automatically create a grayscale image from the loaded resource. The grayscale image will be displayed when the button is set to the disabled state.
- Highlight Image
- The class will automatically create a highlighted image from the loaded resource. The highlighted image is displayed when the mouse hovers over the button boundaries.
- Alternate Image
- You can optionally add an alternate image. The alternate image will be displayed when set by a function call, or when the button is clicked and toggle mode is enabled.
- Toggle Mode
- When enabled, the button switches between the standard image and the alternate image each time it is pressed.
- Pressed State
- When the button is pressed, the image moves down and to the right 1 pixel.
- Tool Tips
- Tool tips can be optionally added.
The image at the top of the page shows the play button in three different states. From left to right, they are: normal, highlighted, and disabled. The other two buttons are just more examples that look cool.
It may not be too obvious from the picture that the second button is in the highlighted state. This is by design, I don’t want to significantly change the image. The highlight state just increases the brightness and the contrast a bit. It is obvious enough when you move the mouse over it. This class prioritizes image quality, so it will never stretch or shrink the image which usually degrades the quality, it will just paint the part that fits. If you need to resize your image, use an image editor like Photoshop.
The picture below shows the play button in the toggled state, and it should be obvious why you would want such a feature. The exit button is in the highlighted state, and the tool tip is shown in this image.
There is no performance penalty for using GDI+ (assuming such a penalty exists) since it is only used during initialization. On creation, the images are converted to bitmaps, and the bitmaps are used when the control needs to be repainted.
Using the code
Step 1 – Add these files to your project
Step 2 – Add resources, member variables, and images
Add a button to your dialog box with the resource editor, set the resource ID, and erase the text in the Caption box. You could set the style to Owner Draw, but you don’t need to because the code will automatically set this style.
Using the Class Wizard, add a variable to the ID you just created. In this example, I set the ID to IDC_PLAY
, and the variable name to m_cPlay
. Edit the dialog’s .h file, and change the control from CButton
to CGdiButton
. Don’t forget to include the file “GdipButton.h”.
In the resource editor, import the .png file from the res folder and set the resource type to PNG. Using PNG is just convention, it can be anything as long as the code matches what you name it. Right click on IDR_PNG1
, select Properties, and rename it to something useful, IDR_PLAY
in this example.
Step 3 – Add the LoadStdImage() function
Now, we just need to load the image to the button on initialization. In the OnInitDialg()
function, add the following code near the bottom:
BOOL CTestGdipButtonDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_cPlay.LoadStdImage(IDR_PLAY, _T("PNG"));
return TRUE;
}
Step 4 – Build and Run
You should be able to run it now and see your new PNG button! If it crashes here, it is probably because you didn’t initialize GDI+. Review the Background section of this article.
The demo project
This is all the code that is needed to create the buttons in the test program:
m_cPlay.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlay.LoadAltImage(IDR_PAUSE, _T("PNG"));
m_cPlay.EnableToggle(TRUE);
m_cPlayHi.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlayDis.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlayDis.EnableButton(FALSE);
m_cGear.LoadStdImage(IDR_GEAR, _T("PNG"));
m_cShutDn.LoadStdImage(IDR_EXIT, _T("PNG"));
m_cShutDn.SetToolTipText(_T("Close Program"));
Both the VC6 and VS2005 versions are included in the demo project.
Issues with transparent images
The button control has no idea what the background beneath it should be; it gets this information from the bitmap associated with the current DC. In most cases, this works fine, the background is what is on the screen just before the control is painted. However, the application may not be the top most when it is launched. An always on top application like Task Manager may be in the way, so when it gets the background image, it is the wrong data. This is overcome by calling the SetBkGnd()
function by the code that actually creates the background.
Set all the button backgrounds in the parent’s OnEraseBkgnd()
function. The demo program does this with the following code:
BOOL CTestGdipButtonDlg::OnEraseBkgnd(CDC* pDC)
{
CDialog::OnEraseBkgnd(pDC);
CRect rect;
GetClientRect(rect);
CMemDC pDevC(pDC, rect);
SetButtonBackGrounds(pDevC);
return TRUE;
}
void CTestGdipButtonDlg::SetButtonBackGrounds(CDC *pDC)
{
m_cPlay.SetBkGnd(pDC);
m_cPlayHi.SetBkGnd(pDC);
m_cPlayDis.SetBkGnd(pDC);
m_cShutDn.SetBkGnd(pDC);
}
Since the DC that is passed is a memory DC, it doesn’t matter what other applications might be doing. The code above assumes you want to use something other than the crummy default background; otherwise, you would probably just use the default crummy button.
VC6 Build issues
VC6 needs a few extra things to compile correctly, add the following to your stdafx.h file. Also add the SDK include and lib paths to your environment.
#if defined(_MSC_VER) && _MSC_VER == 1200
#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include <Specstrings.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;
#else
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;
#endif
History
- Version 1.0 (10/June/2008)
I am currently working as a consultant in Southern California.
I have worked as a Hardware Engineer, Firmware Engineer, Software Engineer and Applications Engineer.
I spent 13 years in the Disk Drive industry and the last 7 working in GPS.