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:
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)
; else
;
*pResult = 0;
}
void CRegionButtonTestDlg::OnDeltaposVert(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN *pNMUpDown = (NM_UPDOWN *) pNMHDR;
if (0 < pNMUpDown->iDelta)
; else
;
*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.
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;
dwPos = GetMessagePos();
pt.x = LOWORD(dwPos);
pt.y = HIWORD(dwPos);
ScreenToClient(&pt);
for (UINT x = 0; x < m_uRegionCount; x++)
{
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:
Enjoy!