Click here to Skip to main content
15,867,282 members
Articles / Desktop Programming / MFC
Article

XP Style CBitmapButton (CHoverBitmapButton)

Rate me:
Please Sign up or sign in to vote.
4.21/5 (15 votes)
22 Feb 2003CPOL4 min read 233.3K   4.2K   67   24
Style and theme aware bitmaps, give your old apps a new look under Windows XP.

Sample Image - CHoverBitmapButton.jpg

Introduction

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.

What about the bitmaps?

You should start with the same bitmaps as you used in the stock CBitmapButton class, which includes drawing the edge.   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: Image 2 (directU.bmp), and for the XP style, I created a new bitmap: Image 3(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:

StateClassic Windows Style XP Style 
UP"DIRECTU"Image 4"XPDIRECTU"Image 5
DOWN"DIRECTD"Image 6  
FOCUSED"DIRECTF"Image 7  
DISABLED"DIRECTX"Image 8"XPDIRECTX"Image 9

The new XP Style buttons will be drawn using the DLL function DrawThemeBackground that draws the button texture and edges.

Change my style

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.

Includes

Add the line 

#include "ThemeLib.h"

to stdafx.h

Using the class

As has been mentioned, the use is slightly different between a Dialog type app vs. a Doc/View type app.

Dialog type app:

In the main CDialog derived class’s 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 MainFrame.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)

Credits

Thanks to Ewan Ward for the work in his Native Win32 Theme aware Owner-draw Controls without MFC.

License

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


Written By
Web Developer
United States United States
Rail Jon Rogut is a Grammy nominated recording engineer who writes software in his spare time.

Comments and Discussions

 
Generalbug with xp-styles Pin
arkebuzy22-Nov-05 21:40
arkebuzy22-Nov-05 21:40 
GeneralRe: bug with xp-styles Pin
Rail Jon Rogut22-Nov-05 21:51
Rail Jon Rogut22-Nov-05 21:51 
GeneralRe: bug with xp-styles Pin
arkebuzy22-Nov-05 22:03
arkebuzy22-Nov-05 22:03 
QuestionXP-styled CFileDialog Pin
Oleksa Dovbush11-Sep-05 10:50
Oleksa Dovbush11-Sep-05 10:50 
AnswerRe: XP-styled CFileDialog Pin
Rail Jon Rogut11-Sep-05 13:08
Rail Jon Rogut11-Sep-05 13:08 
QuestionRe: XP-styled CFileDialog Pin
Oleksa Dovbush12-Sep-05 0:20
Oleksa Dovbush12-Sep-05 0:20 
AnswerRe: XP-styled CFileDialog Pin
Rail Jon Rogut12-Sep-05 7:16
Rail Jon Rogut12-Sep-05 7:16 
GeneralDefault button Pin
Oleksa Dovbush11-Sep-05 9:48
Oleksa Dovbush11-Sep-05 9:48 
GeneralRe: Default button Pin
Rail Jon Rogut11-Sep-05 9:57
Rail Jon Rogut11-Sep-05 9:57 
QuestionRe: Default button Pin
Oleksa Dovbush11-Sep-05 11:02
Oleksa Dovbush11-Sep-05 11:02 
GeneralA more proper way to see if themes are activated Pin
PEK3-Sep-04 4:22
PEK3-Sep-04 4:22 
I found two bugs in the CTheme class:

1 In my Swedish version of Windows XP the class fails to see if themes are NOT activated. The reason to this that the value "Windows standard" is saved in the registry when themes are not used. The class compares with "Windows Standard" (capital S).
2 It is possible to inactivate themes for a specific application. Right click one the exe, select compability and "Inactive themes" (or something like that). The class doesn’t support this.

The solution to this is to use "IsAppThemed" in UXTHEME.DLL. Changes to do:

Add this in themelib.h:
typedef BOOL (__stdcall *PFNISTHEMEACTIVE)(VOID);


Add this member in theme.h:
PFNISTHEMEACTIVE zIsAppThemed;


Add this in CTheme::CTheme()
zIsAppThemed = NULL;


Replace CTheme::Init() with this:

void CTheme::Init()
{
	m_hModThemes = LoadLibrary(_T("UXTHEME.DLL"));

	if(m_hModThemes)
	{
		zOpenThemeData = (PFNOPENTHEMEDATA)GetProcAddress
				(m_hModThemes, "OpenThemeData");
		zDrawThemeBackground = (PFNDRAWTHEMEBACKGROUND)GetProcAddress
				(m_hModThemes, "DrawThemeBackground");
		zCloseThemeData = (PFNCLOSETHEMEDATA)GetProcAddress
				(m_hModThemes, "CloseThemeData");
		zDrawThemeText = (PFNDRAWTHEMETEXT)GetProcAddress
				(m_hModThemes, "DrawThemeText");
		zGetThemeBackgroundContentRect = (PFNGETTHEMEBACKGROUNDCONTENTRECT)GetProcAddress
				(m_hModThemes, "GetThemeBackgroundContentRect");
		zDrawThemeEdge = (PFNDRAWTHEMEEDGE)GetProcAddress
				(m_hModThemes, "DrawThemeEdge");
		zDrawThemeIcon = (PFNDRAWTHEMEICON)GetProcAddress
				(m_hModThemes, "DrawThemeIcon");

		//Changed by PEK
		zIsAppThemed = (PFNISTHEMEACTIVE)GetProcAddress
				(m_hModThemes, "IsAppThemed");
		
		if(	zOpenThemeData && 
			zDrawThemeBackground && 
			zCloseThemeData && 
			zDrawThemeText && 
			zGetThemeBackgroundContentRect && 
			zDrawThemeEdge && 
			zDrawThemeIcon && 
			zIsAppThemed)
		{
			m_bLibLoaded = TRUE;			
		}
		else
		{
			::FreeLibrary(m_hModThemes);
			m_hModThemes = NULL;
		}
	}

	m_bXPTheme = GetAppearance();
}


And finally replace CTheme::GetApperarance with this:
BOOL	CTheme::GetAppearance(void)
{
	//No library loaded, no themes possible
	if(!m_hModThemes)
		return FALSE;
	
	if(zIsAppThemed != NULL)
		//Ask DLL is theme is used
		return zIsAppThemed();
	
	return FALSE;

	//NOTE! No need to use registry here!
}


Now the class always tries to load the DLL. This is necessary because GetAppearance() will use a function in the DLL if it successely loaded. As you see there is no need to check Window versions or need to check values in the registry.

I also missed two functions, "DrawThemeText" and "GetThemeBackgroundContentRect". Sense DrawThemeText only accepts Unicode strings, I added support for ANSI too:

void	CTheme::DrawThemeText(	HDC dc,
					  int iPartId,
					  int iStateId,
					  LPCSTR pszText,
					  int iCharCount,
					  DWORD dwTextFlags,
					  DWORD dwTextFlags2,
					  const RECT *pRect
					  )
{
	if(m_hTheme && zDrawThemeText)
	{
		//Convert string to unicode
		int length = iCharCount;

		if(iCharCount == -1)
		{
			length = _mbstrlen(pszText);
		}

		//Use api convert routine
		wchar_t* wbuffer = new wchar_t[length+1];
		
		MultiByteToWideChar(	CP_THREAD_ACP,
					0,
					pszText,
					length,
					wbuffer,
					length+1);
		
		wbuffer[length] = '\0';
		
		DrawThemeText(	dc,
				iPartId,
				iStateId,
				wbuffer,
				iCharCount,
				dwTextFlags,
				dwTextFlags2,
				pRect );
		
		delete [] wbuffer;

	}
}

void	CTheme::DrawThemeText(	HDC dc,
				int iPartId,
				int iStateId,
				LPCWSTR pszText,
				int iCharCount,
				DWORD dwTextFlags,
				DWORD dwTextFlags2,
				const RECT *pRect
				 )
{
	if(m_hTheme && zDrawThemeText)
	{
		zDrawThemeText(	m_hTheme,
				dc,
				iPartId,
				iStateId,
				pszText,
				iCharCount,
				dwTextFlags,
				dwTextFlags2,
				pRect );
	}
}

void	CTheme::GetThemeBackgroundContentRect(	HDC dc,
					  int iPartID,
					  int iStateID,
					  const RECT* pBoundingRect,
					  RECT* pContentRect )
{
	if(m_hTheme && zGetThemeBackgroundContentRect)
	{
		zGetThemeBackgroundContentRect(	m_hTheme,
			dc,
			iPartID,
			iStateID,
			pBoundingRect,
			pContentRect
		  );
	}
}


It might also be a good idea to include "#include "themelib.h" in "theme.h" to make it easier to move the files to other projects.

In CHoverBitmapButton::OnDrawItem iMode isn’t initialized and might be used without any value set.

Otherwise, great work! Smile | :)
GeneralA minor Unicode fix Pin
PEK6-Sep-04 9:41
PEK6-Sep-04 9:41 
QuestionHow to use as a checkbox Pin
Erwin Davidse17-Jun-04 4:08
Erwin Davidse17-Jun-04 4:08 
GeneralWindows Version detection BUG (CTheme) Pin
cyril.jean1-Jun-04 22:27
cyril.jean1-Jun-04 22:27 
GeneralTurning into a control Pin
t9mike12-May-03 5:14
t9mike12-May-03 5:14 
Generalfew drawing problems Pin
Tibor Blazko26-Mar-03 22:37
Tibor Blazko26-Mar-03 22:37 
GeneralRe: few drawing problems Pin
Rail Jon Rogut27-Mar-03 8:36
Rail Jon Rogut27-Mar-03 8:36 
GeneralRe: few drawing problems Pin
Tibor Blazko27-Mar-03 23:16
Tibor Blazko27-Mar-03 23:16 
GeneralRe: few drawing problems Pin
Rail Jon Rogut28-Mar-03 7:58
Rail Jon Rogut28-Mar-03 7:58 
GeneralRe: few drawing problems Pin
Rail Jon Rogut28-Mar-03 9:30
Rail Jon Rogut28-Mar-03 9:30 
GeneralRe: few drawing problems Pin
Tibor Blazko30-Mar-03 20:23
Tibor Blazko30-Mar-03 20:23 
GeneralHow to implement this button in oracle developer. Pin
srkhan2119-Apr-04 10:27
srkhan2119-Apr-04 10:27 
GeneralRe: few drawing problems Pin
Stephen C. Steel13-Dec-05 6:45
Stephen C. Steel13-Dec-05 6:45 
GeneralRe: few drawing problems Pin
Rail Jon Rogut13-Dec-05 6:52
Rail Jon Rogut13-Dec-05 6:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.