Button is the only control where we have to struggle or have to take more effort to change its shape and properties. But, it is not that much difficult to change its properties as it seems. The only tough thing is, we need to know how to draw well. A good artist can make a good-looking button. If you know well to play with “Device Caps” you can do more with button designs, that's all. This article is just about the basics of making your own owner drawn irregular shape buttons.
What to do is simple
Here is what you have to do, if you want to develop your own button control. Subclass
CMyButton in my sample), i.e.:
- Go to your workspace, right click on your project name, click on New Class.
- Type in your new class name, select its base class as
- Right click on your class name in the workspace, click on Add Virtual Function.
PreSubclassWindow() virtual functions to your class.
- Modify the style of your button to owner drawn (no problem if already set). In
PreSubclassWindow() function, add the following code:
DrawItem() virtual function comes along with a parameter
lpDrawItemStruct of type
DrawItem() is where your button has been drawn. Refer MSDN for full details of the structure
LPDRAWITEMSTRUCT. Here I am explaining the members needed just to make a simple button.
Defines the drawing action required. This will be one or more of the following bits:
ODA_DRAWENTIRE - This bit is set when the entire control needs to be drawn.
ODA_FOCUS - This bit is set when the control gains or loses input focus. The
itemState member should be checked to determine whether the control has focus.
ODA_SELECT - This bit is set when only the selection status has changed. The
itemState member should be checked to determine the new selection state.
Specifies the visual state of the item after the current drawing action takes place. That is, if a menu item is to be dimmed, the state flag
ODS_GRAYED will be set. The state flags are as follows:
ODS_CHECKED - This bit is set if the menu item is to be checked. This bit is used only in a menu.
ODS_DISABLED - This bit is set if the item is to be drawn as disabled.
ODS_FOCUS - This bit is set if the item has input focus.
ODS_GRAYED - This bit is set if the item is to be dimmed. This bit is used only in a menu.
ODS_SELECTED - This bit is set if the item’s status is selected.
ODS_COMBOBOXEDIT - The drawing takes place in the selection field (edit control) of an ownerdrawn combo box.
ODS_DEFAULT - The item is the default item.
Identifies a device context. This device context must be used when performing drawing operations on the control.
DrawItem Virtual Function
Here is where your drawing is being done. The following is the code snap of the sample:
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
// Construct your buttons region. This wont reflect in your view.
// But you can sense it by clicking the region area.
// Get dc for the button
} // no break; for this case
if(lpDrawItemStruct->itemState & ODS_SELECTED)
// Draw button down state
HRGN r= ::CreateRectRgn(0,0,0,0);
// Draw button caption.
About the Sample
The sample application demonstrates owner drawn irregular shape buttons. You can draw your own button using your mouse, on the dialog. The button will be created in the drawn shape on left button up. Nothing special with this concept. Just the drawn coordinates are collected as path using
EndPath of the CDC. Path is then converted into region. Using that region irregular buttons are created and drawn (
To do so, a DC of the dialog is created as a member function (usually no one will do)
CDC *m_pdc;. This DC is used to create a path while you draw in the dialog. Here is the drawing and path making code:
void CButtonSubDlg::OnLButtonDown(UINT nFlags, CPoint point)
void CButtonSubDlg::OnMouseMove(UINT nFlags, CPoint point)
// Show mouse position
CString mouse; mouse.Format("Irregular Buttons - X:%d Y:%d",point.x,point.y);
void CButtonSubDlg::OnLButtonUp(UINT nFlags, CPoint point)
int res=CombineRgn(rrgn,cRgn.operator HRGN(),0,RGN_COPY);
MessageBox("Null Region::Cannot create region for button");
MessageBox("Error::Cannot create region");
m_but= new CMyButton();
BeginPath() is called in
EndPath() is called in
OnLButtonUp(). This is to grab the path from the drawn freehand drawing.
Following is the code to sense the clicked dynamically created buttons:
BOOL CButtonSubDlg::OnCommand(WPARAM wParam, LPARAM lParam)
CString smsg;smsg.Format("Button %d Clicked",i+1);
return CDialog::OnCommand(wParam, lParam);
Finally delete all dynamically created buttons and the device cap (used to generate the path):
The region generated has been set to the
CMyButton class using the following function:
void CMyButton::SetRgn(CRgn *region)
Region is where this demo application stands at all. The freehand drawing drawn by you will be generated into a path and the path is converted into region, finally a button is created using that region.
SetWindowRgn() function will set the shape for a window as by the given region. In the case of an owner-drawn button
SetWindowRgn() is used to set the region of the button. But, this is not enough to visually set an irregular shaped button. For visual effects, you need to draw the button as per the region.
CombineRgn in this demo is used to copy a region into variables.
RGN_COPY option lets the
CombineRgn function to copy a region into another.
This demo project is of two parts, _SAMPLE1, _SAMPLE2. _SAMPLE1 is the project part defined above. _SAMPLE2 is the code sample from MSDN to change a button's text color. For the _SAMPLE2 project, go to project settings, and change _SAMPLE1 to _SAMPLE2 in the preprocessor definition.
None of the articles can satisfy one's expectations. But, each article should be a seed for your technical growth. Thus, I believe that this would be a seed. Thank you all.