MFC, Round Windows, and Highlight Buttons

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

If you've tried to create programs based on the 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 Project

The first thing you must do is create a bitmap with images that you need. You could use any painter program, as Adobe Photoshop, Corel Photo-Paint, and so forth. You must design the background image of your window and all buttons' images in different conditions. The standard Windows button conditions are normal, pressed, disabled, and default. To use the highlight condition, you need to create some additional code. Also, you need to include your background bmp file into your project resources.

Creating the Project

In most cases, a non-standard design is used for dialog windows. This project example is based on a Dialog MFC Project. I created the Dialog Project, opened the recourse editor, and removed the title and borders of dialog (by using the Properties menu). Working with such a window is easier. If you have a title, you must calculate its size because the title size is dependent on your Windows settings. If you do not have a title and borders, all of the window's rectangle is the client area. By using the WM_PAINT message, we can draw all over the window's rectangle.

This demo dialog includes only one button, "OK." To repaint the button from your code, you need switch off the "Default Button" parameter and switch on the "Owner Draw" parameter. You could do it by using the resource editor.

Designing the Window Region

The visible edge of a window could be any shape. The shape is determined by its region. The CRgn class supports rectangles, round rectangles, elliptical, and polygon regions. More than that, you could combine regions. For example, when I was creating the Color Chains game (http://www.brigsoft.com/colorch), I created a window that looks like three rounded rectangles. In the BSAlarm project (http://www.brigsoft.com/bsalarm), I used a combination of round rectangle regions and an elliptical region.

I do not change the window region during program execution, so I attach the region to the window before it is shown. The best place to do it is in the OnCreate() message handler for a normal window or OnInitDialog() for a dialog window. Use the wizard and the WM_CREATE (WM_INITDIALOG) messages to insert these functions into 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 the window size according to the background image. It is much easier than making the dialog window size using the resource editor. Pay attention to m_WinRgn. The region is a member of the CRoundWindowDlg class, so it is created before the window is created and removes itself after the window is destroyed. It is important. Using a local variable for the window region could cause an error.

Drawing the Background

You can draw any image into a dialog window as into a plain window by using the WM_PAINT message or the OnPaint() MCF handler. Draw the background before drawing 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 only for drawing the background.

Cool Buttons

The highlight button status is not standard for Windows. So we must catch the WM_MOUSEMOVE message and highlight it in our code. To do so, I created the CColorBtn class that's based on CButton. Using the wizard, I created a button member in the demo project and then changed CBurtton to CColorBtn in the dialog window class declaration. The CColorBtn class uses the same bitmap recourse as the background window. Button conditions images are placed below the 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);

}

The value of the m_nStatus member is assigned in the OnLButtonDown(), OnLButtonUp(), and OnMouseMove() functions. OnLButtonDown() and OnLButtonUp() are clear. I had already implemented OnMouseMove(), so the following code was used:

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 had captured the mouse to determine the moment when the mouse pointer left the button shape. Other ways of doing this are using TRACKMOUSEEVENT or checking the mouse pointer from the OnTime() or OnIdle() functions.

Button coordinates are assigned in the class constructor. The Move() function places the button window to its coordinates.

Radio Buttons and Check Boxes

Radio buttons and check boxes do not have the "Owner Draw" parameter. In this demo project, I have not used these buttons, but I have used them before. As with any window, these buttons have a window procedure. If we catch this procedure, we can change any window's functionality. We need catch the drawing. To do so, use these steps:

  1. By using the GetDlgItem function, we could receive the handle of the button window (SDK) or pointer to CWnd (MFC).
  2. By using the GetWindowLong function, we can change the window proc function to our code.
  3. Then we draw what we need while processing the WM_ENABLE, BM_SETSTATE, BM_SETCHECK, and WM_PAINT messages. Do not ask me why it is so. All questions should be sent to Microsoft. I was tracing all button messages to define when drawing took place.

Links

I used the technique that was described in this article in the following projects:

© Alex Rest, 2002

Downloads

Download demo project - 38 Kb


Comments

  • Ved at bruge monster beats hovedtelefoner er bedre end andre

    Posted by dsjupk732 on 07/18/2013 02:40am

    Hvis du interesserer lyden, hvordan man kan lytte til din musik, så vil du elske deBeats by Dr. Dre solo højtydende hovedtelefoner. Uanset om du er fagfolk eller entusiaster, de er den perfekte høj kvalitet at lytte til dig igen, hvis du interesserer lyd, hvordan du kan lytte til din musik på farten, så vil du elske beat af Dr. Dre Solo high-performance hovedtelefoner. Uanset om du er fagfolk eller entusiaster, de er perfekte, høj kvalitet lytter til dig på vejen. [url=http://beatsbydretilbud.webspawner.com/]beats by dre[/url] Dr. Dre Beats by Dre hovedtelefoner tendens til at være så dygtige i mange hovedtelefoner producerer. Blandt det store udvalg af Monster Beatshovedtelefoner med næsten alle håndsæt kan individuelt kunne være Fahion trend med blanding, med fremragende fordele. De behøver ikke nødvendigvis giver et individ af Dr. Dre hovedtelefoner som de to sider. [urlhttp://beatsbydredanmark.webspawner.com/]Beats by Dre[/url] BIII booo din troede involverer let jazz, med essentials involverer normal, god ole ‘, sammen med digitalkameraer sange for dig at lave en ny specifikt eksklusiv lyd. BIII øjeblikket omdefinerer private musik ekspertise til at gøre dine musikernes øretelefoner. Enhver observere, hver eneste nuance, kan optaget med aldrig-hørt-før realistisk look, plus iøjnefaldende design og stil trådløse høretelefoner bestemt ved en ny trompet mundstykke har som en installation vidnesbyrd om den mestre kunsten. Disse former for sædvanligvis er ikke kun de bedste ørepuder vedrørende jazz. Uanset hvad nogen hører, vil BIII sikkert vokset til betragtes som en af dine nuværende all-time musikalske teknologi højder.

    Reply
  • How to add more than one buttons on the demo project ?

    Posted by James on 09/12/2012 07:50am

    Download the demo source, tried and it works great. However, is there a way to add more buttons ?

    Reply
  • It's great

    Posted by Legacy on 05/09/2003 12:00am

    Originally posted by: SzadQ

    I was seenig many programs that do the same, but your is the best - short and quicly. Thanks!

    Reply
  • I think using WindowFromPoint would be a better idea

    Posted by Legacy on 10/14/2002 12:00am

    Originally posted by: Rich

    If you are making round buttons it's weird using a CRect to see if the mouse has left the button area.
    
    

    I think using WindowFromPoint would be a better idea

    i.e.

    OnMouseMove(UINT nFlags, CPoint point)
    {
    POINT p2 = point;
    ClientToScreen(&p2);
    CWnd* wndUnderMouse = WindowFromPoint(p2);
    if (!wndUnderMouse || wndUnderMouse->m_hWnd != this->m_hWnd)
    {
    // Mouse has left the button code
    m_nStatus = Normal;
    if(GetCapture()==this)
    {
    bool bRet = ReleaseCapture();
    if(!bRet)
    {
    ASSERT(0);
    }
    }
    }
    else
    {
    m_nStatus = Light;
    }
    CWnd::OnMouseMove(nFlags, point);
    }

    but I'm sure there will be another way

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

Top White Papers and Webcasts

  • Flash technology is becoming more prominent in the storage industry. Offering superior speed and reliability when compared to traditional hard disk drives – flash storage is a flexible and increasingly cost-effective technology that can be used to optimize enterprise storage environments. This ebook explores the many uses and benefits of flash storage technology in the enterprise. Check it out to discover and learn all you need to: Optimize storage performance Leverage server flash as storage cache …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds