XP Style CBitmapButton (CHoverBitmapButton)

I was in the middle of updating a program that I’d developed under NT and Win2K and I’d used CBitmapButton throughout the program. When porting over a new version of the program to run under XP, I noticed that my “old” bitmap buttons in VC7 weren’t style aware... and made the app look a tad dated. A search on the web brought my attention to a couple of other examples of hover buttons... but none which took my investment with CBitmapButton into consideration. I wanted a CBitmapButton derived class that I could just plug into my older app. This is the first article I’ve submitted here... so excuse my prose.
What’s my
Theme?
Using
UXTHEME.DLL, as I found in an example
project by Ewan Ward.. I created a new CTheme class. If you prefer, you can use the WTL CTheme
class... however, I just created one
that’s suitable for my purpose.
If you’re creating an MFC
Doc/View application, then in CmainFrame declare a CTheme member m_theme, if it’s a dialog based app – in the Cdialog derived
main window declare a CTheme member m_theme:
CTheme m_theme;
In a Doc/View app, in CmainFrame::OnCreate(), or in a Dialog based app in OnInitDialog() add:
m_theme.Init(m_hWnd);
The
function CTheme::Init() calls a function GetAppearance()to see if the style is XP Style or Windows Classic
Style. Actually, it just checks if the
current OS is XP, and if the Classic Style is selected.
BOOL CTheme::GetAppearance(void)
{
// For XP -
Detect if the Window Style is Classic or XP
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
if
(osvi.dwMajorVersion < 5) // Earlier than XP
return
FALSE;
/////////////////////////////////////////////////////
HKEY hKey;
CString szSubKey = _T("Control
Panel\\Appearance");
CString szCurrent
= _T("Current");
DWORD dwSize
= 200;
unsigned char * pBuffer = new unsigned char[dwSize];
memset(pBuffer, 0, dwSize);
if
(RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)szSubKey, 0L, KEY_READ, &hKey) !=
ERROR_SUCCESS)
{
// Can't
find it
delete
[]pBuffer;
return
FALSE;
}
RegQueryValueEx(hKey, szCurrent, NULL, NULL,
pBuffer, &dwSize);
RegCloseKey(hKey);
szCurrent = pBuffer;
delete
[]pBuffer;
if
(szCurrent == _T("Windows Standard"))
return
FALSE;
return
TRUE;
}
If
CTheme::Init() is never called, then CHoverBitmapButton will default to using
the older button style. If the style is
XP Style, then the DLL is loaded.
You
should start with the same bitmaps as you used in the stock CBitmapButton
class.. which includes drawing the edges…
For the XP style buttons, create (or copy) your old button bitmaps with
a white background – you don’t have to draw the edges as you did with the old
CBitmapButton. For example, I had a
button:
(directU.bmp), and for the XP style, I
created a new bitmap:
(xpdirectU.bmp). For the older style I had F, X and D bitmaps
as well... for the XP style, I only needed the U and X bitmaps. I also named the resources the same – except
I prefixed the new XP style resource names with the letters XP. This is important for the CHoverBitmapButton
class to differentiate between the XP and Classic style bitmaps. You may set the prefix yourself using the
public function CHoverBitmapButton::SetPrefix()
– or just use the default.
The bitmaps are loaded the
same as before with either CHoverBitmapButton::Load() or
CHoverBitmapButton::AutoLoad().
In the example Dialog
application – in OnInitDialog() :
m_CtrlButton.Load(IDC_DIRECT, this, &m_theme);
In
a Doc/View application you could use the normal:
m_CtrlButton.AutoLoad(IDC_DIRECT, this);
and
in the CHoverBitmapButton constructor,
m_pTheme can be initialized using a pointer to the variable m_theme via
CHoverBitmapButton ::GetFrame().
So
for the 2 styles – the bitmap resources would be named:
|
State |
Classic
Windows Style |
|
XP Style |
|
|
UP |
“DIRECTU” |
|
“XPDIRECTU” |
|
|
DOWN |
“DIRECTD” |
|
|
|
|
FOCUSED |
“DIRECTF” |
|
|
|
|
DISABLED |
“DIRECTX” |
|
“XPDIRECTX” |
|
The
new XP Style buttons will be drawn using the DLL function DrawThemeBackground that draws the button texture and
edges.
To
handle the message WM_THEMECHANGED, you have to set WINVER, _WIN32_WINNT, _WIN32_WINDOWS and _WIN32_IE all
equal to 0x0501 in stdafx.h
If you don’t want to detect the
changes, then you could have an app that runs under previous versions of
Windows... but if your only target is XP… then the example includes the code to
handle the WM_THEMECHANGED
message.
Add
the line
#include "ThemeLib.h"
to
stdafx.h
As
has been mentioned, the use is slightly different between a Dialog type app vs.
a Doc/View type app.
CDialog type app:
In
the main CDialog derived class’ header file:
#include
"theme.h"
#include "HoverBitmapButton.h"
and
add a public class member:
CTheme m_theme;
Change
the CButton controls that you added with ClassWizard to:
CHoverBitmapButton m_CtrlButton;
And
in OnInitDialog:
m_theme.Init(m_hWnd);
m_CtrlButton.Load(IDC_DIRECT, this, &m_theme);
Doc/View type app:
In CMainFrame.h:
#include "theme.h"
and
add a public class member:
CTheme m_theme;
And
in CMainFrame::OnCreate():
m_theme.Init(m_hWnd);
In
a CDialog derived class which has a button, in the header file:
#include
"HoverBitmapButton.h"
Change
the CButton controls that you added with ClassWizard to:
CHoverBitmapButton m_CtrlButton;
And
in OnInitDialog:
m_ CtrlButton.AutoLoad(IDC_DIRECTORY, this);
Code changes required:
For
Doc/View, change the code in HoverBitmapButton.cpp where the comments show
Doc/View.
In
the creator:
CHoverBitmapButton::CHoverBitmapButton()
{
m_bHovering = FALSE;
// remove the comments from these 2
lines:
m_pTheme =
&(GetFrame()->m_theme); // If Doc/View m_theme is
// a member of CMainFrame
m_bXPTheme = m_pTheme->m_bXPTheme;
m_szXPPrefix = _T("XP");
m_bThemeChanging = FALSE;
}
In
the Header and the code file, uncomment the function
CMainFrame*
CHoverBitmapButton::GetFrame(void)
Thanks
to Ewan Ward for the work in his Native
Win32 Theme aware Owner-draw Controls without MFC.
Rail
Jon Rogut is a Grammy nominated recording engineer who also writes software in
his spare time.