views:

175

answers:

3

I would like to update some toolbar-like code we have to have a Vista/Win7 gradient roundedness to them.

Currently, the buttons have the Windows 2000 look & feel: blocky, single-tone.

I've played around with the XP themes, and using DrawThemeBackground, DrawThemeEdge, etc.; but I'm very dissatisfied with the theme drawing mechanics (the buttons are large, and the theme draws them as 2-tone, top half and bottom half, which looks okay when the buttons are small - it gives them a halfway decent appearance of being a gradient or having a rounded quality to them. But as large as these buttons are, they look stupid.

Experimenting by simply observing how many of the controls are drawn in various apps and controls, I can see that most of them seem to use gradients - where the top of the control appears a light color and fades to the bottom to a darker color - OR - where it is a lighter color than the background at the top, increases towards near-white at the middle, then fades back to a darker color towards the bottom.

I'm not really sure where to go from here. DrawThemeXXX seem to be inadequate. I don't really want to replace the entire control with a new one that has improved drawing but would require that I swap out some of the code for how the current custom control works, and risk various problems with some other library. I'd rather just have a way to draw arbitrary objects in the style of the current version of Windows that I'm running on. I would have thought that the theme drawing functions would have covered this. But they're fairly brain damaged, as I described.

Can someone point me towards 'How are modern C++ applications supposed to draw custom GUI elements so that they might reasonably expect a graceful appearance under XP, Vista, and Windows 7?'

We use MFC, Gdiplus, and raw Win32 APIs in our code, currently.

Here's to hoping someone knows a great deal about drawing modern GUIs under Windows from C++!

Just so that this isn't a wall of text, here's the current version of the paint handler, which draws the button with an etched border when 'hot-tracking' and both an etched border and the icon + text "depressed" (shifted by 1,1) when in a pressed state:

void CPlacesButton::PaintButton(CDC & dc, CRect & rcClient)
{
 const int kLabelHeight = 8;

 COLORREF clrHiLt = GetSysColor(COLOR_BTNHIGHLIGHT);
 COLORREF clrShdo = GetSysColor(COLOR_BTNSHADOW);
 COLORREF clrText = GetSysColor(COLOR_BTNTEXT);
 COLORREF clrFace = GetSysColor(COLOR_BTNFACE);

 // draw the button's background & border

 if (m_bPressed || m_bDrawPressed || m_bMouseOnButton)
 {
  COLORREF clrDarkened = Darken(clrFace, -0.01f);
  dc.FillRect(rcClient, &CBrush(clrDarkened));

  //dc.Draw3dRect(rcClient, clrShdo, clrHiLt);
  //dc.RoundRect(&rcClient, CPoint(10,10));
  dc.DrawEdge(&rcClient, EDGE_ETCHED, BF_RECT|BF_FLAT);
  //dc.DrawFrameControl(&rcClient, DFC_BUTTON, DFCS_BUTTONPUSH|DFCS_PUSHED);
 }
//  else if (m_bMouseOnButton) // hot draw
//   //dc.Draw3dRect(rcClient, clrShdo, clrHiLt);
//   dc.DrawEdge(&rcClient, EDGE_ETCHED, BF_RECT);
//   //dc.RoundRect(&rcClient, CPoint(10,10));
 else
  dc.FillRect(rcClient, &CBrush(clrFace));

 // use transparent mode for everything that follows
 dc.SetBkMode(TRANSPARENT);

 // center icon
 CPoint ptIcon((rcClient.Width() - m_nIconSize) / 2, ((rcClient.Height() - m_nIconSize) / 2) - kLabelHeight);
 if (m_bPressed || m_bDrawPressed)
  ptIcon.Offset(1, 1);

 // determine the state to draw ourselves in
 const UINT nState = DST_ICON | (IsEnabled() ? DSS_NORMAL : DSS_DISABLED);

 // draw our icon
 dc.DrawState(ptIcon, CSize(m_nIconSize, m_nIconSize), m_hIcon, nState, (HBRUSH)NULL);

 // create & select the font to use for the button's label
 CFont guiFont;
 VERIFY(guiFont.CreateStockObject(DEFAULT_GUI_FONT));
 AutoSelectGDIObject select_font(dc, guiFont);

 // determine clipping rect for label
 CRect rcText(0, ptIcon.y+m_nIconSize+kLabelHeight, rcClient.Width(), ptIcon.y+m_nIconSize+kLabelHeight);
 rcText.InflateRect(0,20);
 if (m_bPressed || m_bDrawPressed)
  rcText.OffsetRect(1, 1);

 dc.SetTextColor(clrText);
 if (IsEnabled())
  dc.DrawText(m_strCaption, rcText, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
 else
  dc.GrayString(NULL, NULL, (LPARAM)(LPCTSTR)m_strCaption, 0, rcText.TopLeft().x, rcText.TopLeft().y, rcText.Width(), rcText.Height());
}

I left some of the commented out variations in the code to indicate some hints as to what other possibilities I've tried. However, they're just a hint, as the complete alternate examples are not present.

+1  A: 

MFC alone isn't exactly skinning friendly. Apart from using another GUI (Qt is great for custom skinning) you can look at solutions like SkinCrafter.

Kornel Kisielewicz
Mordachai
Qt is awesome! Especially with QStyle and http://doc.trolltech.com/4.6/stylesheet.html
ephemient
I was under the impression that QT was more of a framework for the entire app than an add-in for a few custom controls?
Mordachai
I'd really like to help you in this regard, but personally I wouldn't touch MFC with a stick -- I use Delphi or wxWindows for writing gui applications :/
Kornel Kisielewicz
+2  A: 

Actually duplicating the look of the various flavors of Windows is ridiculously difficult, especially if your app can run on more than one version of windows.

I think that they intended to give you the api's to do this back in the Win2k/Win95 days, but then WinXP came along with shading and overlays, and the old API was completely inadequate.

So they came up with the theme stuff, which isn't really even an API so much as an API and a set of graphical primitives all jammed together. But they didn't follow through and allow the set of graphical primitives to be extended or replaced, so themes only works when your controls are a close match to the standard set.

So, for Win9x/Win2k. You use

DrawFrameControl
DrawEdge

For WinXP

DrawTheme

For WinVista/7

DrawTheme
DwmXXX functions
GradientFill ??

Now I suspect that Windows isn't actually using GradientDraw. I suspect it's actually using some DX10 shaders that are built in to the window manager code, but I don't know how to get at that, s I've been using GradientDraw instead. This code will give you a linear fade from the top of the control to the bottom.

INLINE void SetTrivertex(TRIVERTEX & vtx, int x, int y, COLORREF cr)
{
   vtx.x      = x;
   vtx.y      = y;
   vtx.Red    = (SHORT)(GetRValue(cr) * 256L);
   vtx.Green  = (SHORT)(GetGValue(cr) * 256L);
   vtx.Blue   = (SHORT)(GetBValue(cr) * 256L);
   vtx.Alpha  = (SHORT)(255 * 256L);
}

...

  // fill the interior from the top down with a gradient that starts at crTop
  // and ends with the crBottom
  TRIVERTEX vtx[2];
  SetTrivertex (vtx[0], prc->left+1, prc->top+1, crTop);
  SetTrivertex (vtx[1], prc->right-1, prc->bottom-1, crBottom);

  GRADIENT_RECT gRect = { 0, 1 };
  GradientFill(hdc, vtx, 2, &gRect, 1, GRADIENT_FILL_RECT_V); 
John Knoeller
Thanks for the above. I may end up using it, and I may not, but I wasn't aware of it. :)
Mordachai
+4  A: 

You never mentioned the MFC Feature Pack. Have you taken a look at it yet? Download for VS2008, included with VS2008 SP1. The CDrawingManager has lots of special effects. It has great support for application themes.

Hans Passant
I need to take some time and really investigate the feature pack. I do have 2008SP1, so presumably I have it. I just don't know where to begin with the feature pack. Our MFC app is large, and I'm not aware of how easy it is or isn't to 'drop in' the feature pack.
Mordachai