Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

A user draw button that supports PNG files with transparency, for Visual C++ 6.0 and VS2005

4.93/5 (71 votes)
16 Jul 2008CPOL5 min read 8   30.6K  
With this class, you can add a PNG image to your button and automatically get a highlighted and grayscale version.

Image 1

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.

GdipButton2.PNG

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

    /// a bunch of stuff here

    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:

// load the standard image and alternate image
m_cPlay.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlay.LoadAltImage(IDR_PAUSE, _T("PNG"));
m_cPlay.EnableToggle(TRUE);

// just to show highlight state for article
m_cPlayHi.LoadStdImage(IDR_PLAY, _T("PNG"));

// set as disabled
m_cPlayDis.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlayDis.EnableButton(FALSE);

// show a larger button type
m_cGear.LoadStdImage(IDR_GEAR, _T("PNG"));

// replace the OK button with something
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);

    // fill in the back ground with something

    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.

// VC6
#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;

// VS2005
#else 

#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;

#endif

History

  • Version 1.0 (10/June/2008)
    • First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)