I have successfully added support for Windows XP themes to FooButton. It wasn't so hard that I expected it to be . The code I will show here will compile in VC6, and programs should run without problems in other operating systems than Window XP. I have tried on Win98 and had no problems at all. "FOO_USEXPTHEMES" must be defined, otherwise will the code be compiled without support for themes.
The first thing to do is to have some kind of theme manager that loads "uxtheme.dll". There is such class in the article "XP Style CBitmapButton (CHoverBitmapButton)". However, I found some bugs in it but have posted a bugfix in the forum. Apply it, and copy the files "theme.cpp", "theme.h" and "ThemeLib.h" to an project that using FooButton. I will assume that the project is the demo project on this article.
Add the following in "FooButtonDemoDlg.cpp" (the main window):
#ifdef FOO_USEXPTHEMES
#include "theme.h"
CTheme gThemeManager;
#endif
I will use the global variable "gThemeManager". I would prefer with another solution, but this was the easiest I come up to . Anyway, init this in CFooButtonDemoDlg::OnInitDialog() by calling:
#ifdef FOO_USEXPTHEMES
gThemeManager.Init(m_hWnd);
#endif
It is nice if the theme could be changed when the program is running. Add this member function in the class CFooButtonDemoDlg:
#ifdef FOO_USEXPTHEMES
LRESULT CFooButtonDemoDlg::OnThemeChanged(WPARAM, LPARAM)
{
gThemeManager.ThemeChanged(m_hWnd);
return 1;
}
#endif
Add this the message map:
#ifdef FOO_USEXPTHEMES
ON_MESSAGE(0x031A, OnThemeChanged)
#endif
Now it's only some changes to do in the FooButton class . Add the following in the header:
#ifdef FOO_USEXPTHEMES
protected:
CRect GetInternalRect(LPDRAWITEMSTRUCT lpDrawItemStruct);
int GetXPMode(UINT state) const;
#endif
Add this in the file foobutton.cpp. This will give the class access to the global theme manager:
#ifdef FOO_USEXPTHEMES
#include "theme.h"
extern CTheme gThemeManager;
#endif
I declared two new member functions. The code for these is:
#ifdef FOO_USEXPTHEMES
CRect FooButton::GetInternalRect(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CRect rectButton;
GetClientRect (&rectButton);
if(gThemeManager.m_bXPTheme)
{
CRect newRectButton = rectButton;
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
gThemeManager.GetThemeBackgroundContentRect( *pDC,
BP_PUSHBUTTON,
GetXPMode( lpDrawItemStruct->itemState ),
&rectButton,
&newRectButton
);
rectButton = newRectButton;
}
return rectButton;
}
int FooButton::GetXPMode(UINT state) const
{
BOOL bPressed = state & ODS_SELECTED;
BOOL bGotFocus = state & ODS_FOCUS;
BOOL bDisabled = state & ODS_DISABLED;
int iMode=0;
if (m_bTracking)
{
if (bPressed)
{
iMode = PBS_PRESSED;
}
else
{
iMode = PBS_HOT;
}
}
else
{
if (!bGotFocus && !bDisabled)
{
iMode = PBS_NORMAL;
}
if (bDisabled)
{
iMode = PBS_DISABLED;
}
if(IsDefault() && !bDisabled)
iMode |= PBS_DEFAULTED;
}
return iMode;
}
#endif
Button should always be hot tracked if themes are supported. Replace FooButton::OnMouseMove() with the following:
void FooButton::OnMouseMove(UINT nFlags, CPoint point)
{
#ifndef FOO_USEXPTHEMES
if (m_bHot)
#else
{
if (!m_bTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = m_hWnd;
tme.dwFlags = TME_LEAVE;
tme.dwHoverTime = HOVER_DEFAULT;
m_bTracking = _TrackMouseEvent(&tme);
Invalidate();
}
}
#endif
COddButton::OnMouseMove(nFlags, point);
}
Replace FooButton::drawFrame with this:
void FooButton::drawFrame
(CDC* pDC,
LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ASSERT (pDC != NULL);
ASSERT (lpDrawItemStruct != NULL);
if (!m_bStatic && !m_bHyperlink) {
if (m_bHot && !m_bChecked)
drawHotButtonFrame (lpDrawItemStruct);
else
{
#ifdef FOO_USEXPTHEMES
if(gThemeManager.m_bXPTheme)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
int iMode = GetXPMode( lpDrawItemStruct->itemState );
gThemeManager.DrawThemeBackground( *pDC,
&(lpDrawItemStruct->rcItem),
BP_PUSHBUTTON,
iMode);
}
else
#endif
{
UINT uFrameCtrl = DFCS_BUTTONPUSH;
if (m_bChecked)
uFrameCtrl |= DFCS_CHECKED;
else
if (((lpDrawItemStruct->itemState & ODS_SELECTED) == ODS_SELECTED) && !m_bMulti)
uFrameCtrl |= DFCS_PUSHED;
if ((lpDrawItemStruct->itemState & ODS_DISABLED) == ODS_DISABLED)
uFrameCtrl |= DFCS_INACTIVE;
pDC->DrawFrameControl (&(lpDrawItemStruct->rcItem), DFC_BUTTON , uFrameCtrl);
if (m_bMulti && m_bMultiClicked)
drawMultiDropDownRegion (pDC, lpDrawItemStruct);
}
}
}
}
In the function FooButton::drawCaption() I was forced to remove code that calculates the height of the text (couldn't figure out how to do this with themes). However, sense the text a centered vertically I don't think this is necessary. The new function looks like this:
void FooButton::drawCaption
(CDC* pDC,
LPDRAWITEMSTRUCT lpDrawItemStruct,
int nLeftEdge,
int& nRightEdge)
{
nRightEdge = 0;
if (!m_bText)
return;
if (!m_bCenter)
nLeftEdge += C_BitmapBorder_X;
CRect rectButton;
GetClientRect (&rectButton);
nRightEdge = rectButton.right;
if (m_bDropDown)
nRightEdge -= 6;
else
if (m_bMulti) {
nRightEdge -= 12;
}
int nAvailableWidth = nRightEdge - nLeftEdge;
if (nAvailableWidth <= 0)
return;
CString strCaption;
GetWindowText (strCaption);
DWORD dwFormat = DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS;
dwFormat |= (m_bCenter ? DT_CENTER : DT_LEFT);
if (m_bMultiLine)
dwFormat = DT_WORDBREAK | DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER;
CRect rectCaption;
GetClientRect (&rectCaption);
#ifdef FOO_USEXPTHEMES
rectCaption = GetInternalRect(lpDrawItemStruct);
#endif
strCaption.ReleaseBuffer();
int nWidth = rectCaption.Width();
rectCaption.left = nLeftEdge;
rectCaption.right = nRightEdge;
if (m_bCenter && (nAvailableWidth > nWidth)) {
int nOffsetX = (nAvailableWidth - nWidth) / 2;
rectCaption.left += nOffsetX;
rectCaption.right -= nOffsetX;
}
if ((m_type != FooButton::Type::checkBox) && (m_type != FooButton::Type::radio))
if (!m_bStatic && !m_bHyperlink && !m_bMultiClicked)
if (lpDrawItemStruct->itemState & ODS_SELECTED)
rectCaption.OffsetRect (1, 1);
pDC->SetBkMode (TRANSPARENT);
pDC->SetBkColor (::GetSysColor (COLOR_BTNFACE));
if (m_bHyperlink)
pDC->SetTextColor (C_HyperlinkColor);
else
pDC->SetTextColor (::GetSysColor(COLOR_BTNTEXT));
CFont* pOldFont = NULL;
CFont underlineFont;
if (m_bHyperlink) {
LOGFONT logFont;
VERIFY (GetFont()->GetLogFont (&logFont));
logFont.lfUnderline = TRUE;
VERIFY (underlineFont.CreateFontIndirect (&logFont));
pOldFont = pDC->SelectObject (&underlineFont);
}
#ifdef FOO_USEXPTHEMES
if(gThemeManager.m_bXPTheme && !m_bHyperlink)
{
gThemeManager.DrawThemeText( pDC->m_hDC,
BP_PUSHBUTTON,
GetXPMode(lpDrawItemStruct->itemState),
strCaption,
strCaption.GetLength(),
dwFormat,
NULL,
&rectCaption
);
}
else
#endif
{
if ((lpDrawItemStruct->itemState & ODS_DISABLED) == ODS_DISABLED)
{
::OffsetRect (&rectCaption, 1, 1);
::SetTextColor (pDC->m_hDC, ::GetSysColor (COLOR_3DHILIGHT));
::DrawTextEx ( pDC->m_hDC,
strCaption.GetBuffer(0),
strCaption.GetLength(),
&rectCaption,
dwFormat,
NULL);
strCaption.ReleaseBuffer();
::OffsetRect (&rectCaption, -1, -1);
::SetTextColor (pDC->m_hDC, ::GetSysColor (COLOR_GRAYTEXT));
::DrawTextEx (pDC->m_hDC, strCaption.GetBuffer(0), strCaption.GetLength(),
&rectCaption,
dwFormat,
NULL);
}
else
::DrawTextEx (pDC->m_hDC, strCaption.GetBuffer(0), strCaption.GetLength(),
&rectCaption,
dwFormat,
NULL);
}
strCaption.ReleaseBuffer();
if (pOldFont != NULL)
pDC->SelectObject (pOldFont);
}
I did a minor changing in FooButton::drawFocus():
void FooButton::drawFocus
(CDC* pDC,
LPDRAWITEMSTRUCT lpDrawItemStruct,
int nLeftEdge,
int nRightEdge)
{
ASSERT (pDC != NULL);
ASSERT (lpDrawItemStruct != NULL);
if (!m_bStatic && !m_bHot && (getFocusStyle() != FooButton::Focus::noFocus))
if ((lpDrawItemStruct->itemState & ODS_FOCUS) == ODS_FOCUS)
{
CRect rectButton;
#ifdef FOO_USEXPTHEMES
if(gThemeManager.m_bXPTheme)
{
rectButton = GetInternalRect(lpDrawItemStruct);
}
else
#endif
{
GetClientRect (&rectButton);
rectButton.left = nLeftEdge + 4;
rectButton.right = nRightEdge - 4;
rectButton.top += 4;
rectButton.bottom -= 4;
}
::DrawFocusRect (pDC->m_hDC, &rectButton);
}
}
It's no need to draw a default border if themes are activated (this is already done in drawFrame()). So FooButton::drawDefaultBorder() could be replaced with:
void FooButton::drawDefaultBorder
(CDC* pDC,
LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ASSERT (pDC != NULL);
ASSERT (lpDrawItemStruct != NULL);
FooButton::Type btnType = getType();
if ((btnType != FooButton::Type::pushButton) &&
(btnType != FooButton::Type::pushButtonDropDown) &&
(btnType != FooButton::Type::pushButtonMulti))
return;
#ifdef FOO_USEXPTHEMES
if(gThemeManager.m_bXPTheme)
return;
#endif
if (getFocusStyle() != FooButton::Focus::defaultFocus)
return;
if (!IsDefault())
return;
COLORREF rgbBorder = GetSysColor (COLOR_3DDKSHADOW);
CBrush borderBrush (rgbBorder);
CRect innerRect = lpDrawItemStruct->rcItem;
if ((lpDrawItemStruct->itemState & ODS_SELECTED) == 0) {
COLORREF rgbBorder = GetSysColor (COLOR_3DDKSHADOW);
CPen borderGreyPen (PS_SOLID, 1, rgbBorder);
CPen* pOldPen = pDC->SelectObject (&borderGreyPen );
pDC->MoveTo (innerRect.right - 2, innerRect.top + 1);
pDC->LineTo (innerRect.right - 2, innerRect.bottom - 2);
pDC->LineTo (innerRect.left, innerRect.bottom - 2);
COLORREF rgbBrGrey = GetSysColor (COLOR_3DHILIGHT);
CPen brightGreyPen (PS_SOLID, 1, rgbBrGrey);
pOldPen = pDC->SelectObject (&brightGreyPen);
pDC->MoveTo (innerRect.right - 3, innerRect.top + 1);
pDC->LineTo (innerRect.left + 1, innerRect.top + 1);
pDC->LineTo (innerRect.left + 1, innerRect.bottom - 2);
COLORREF rgbDkGrey = GetSysColor (COLOR_BTNSHADOW);
CPen darkGreyPen (PS_SOLID, 1, rgbDkGrey);
pOldPen = pDC->SelectObject (&darkGreyPen);
pDC->MoveTo (innerRect.right - 3, innerRect.top + 2);
pDC->LineTo (innerRect.right - 3, innerRect.bottom - 3);
pDC->LineTo (innerRect.left + 1, innerRect.bottom - 3);
pDC->SelectObject (pOldPen);
} else {
innerRect.InflateRect (1, 1, -1, -1);
pDC->FrameRect (&innerRect, &borderBrush);
}
pDC->FrameRect(&lpDrawItemStruct->rcItem, &borderBrush);
}
I think that is all! However, themes are not fully supported. Radio buttons and check boxes are drawn in the old way, but it shouldn't be hard to fix this. I also think that GetInternalRect() should be used more often. This returns the inner area inside the frame. This could probably be used to place the bitmap more correctly.
And I'm sure that a few minor bugs are added, but the code seems to do what I want it to do .
|