|
|
If the button is a hyperlink and the mouse pointer is over it when the button is destroyed (like when the user press escape) DestroyCursor fails in the destructor.
My suggestion is to have this code in OnSetCursor:
BOOL FooButton::OnSetCursor (CWnd* , UINT , UINT )
{
if(m_bHyperlink)
{
HCURSOR cursor = LoadCursor(NULL, MAKEINTRESOURCE(32649) );
if(cursor)
::SetCursor( cursor );
}
return FALSE;
}
This will try to load the standard hand cursor instead of using the cursor in "winhlp32.exe". When doing this I don't think it's necessary to destroy the cursor (but I'm not totally sure). The good thing with this is that the user could decide which cursor to use. The bad thing is that in some operating systems will the arrow be used instead. However, this will only happens in Windows 95 and NT, which I think acceptable. The hand cursor will be used in Windows 98.
I have tried this code in Window 95, 98 and NT and it works as expected.
|
|
|
|
|
Thanks, as always! Will update the article soon.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
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 .
|
|
|
|
|
I had the function FooButton::OnThemeChanged in my code yesterday, but that function isn't necessary. I have edited the previous message and removed this code.
|
|
|
|
|
Thanks for the feedback! Am looking into your mods.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
I accidentally draw the default frame on buttons that where disabled, but that is not how normal buttons act. I have updated the code (I simply check that bDisabled is not true before setting default frame in GetXPMode).
Another bug was that hyperlinks wasn't correctly drawn when themes was used. I solved this by drawing hyperlinks in the traditional way (that is, not using DrawThemeText).
|
|
|
|
|
The system color wasn't used to draw text caption. The code is updated.
|
|
|
|
|
Nice!
Some improvement:
void FooButton::DisabledBlt
(HDC hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
HBITMAP hBitmap,
int nXSrc,
int nYSrc)
{
ASSERT (hdcDest && hBitmap);
ASSERT (nWidth > 0 && nHeight > 0);
HDC hDC = CreateCompatibleDC (hdcDest);
ASSERT (hDC);
if (hDC) {
HDC bwDC = CreateCompatibleDC(hDC);
ASSERT (bwDC);
if (bwDC) {
struct
{
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[2];
} RGBBWBITMAPINFO = {
{
sizeof(BITMAPINFOHEADER),
nWidth,
nHeight,
1,
1,
BI_RGB,
0,
0,
0,
0,
0
},
{
{ 0x00, 0x00, 0x00, 0x00 },
{ 0xFF, 0xFF, 0xFF, 0x00 }
}
};
void* pbitsBW;
HBITMAP hBitmapBW = CreateDIBSection (bwDC,
(LPBITMAPINFO) &RGBBWBITMAPINFO,
DIB_RGB_COLORS, &pbitsBW, NULL, 0);
ASSERT (hBitmapBW);
if (hBitmapBW) {
SelectObject (bwDC, hBitmapBW);
SelectObject (hDC, hBitmap);
BitBlt (bwDC, 0, 0, nWidth, nHeight, hDC, nXSrc, nYSrc, SRCCOPY);
#ifdef FOO_USEXPTHEMES
if (!gThemeManager.m_bXPTheme)
#endif
FillRect (hdcDest,
CRect (nXDest, nYDest, nXDest + nWidth, nYDest + nHeight),
GetSysColorBrush (COLOR_3DFACE));
HBRUSH hb = CreateSolidBrush (GetSysColor (COLOR_3DHILIGHT));
HBRUSH oldBrush = (HBRUSH) SelectObject (hdcDest, hb);
BitBlt (hdcDest, nXDest + 1, nYDest + 1, nWidth, nHeight, bwDC, 0, 0, 0xB8074A);
hb = CreateSolidBrush (GetSysColor (COLOR_3DSHADOW));
DeleteObject (SelectObject(hdcDest, hb));
BitBlt (hdcDest, nXDest, nYDest, nWidth, nHeight, bwDC, 0, 0, 0xB8074A);
DeleteObject (SelectObject (hdcDest, oldBrush));
}
VERIFY (DeleteDC (bwDC));
}
VERIFY (DeleteDC(hDC));
}
}
You don't need to draw gray phone when XP themes are used.
|
|
|
|
|
These are the last three lines in OnLButtonUp:
CButton::OnLButtonUp (nFlags, point);
m_bMultiClicked = false;
Invalidate();
In one of my programs the button is destroyed when it is clicked. This means that the button exist when CButton::OnLButtonUp (nFlags, point) executes, but it's destroyed when next line run. Crash!
Now I have changed the order of the lines, and it seems works fine for me .
|
|
|
|
|
Good point - thanks! I'll update the code. Will also add a call to force a repaint immediately, eg:
m_bMultiClicked = false;
Invalidate();
UpdateWindow();
CButton::OnLButtonUp (nFlags, point);
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
How to enable XP themes support for this class?
If you add manifest file it does not help
|
|
|
|
|
Unfortunately, it currently doesn't support XP themes.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
So... good project, but need some improvement.
|
|
|
|
|
Agreed - there's always room for improvement!
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
This article's forum indexes seem to be screwed up.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
I downloaded your second verison of the demo, and during the compilation I discovered that it does not compile on my system (WinXP Prof.) with error message that COLOR_HOTLIGHT is not defined. After looking into MSDN I found that COLOR_HOTLIGHT is not defined for NT and 95, without any other explanation for XP. After changing the line to something different, just to try to recompile, it worked fine. (I changed it temporarily to "rgbPen = GetSysColor(COLOR_HIGHLIGHT);".
Have a good one!
Amer Gerzic
|
|
|
|
|
Perhaps you need to install the latest version of the Platform SDK? My copy of winuser.h defines it as:
#if(WINVER >= 0x0500)
#define COLOR_HOTLIGHT 26
... /ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
Hi,
The button in the demo responds to the shortcut key 'F', and also responds to ALT+F, CTRL+F, SHIFT+F. This default behavior is embedded somewhere in CButton class or in its base class.
Do you know how to make the button respond only to a particular combination of keys, e.g. CTRL+F?
It would be a nice feature in FooButton.
gyar
|
|
|
|
|
I think you can control this by overriding PreTranslateMessage() and ignoring the Alt+F and Shift+F keystrokes.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
this feature is amazing but there is one "-". then pressing button as in "standart situation", it does not draws as pushed inside.. (like CHECKED state)
but awesome..
----------------------------
never stop coding.
|
|
|
|
|
Actually multi buttons behave differently in Outlook and IE. I happened to choose the IE route, although one could argue that the Outlook behavior is more appealing/intuitive. Hmm, looks like we might need another FooButton property...
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
Your way of disabling "unreferenced var warnings" is not perfect. You can simply comment out variable names in the function definition to achive the same effect. Don't modify the headers, just .cpp files. See example below.
BOOL FooButton::OnSetCursor (CWnd* /*pWnd*/, UINT /*nHitTest*/, UINT /*message*/)
{
// Don't need the code below any more
// pWnd = pWnd;
// nHitTest = nHitTest;
// message = message;
}
Also, how does your control work with XP visual styles?
Andrew
|
|
|
|
|
AndrewSmirnov wrote:
simply comment out variable names in the function definition
Good point - thanks! I'll update the code soon.
AndrewSmirnov wrote:
does your control work with XP visual styles
No, although it uses CDC::DrawFrameControl() which may honor some aspects of the current theme (unknown).
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
I do the following to prevent "unreferenced var warning"
BOOL FooButton::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
(void)pWnd;
(void)nHitTest;
(void)message;
}
Regards,
Simon Hughes
|
|
|
|
|