Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CRegionButton - A multidirectional button

0.00/5 (No votes)
17 May 2004 1  
A class for making a button appear as though it has many regions.

Introduction

This article is a brief discussion on how you would go about creating a button class that acts as a multidirectional button. Most buttons perform a task when clicked. No matter where you click on the button, the task is still the same. If the task happened to be something like moving an object in the viewport in some direction (e.g., left), you'd need one button per direction. Do-able, but a bit awkward.

A slight improvement might be to use two spin button controls, one with the UDS_HORZ style and then the other with the UDS_VERT style. These two controls can be placed atop each other, producing something like:

Sample Image - RegionButton1.jpg

While not incorrect, there are several drawbacks to this approach. The first is that the controls do not overlap cleanly. It's plain to see that one control is actually obscuring the other. This might be considered a minor annoyance at best.

Second, each control requires its own CSpinButtonCtrl variable, which also means that two UDN_DELTAPOS handlers will be needed. These handlers might look like:

void CRegionButtonTestDlg::OnDeltaposHorz(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_UPDOWN *pNMUpDown = (NM_UPDOWN *) pNMHDR;

    if (0 < pNMUpDown->iDelta)
        ; // left

    else
        ; // right

 
    *pResult = 0;
}

void CRegionButtonTestDlg::OnDeltaposVert(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_UPDOWN *pNMUpDown = (NM_UPDOWN *) pNMHDR;

    if (0 < pNMUpDown->iDelta)
        ; // down

    else
        ; // up


    *pResult = 0;
}

If you needed a change in one handler, chances are that you'd also need it in the other. Duplicating code is rarely fun.

A better solution would be to use one control that acted differently depending on where it was clicked. A button fills this need pretty well.

The class I created, CRegionButton is derived from CBitmapButton and is very easy to use. It has two private member variables, and one public method. The two member variables are to keep track of the different regions on the button. It currently works with 4 or 9 regions, but other numbers will work as long as they are a perfect square (e.g., 16, 25).

The public method, CalculateRegions(), is really where all the "magic" takes place. It should be called sometime after LoadBitmaps(). This method will create an array of CRect objects that represent the different regions of the bitmap.

In the case of a 4-region button, the button would be equally divided into four regions numbered 0 through 3.

Sample Image - RegionButton3.jpg

So, if the bitmap used for the button was 80x80 pixels, region 0 of the button would be l=0, t=0, r=39, and b=39.

Once the regions have been calculated, now it's just a matter of detecting mouse clicks with a BN_CLICKED handler. The code for that looks like:

void CRegionButton::OnClicked() 
{
    CPoint  pt;
    DWORD   dwPos;
    
    // where was the mouse clicked

    dwPos = GetMessagePos();

    // get the X/Y screen coordinates

    pt.x = LOWORD(dwPos);
    pt.y = HIWORD(dwPos);

    // convert them to client coordinates

    ScreenToClient(&pt);

    // see if the mouse click is within any of the regions

    for (UINT x = 0; x < m_uRegionCount; x++)
    {
        // if so, send a message to the parent

        // including the region that was clicked

        if (m_pRegion[x].PtInRect(pt) != FALSE)
        {
            GetParent()->SendMessage(UDM_REGION_CLICKED, x);
            break;
        }
    }
}

The message that is sent to the parent is a registered message created by calling RegisterWindowMessage(). You can see the implementation of this message in the class' .h file. Notice that the region that was clicked is sent as the WPARAM parameter.

At this point, the only thing left to do is respond to the message. An ON_REGISTERED_MESSAGE() entry will need to be added to the dialog's message map. The code for that looks like:

LRESULT CRegionButtonTestDlg::OnRegionClicked( WPARAM wParam, LPARAM lParam )
{
    CString     str;

    str.Format("You clicked in region %u", wParam);

    return (0);
}

Now, we have but one method that gets called no matter where we click on the button, but also tells where upon the button we clicked. When in use, this button might look like:

Sample Image - RegionButton2.jpg

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here