In this tip, you will see an easy to use class to support displaying icons on button controls. Supports placing icon to the left or right of the text or center with text overlay.
Introduction
I needed to add a 16x16 graphic icon to a button with the icon to the right of the text, but I could only get BM_SETIMAGE
to place it to the left of the text. After asking around online and researching how it may be done, I threw together this class to give me what I need. Thanks to everyone who provided helpful tips and information. I'm giving back so others don't have to hassle with the same thing.
I tested this back to NT4 SP4 to ensure apps can still run on those versions if needed.
Improvement Needed
The icons were put in an image list in an attempt to support drawing disabled icons. I was hoping to use DrawThemedIcon()
for the proper look, but I couldn't get the function to do anything; so I abandoned it and just used the non-themed method for both themed and non-themed environments. It currently uses ILD_BLEND25
for disabled icons, however, it would be better to lighten the icon image to match what the default controls do because it looks better, and when using ILD_BLEND25
in a limited color environment, you can see the blue selection over the icon.
My .ico are only 16x16 so I hard coded the size, but in a way that could be improved easily, should someone need other sizes.
Using the Code
One method for use is to do the following:
- Include header file:
#include "cownerdrawbutton.h"
- Setup icon map of button ID and Icon ID:
static COwnerDrawButton::sIconMap OwnerDrawIconMap[]={
{ IDI_ICONOK, IDOK, COwnerDrawButton::sIconMap::RIGHT },
{ IDI_ICONCANCEL, IDCANCEL, COwnerDrawButton::sIconMap::LEFT },
{ IDI_ICONHELP, IDHELP, COwnerDrawButton::sIconMap::LEFT },
};
- Setup a global variable and reference the map:
COwnerDrawButton OwnerDrawButton(OwnerDrawIconMap, _countof(OwnerDrawIconMap));
- Setup the dialog callback procedure with:
case WM_INITDIALOG:
{
OwnerDrawButton.SetupButtonsForHWND(hwnddlg, g_hResInstance);
break;
}
case WM_DRAWITEM:
{
LRESULT lr=OwnerDrawButton.DrawItem(reinterpret_cast<LPDRAWITEMSTRUCT>(lparam));
if (lr!=-1) {
return lr;
}
break;
}
case WM_NOTIFY:
{
LRESULT lr=OwnerDrawButton.CustomDrawItem(reinterpret_cast<LPNMCUSTOMDRAW>(lparam));
if (lr!=-1) {
SetWindowLongPtr(hwnd, DWLP_MSGRESULT, lr); return lr;
}
break;
}
- That's all folks!
MFC
It wasn't designed for MFC but can still be used with. Here's one example:
- Include header file:
#include "cownerdrawbutton.h"
- Setup icon map of button ID and Icon ID:
static COwnerDrawButton::sIconMap OwnerDrawIconMap[]={
{ IDI_ICONOK, IDOK, COwnerDrawButton::sIconMap::RIGHT },
{ IDI_ICONCANCEL, IDCANCEL, COwnerDrawButton::sIconMap::LEFT },
{ IDI_ICONHELP, IDHELP, COwnerDrawButton::sIconMap::LEFT },
};
- Setup a global variable and reference the map:
COwnerDrawButton OwnerDrawButton(OwnerDrawIconMap, _countof(OwnerDrawIconMap));
- Setup the dialog with:
class CMyDlg : public CDialogEx
{
afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
};
CMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
OwnerDrawButton.SetupButtonsForHWND(GetSafeHwnd(), AfxGetInstanceHandle());
return TRUE;
}
BEGIN_MESSAGE_MAP(CMFCButtonIconsDlg, CDialogEx)
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMyDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
LRESULT lr=OwnerDrawButton.DrawItem(lpDrawItemStruct);
if (lr!=-1) {
return;
}
CDialogEx::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
BOOL CMyDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lr=OwnerDrawButton.CustomDrawItem(reinterpret_cast<LPNMCUSTOMDRAW>(lParam));
if (lr!=-1) {
*pResult=lr;
return TRUE;
}
return CDialogEx::OnNotify(wParam, lParam, pResult);
}
- That's all folks!
History
- 11th November, 2021: Release 1.0
- 13th November 2021: Release 1.01
- Builds in non-unicode (multi-byte) configurations.
- made dll handle / function pointer static to use less memory if using multiple variables.