|
Hello Ravi,
Using your class on a dialog I'm creating, causes a compilation error around line 853 announcing that COLOR_HOTLIGHT is not defined.
The cause was that the generated #define WINVER in the Stdafx.h file is set to 0x0400 (The default value VC 7.1 sets when this macro is not set, to allow support for the old versions of windows.), look on Using the Windows Headers[^]
Changing the WINVER to 0x0500 fix the problem. (That is, you need to take care to change this on every new dialog project you create, otherwise VC will set the wrong value to allow backward compatibility to Win98!)
Thanks again for sharing your code.
-- Ricky Marek (AKA: rbid)
-- "Things are only impossible until they are not" --- Jean-Luc Picard
My articles
|
|
|
|
|
rbid wrote:
COLOR_HOTLIGHT is not defined
Thanks, Ricky! The article has been updated.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
Hello Ravi,
Great article.
Looking on the Demo, I saw that the "Static Group Box" corners are "rounded" and it's text is blue (On the MFC projects my VC is generating, the corners are not rounded and the text is black)
How you change this is setup? (I guess it is not related to your control)
Thanks. (<font color=green>myVoteForYourArticle=5;</font> )
-- Ricky Marek (AKA: rbid)
-- "Things are only impossible until they are not" --- Jean-Luc Picard
My articles
|
|
|
|
|
Thanks for your comments (and your vote!)
The XP look is achieved by including the custom resource "24" in the demo app. You can simply copy it from the demo app's .rc file into your app's .rc file to obtain the same result.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
Hello,
I did not find any resource "24" in your demo app.
What I found is that you have a custom RT_MANIFEST resource that points out to a file called 1.bin (which is a *.manifest XML file).
The ID for this resource file is '1'. (I'm not sure if this is legal due that some composite controls like combo-boxes use this ID for their internal use)
Changing the ID to other value than '1' will not show the XP look.
Looking around in the MSDN documentation I found the following:
- Create a manifest file on your application res directory ( E.g.
res\\FooButtonDemo.manifest , just copy/rename the 1.bin file you have, you may open your editor and tailor it to your needs, see the MSDN link below) - In the
res\\FooButtonDemo.rc file add the text:
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "res\\FooButtonDemo.manifest" - Recompile your project
That is, you don't need to create the custom resource and give it the ID=1.
There are other steps required for a real theme support, but these ones were enough to provide the XP look you have.
Look in: Using Windows XP Visual Styles[^]
Remember: The steps above worked for me using WinXP, VC 7.1 (.Net 2003)
Hope that I did not opened a pandora box :->
Thanks for the tip.
-- Ricky Marek (AKA: rbid)
-- "Things are only impossible until they are not" --- Jean-Luc Picard
My articles
|
|
|
|
|
rbid wrote:
did not find any resource "24" in your demo app.
That's how it shows up in VC6. (I haven't yet moved to VC7.1 at home).
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
The article has been updated. See the revision history for details.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
The article has been updated. See the revision history for details.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
How could I use an index value to "check()" the right button in a button group?
For example, have a value, m_type, which will be 0 - 2. My three corresponding button group buttons are c_line, c_spots and c_ignore. Given an index value, and without making an array of buttons (the easy answer ), is there an easy way to check the right group button in your class?
|
|
|
|
|
I use a map to map enums to related controls (they don't have to be FooButton s). Here's a code fragment off the top of my head (uncompiled and untested):
void checkTheRightFooButton (enum Blah theEnum)
{
<code>
m_btnLine.check (false);
m_btnSpots (false);
m_btnIgnore (false);
<code>
CMapPtrToPtr mapEnumToCtrl;
mapEnumToCtrl.SetAt ((void *) c_line, &m_btnLine);
mapEnumToCtrl.SetAt ((void *) c_spots, &m_btnSpots);
mapEnumToCtrl.SetAt ((void *) c_ignore, &m_btnIgnore);
<code>
<code>
void* pTheFooBtn = NULL;
BOOL bMatched = mapEnumToCtrl.Lookup ((void *) theEnum, pTheFooBtn);
if (bMatched) {
ASSERT (pTheFooBtn != NULL);
((FooButton *) pTheFooBtn)->check (true);
}
} /ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
I used some of your FooButton code to add a check-button group to my button class. I put some traces in DrawItem and noticed that it was being called up to 4 times for a check-button-group-style button.
I may be wrong, but at first blush it looks like the Invalidate() call in OnLButtonUp is superfluous, at least for check-buttons, because Invalidate() is already being called in check().
Take care,
Dan.
|
|
|
|
|
It's possible - I'll look into it. Thanks!
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
Let me just say that this is an excellent class and I the quality of your code is top notch. Specifically, the commenting--especially for a class that you would expect people to download and try to learn--is second to none and perfect for this kind of project. I work with some programmers who seem like they have taken a vow of commenting silence, and I'm going to show them your comments as an example. Thanks very much for posting this.
(I was actually going to post a question about how the group-type buttons work, but I think I just figured it out. Thanks again.)
|
|
|
|
|
Thanks for your kind words, Dan.
I try to follow Ravi's Really Simple Rule for Comments, which is:- Remove all code from a method, except the start and end of blocks and your comments.
- Replace all conditional expressions with
(...) .
- If the remaining psuedo-code isn't obvious, you need to add more (or better) comments.
Here's a bad example of commenting:
<code>
Iterator itFoo = fooCollection.getIterator();
while (itFoo.hasMore()) {
Foo* pFoo = itFoo.getNext();
<code>
if (pFoo->isBad())
doSomethingTo (pFoo);
}
which translates to:
while (...) {
}
which doesn't make much sense. Here's the same code with better commenting:
<code>
Iterator itFoo = fooCollection.getIterator();
while (itFoo.hasMore()) {
<code>
Foo* pFoo = itFoo.getNext();
if (pFoo->isBad())
doSomethingTo (pFoo);
}
which translates to:
while (...) {
}
which leaves very little room for doubt.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
I was using this as an "Exit" button and ran into an ASSERT in this code. Here's the fix.
void FooButton::OnLButtonUp (UINT nFlags, CPoint point)<br />
{<br />
<br />
CButton::OnLButtonUp (nFlags, point);<br />
<br />
if (!IsWindow(GetSafeHwnd()))<br />
return;<br />
<br />
m_bMultiClicked = false;<br />
Invalidate();<br />
<br />
}
Overall, this is a great class. Good work!
Peteman_R
Boise, Idaho
|
|
|
|
|
Thanks! I'll be updating the article shortly.
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
Hi,
Thanks for your efforts and this great control!
I tried to use bitmaps where I defined the color RGB( 0, 128, 128) for transparency. If the button will be disabled, it loses its transparency and the background changes to dark grey.
Recreating the bitmaps using the default transparent color RGB(255,0,255) solves the problem, but anyway, I think that this behaviour is a bug.
Michael
|
|
|
|
|
|
Hi. First of all, I'd like to say thank you for contributing your class! It's the only one I could find that had an easy way to hang a menu off the right side, and have an icon.
I was running boundschecker http://www.compuware.com/products/devpartner/bounds.htm[^]
on our application (we run it on all our applications - highly recommended to everyone), and it was reporting some errors in FooButton::DisabledBlt
1) line 1048 --> SelectObject (bwDC, hBitmapBW)
This bitmap is still selected into bwDC when bwDC is deleted. The return value from the the SelectObject call should be replaced before the next } (i.e. between the current lines 1069 and 1070)
2) from the online help for CreateDIBSection:
"Windows NT/ 2000: You need to guarantee that the GDI subsystem has completed any drawing to a bitmap created by CreateDIBSection before you draw to the bitmap yourself. Access to the bitmap must be synchronized. Do this by calling the GdiFlush function. This applies to any use of the pointer to the bitmap's bit values, including passing the pointer in calls to functions such as SetDIBits"
Perhaps a call to GDIFlush should be added?
Warren
|
|
|
|
|
Thanks for your comments, Warren. I stole the DisabledBlt() code from elsewhere. Will update the article when I get back (I'm currently travelling). Thanks again!
/ravi
My new year's resolution: 2048 x 1536
Home | Articles | Freeware | Music
ravib@ravib.com
|
|
|
|
|
I accidentally created a FooButton of a static control. To my surprise the code worked. However, the behavior wasn’t what I expected . The following code in PreSubclassWindow will make sure the control is a button:
#ifdef _DEBUG
TCHAR buffer[255];
GetClassName(m_hWnd, buffer, sizeof(buffer)/sizeof(TCHAR));
ASSERT( CString(buffer) == _T("Button"));
#endif
|
|
|
|
|
|
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 .
|
|
|
|
|