Flat (cool look) buttons (2)

Flat look (cool) toolbar buttons have been well covered. But what about including the same sort of button in a Dialog (or form or dialog bar)? This is not supported by the new common controls, so you have to do it yourself.

Here is a class that does flat-look buttons for you. Just like a toolbar, the buttons get borders when the mouse moves over them. The code uses DrawState to draw the buttons.

This class can be (I'll send further articles when I get some time :-) enhanced to include check-style, optional standard looks, icons as well as text, change cursor as mouse moves over, color change (for links), 3D text, size text to fit etc etc. But this is a good starting point :-)

class CMyFlatButton : public CButton {
     DECLARE_DYNAMIC(CMyFlatButton)
private:
     bool m_bRaised;
     bool m_bDrawBackground;
public:
     CMyFlatButton()
          : CButton()
          , m_bRaised(false)  // internal only
          , m_bDrawBackground(true)      // internal only
     {}

     // drawing
protected:
     virtual void DrawText(CDC* pDC, const CRect& rect, LPCTSTR text, UINT state=0);
     virtual void DrawBorders(CDC* pDC, CRect& rect, UINT state=0);
     virtual void Draw(CDC* pDC, const CRect& rect, UINT state=0);

protected:
     //{{AFX_MSG(CMyFlatButton)
     afx_msg BOOL OnEraseBkgnd(CDC* pDC);
     afx_msg void OnMouseMove(UINT nFlags, CPoint point);
     afx_msg LONG OnMouseLeave(WPARAM, LPARAM);
     //}}AFX_MSG
     //{{AFX_VIRTUAL(CMyFlatButton)
     virtual void PreSubclassWindow();
     virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
     //}}AFX_VIRTUAL
     DECLARE_MESSAGE_MAP();
};

///////////////////////////////////////////////////////////////////////////
//
BEGIN_MESSAGE_MAP(CMyFlatButton, CButton)
//{{AFX_MSG_MAP(CMyFlatButton)
ON_WM_ERASEBKGND()
ON_WM_MOUSEMOVE()
ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
IMPLEMENT_DYNAMIC(CMyFlatButton,CButton)


// this routine uses DrawState to draw the text approriately.
// it automatically centres the text within the button border

void CMyFlatButton::DrawText(CDC* pDC, const CRect& rect, LPCTSTR text, UINT state) {
     // we want transparent text
     int nBkModeOld = pDC->SetBkMode(TRANSPARENT);
     // setup text color
     COLORREF textcol = ::GetSysColor(COLOR_BTNTEXT);
     COLORREF oldTextColor = pDC->SetTextColor(textcol);
     // centre the text
     CSize textSizeClient = pDC->GetTextExtent(text,strlen(text));
     int x = rect.left+(rect.Width()-textSizeClient.cx)/2;
     int y = rect.top+(rect.Height()-textSizeClient.cy)/2;
     // draw the text
     pDC->DrawState(CPoint(x,y), rect.Size(), text,
          (state & ODS_DISABLED?DSS_DISABLED:DSS_NORMAL), true, 0, (HBRUSH)NULL
          );
     // restore dc
     pDC->SetTextColor(oldTextColor);
     pDC->SetBkMode(nBkModeOld);
}


// this routine (optionally) draws the borders on the button
// flat buttons use only single-pixel borders, so we cannot use
// DrawFrameControl.

void CMyFlatButton::DrawBorders(CDC* pDC, CRect& rect,UINT state) {
     if (state & ODS_SELECTED) {
          pDC->Draw3dRect(rect,::GetSysColor(COLOR_3DSHADOW),
                               ::GetSysColor(COLOR_3DHILIGHT));
          rect.DeflateRect(1,1);
     } else if (! m_bRaised && ! (state & ODS_FOCUS)) {
          // no border - flat look
          rect.DeflateRect(1,1);
     } else {

          pDC->Draw3dRect(rect,::GetSysColor(COLOR_3DHILIGHT),
                               ::GetSysColor(COLOR_3DSHADOW));
          rect.DeflateRect(1,1);
     }
     rect.DeflateRect(1,1);
     if (state & ODS_SELECTED) {
          rect.OffsetRect(1,1);    // offset image when pressed
     }
}


// Here we draw the appropriate parts of the button

void CMyFlatButton::Draw(CDC* pDC, const CRect& inrect, UINT state) {
     CRect rect = inrect;
     DrawBorders(pDC,rect,state);
     CString text;
     GetWindowText(text);
     if (! text.IsEmpty()) {
          DrawText(pDC,rect,text,state);
     }
}

// Overrides
void CMyFlatButton::PreSubclassWindow() {
     SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW | BS_NOTIFY);
}


// we use m_bDrawBackground flag to allow us to
// repaint behind the button before drawing on top

void CMyFlatButton::DrawItem(LPDRAWITEMSTRUCT lpDIS) {
     if (m_bDrawBackground) {
          m_bDrawBackground = false;
          // if not redrawing the whole thing,
          CWnd* pParent = GetParent();
          CRect rect; GetWindowRect(rect);
          pParent->ScreenToClient(rect);
          pParent->InvalidateRect(rect);
          pParent->UpdateWindow();
     } else {
          m_bDrawBackground = true;
          CDC* pDC = CDC::FromHandle(lpDIS->hDC);
          ASSERT_VALID(pDC);
          CRect rectClient = lpDIS->rcItem;
          Draw(pDC,rectClient,lpDIS->itemState);
     }
}

// Messages
BOOL CMyFlatButton::OnEraseBkgnd(CDC*) {
     return true;    // we don't do any erasing when owner drawn
}


// the m_bRaised flag inidicates whether or not to
// draw the borders.  If the mouse is over the button
// the borders are drawn.  We use TrackMouseEvent to
// detect when the mouse leaves the button so we can
// turn off the flag

void CMyFlatButton::OnMouseMove(UINT, CPoint) {
     if (! m_bRaised) {
          // draw with button borders
          m_bRaised = true;
          Invalidate();
          // remember to remove button borders when we leave
          TRACKMOUSEEVENT trackmouseevent;
          trackmouseevent.cbSize = sizeof(trackmouseevent);
          trackmouseevent.dwFlags = TME_LEAVE;
          trackmouseevent.hwndTrack = GetSafeHwnd();
          trackmouseevent.dwHoverTime = HOVER_DEFAULT;
          _TrackMouseEvent(&trackmouseevent);
     }
}

LONG CMyFlatButton::OnMouseLeave(WPARAM, LPARAM) {
     // remove button borders
     m_bRaised = false;
     Invalidate();
     return 0;
}

Last updated: 13 May 1998

Comments:



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • When it comes to desktops – physical or virtual – it's all about the applications. Cloud-hosted virtual desktops are growing fast because you get local data center-class security and 24x7 access with the complete personalization and flexibility of your own desktop. Organizations make five common mistakes when it comes to planning and implementing their application management strategy. This eBook tells you what they are and how to avoid them, and offers real-life case studies on customers who didn't …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds