article

MFC, Round Windows and Highlight Buttons

Email
Submitted on: 1/4/2015 10:13:00 AM
By: Alex Rest (from psc cd)  
Level: Intermediate
User Rating: By 11 Users
Compatibility: Microsoft Visual C++
Views: 2978
 
     If you tired to create programs based on standard gray-rectangles Windows interface this article for you. It describes creating windows with original shapes, backgrounds and buttons. The VC++ v6.0 demo project is attached.

This article has accompanying files

 
				


MFC, Round Windows and Highlight Buttons


 

MFC, Round Windows and Highlight Buttons

by Alex Rest

Environment : VC6, VC7, Windows 9x/NT/2000/XP

     If you tired to create programs based on standard gray-rectangles Windows interface this article for you. It describes creating windows with original shapes, backgrounds and buttons. The VC++ v6.0 demo project is attached.

Starting

     The first you must to create bitmap with images what you need. You could use any painter program as Adobe Photoshop, Corel Paint, etc. You must design background image of your window and all buttons images in different conditions. Standard Windows button conditions are normal, pressed, disabled, default. To use highlight condition you need create some additional code. Also you need include your background bmp file into project resources.

Project creating

     In most cases non-standard design is used for Dialog Windows. This project example is based on a Dialog MFC Project. I created the Dialog Project, opened recourse editor, removed title and borders of dialog (using properties menu). Working with such window is easier. If you have title you must calculate its size because the title size is depended from Windows Settings. If you have not title and borders all window rectangle is client aria. Using WM_PAINT message we can draw all over window rectangle.
This demo dialog includes only one button "OK". To repaint button from your code you need switch off parameter "Default Button" and switch on parameter "Owner Draw". You could do it using resource editor.

Window region

     Visible edge of a window could be any shape. The shape is determined by region. CRgn class supports rectangles, round rectangles, elliptical and polygon regions. More than you could combine regions. For example, when I was creating Color Chains game (http://www.brigsoft.com/colorch) I created a window that looks like three round rectangles. In the BSAlarm project (http://www.brigsoft.com/bsalarm) I used combination of round rectangle region and elliptical region.
     I do not change window region during program execution. So I attach region to window before it is shown. The best place to do it is OnCreate() message handler for normal window or OnInitDialog() for dialog window. Use wizard and WM_CREATE (WM_INITDIALOG) messages to insert these functions to your project.
Here is OnInitDialog() from the demo project:

BOOL CRoundWindowDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     VERIFY( SetWindowPos( NULL, 0, 0, m_nW, m_nH, SWP_NOMOVE | SWP_NOOWNERZORDER ) );
      VERIFY( m_WinRgn.CreateEllipticRgn( 0, 0, m_nW, m_nH ) );
      VERIFY( SetWindowRgn(m_WinRgn , TRUE ) );

      m_ExitBtn.Move();

      return TRUE;
}

     I use SetWindowPos to set size of window according background image. It is much easier than make dialog window size using resource editor.
Pay attention to m_WinRgn. The region is a member of CRoundWindowDlg class. So it creates before window creating and removes after window destroying. It is important. Using of local variable for window region could cause an error.

Drawing of background

     You can draw any image into a dialog window as into a plain window using WM_PAINT message or OnPaint() MCF handler. Draw background before other images. The drawing is trivial. Here is a function from the demo project:

void CRoundWindowDlg::OnPaint()
{
     CPaintDC dc(this); // device context for painting

     CBitmap BkBmp, *pOldBmp;
      BkBmp.LoadBitmap(IDB_BKBITMAP);
      CDC BmpDc;
      VERIFY( BmpDc.CreateCompatibleDC(&dc) );
      pOldBmp = (CBitmap *)BmpDc.SelectObject(&BkBmp);
      dc.BitBlt(0,0,m_nW,m_nH,&BmpDc,0,0,SRCCOPY);
      BmpDc.SelectObject(pOldBmp);
}

It is draw background only.

Cool buttons

     The highlight button status is not standard for Windows. So we must catch WM_MOUSEMOVE message and do highlight in our code. To do such functionality I created CColorBtn class that based on CButton. Using Wizard I created button member in demo project and than change CBurtton to CColorBtn in the dialog window class declaration.
Class CColorBtn uses same bitmap recourse as the background window. Button conditions images are placed below dialog window background image. The drawing process is trivial for "Owner Draw" buttons:

void CColorBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{

CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CDC BmpDC;

VERIFY( BmpDC.CreateCompatibleDC(pDC) );
CBitmap tmpBmp;
tmpBmp.LoadBitmap(IDB_BKBITMAP);
CBitmap *pOldBmp = (CBitmap *)BmpDC.SelectObject(&tmpBmp);

switch(m_nStatus){
case Normal:

pDC->BitBlt(lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.top,
lpDrawItemStruct->rcItem.right - lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom -lpDrawItemStruct->rcItem.top,
&BmpDC, m_nBmpX, m_nBmpY, SRCCOPY);
break;
case Light:
pDC->BitBlt(lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.top,
lpDrawItemStruct->rcItem.right - lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom -lpDrawItemStruct->rcItem.top,
&BmpDC, m_nBmpX+m_nW, m_nBmpY, SRCCOPY);
break;
case Pressed:
pDC->BitBlt(lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.top,
lpDrawItemStruct->rcItem.right - lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom -lpDrawItemStruct->rcItem.top,
&BmpDC, m_nBmpX+m_nW*2, m_nBmpY, SRCCOPY);
break;
}

BmpDC.SelectObject(pOldBmp);
}


Value of m_nStatus member is assigned in OnLButtonDown(), OnLButtonUp() and OnMouseMove() functions. OnLButtonDown(), OnLButtonUp() are clear. OnMouseMove() I had implemented so:

void CColorBtn::OnMouseMove(UINT nFlags, CPoint point)
{

if(GetCapture()!=this){
     SetCapture();
}

CRect WinRect(0,0,m_nW, m_nH);

if(WinRect.PtInRect(point)){
      m_nStatus = Light;     
}
else{

m_nStatus = Normal;
if(GetCapture()==this){
bool bRet = ReleaseCapture();
if(!bRet){

      ASSERT(0);

}
}
}

CButton::OnMouseMove(nFlags, point);
Invalidate(FALSE);
UpdateWindow();

}

     I hade captured mouse to determine the moment when mouse pointer left the button shape. Other ways is using TRACKMOUSEEVENT or checking mouse pointer from OnTime(), OnIdle() functions.
     Button coordinates are assigned in the class constructor. Move() function places button window to its coordinates.

Radio Buttons and Check Boxes

     Radio Buttons and Check Boxes have not "Owner Draw" parameter. In this demo project I have not used this buttons. But I used them before.
As any windows this buttons have window procedure. If we catch this procedure we can change any window functionality. We need catch drawing. To do so:
     1) Using GetDlgItem function we could receive the handle of button window(SDK) or pointer to CWnd (MFC);
     2) Using GetWindowLong function we can change window proc function to our code.
     3) Then we draw what we need while process WM_ENABLE, BM_SETSTATE, BM_SETCHECK, WM_PAINT messages. Do not ask me why it so. All questions send to Microsoft;-) I was tracing all button messages to define when drawing took place.

Links

The technique was described in this article I used in the projects:
BSAlarm, http://www.brigsoft.com/bsalarm
Color Chains game, http://www.brigsoft.com/colorch

(C) Alex Rest, 2002

winzip iconDownload article

Note: Due to the size or complexity of this submission, the author has submitted it as a .zip file to shorten your download time. Afterdownloading it, you will need a program like Winzip to decompress it.Virus note:All files are scanned once-a-day by Planet Source Code for viruses, but new viruses come out every day, so no prevention program can catch 100% of them. For your own safety, please:
  1. Re-scan downloaded files using your personal virus checker before using it.
  2. NEVER, EVER run compiled files (.exe's, .ocx's, .dll's etc.)--only run source code.

If you don't have a virus scanner, you can get one at many places on the net including:McAfee.com


Other 2 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments


 There are no comments on this submission.
 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.