Updated: June 8, 2010
I know you did not love me the first time when I wrote this article because of my non-nativity and non-familiarity with English language and ON_MESSAGE macro implementation which was causing a crash in release mode. I have updated this article and hope you will like it this time. I have added the Source Files and Sample Projects for download separately and the solution in Sample Project is converted to Visual Studio 2008.
Introduction
There is a bunch of articles regarding creating cool buttons on CP and Internet but if you are looking for a 10-minute solution to a problem without having to go through nice, huge and fancy articles, this one is for you.
The way it works is, it assumes that each button has three states, which are 1) Normal, 2) Hover and 3) Down.
Normal: When a button is visible to the user on the screen and mouse has not entered the button region.
Hover: When a button has lit up for the user who has moved the mouse pointer over it.
Down: This is the state when the button is pushed (or clicked), or when the left mouse button is pressed and not release yet.
For each state of a button, it requires an image from the resource and all three images have to be the same size.
Here is the snapshot of how the three states will look like.
Snapshot
How to use?
Create a button on your dialog and set it to be Owner-Drawn. As most of you may know it already that this could be set either from the dialog resource editor or it could be set in run-time.
There is a Class CCoolButton
, derived from CButton, You need to create a member of CCoolButton type for each button in your Dialog class, and use DDX_Control in DoDataExchange to subclass.
Following is the only constructor you may use:
CCoolButton(int nIDNormal, int nIDHover, int nIDDown);
The three arguments are resource IDs, you should specify the bitmap resource ids. .
The Default constructor is private, you must specify the three bitmaps from resources when creating object.
10-minute implementation
Step 1: Download the Source Files from the link on top.
Step 2: Add Fcool.h and Fcool.cpp to your project.
Step 3: For each button
a) Add three (Normal, Hover and Down) images unless some buttons share the look.
b) Add a member of CCoolButton in your CDialog-derived class
c) Change your buttons to Owner-Drawn.
d) In DoDataExchange(), DDX_Control your button resource id with the member object of CCoolButton in your CDialog-derived class.
Step 4: Tell your boss that your work for the week is done :)
How does it works?
All it does it is, it traps the three 1) When the mouse pointer has not entered the region, 2) Mouse pointer has entered the region and 3) Mouse pointer is in the button region and user has pressed the left button.
In constructor, bitmap has been loaded from resource to three CBitmap object aggregated by CCoolButtons.
_TrackMouseEvent() is being used to trap the event when the mouse pointer leaves the button region, I called it "OnLeave" , and defined in msg map, using OnMessage.
When users moves the mouse pointer in the button region, the status of the button changes to Hover and the Custom (overridden) DrawItem invokes, so we draw Hover Image from m_bmpHover member. and it calls _TrackMouseEvent() to try to see if the pointer has left the region or not.
Following is code for OnMouseMove,
void CCoolButton::OnMouseMove(UINT nFlags, CPoint point)
{
CRect ButtonRect;
TRACKMOUSEEVENT tme;
GetWindowRect(&ButtonRect);
if (m_Status == DOWN)
{
if (IsInRect(ButtonRect,point))
{
}
else
{
SetStatus(HOVER);
Invalidate(FALSE);
}
}
else if (m_PrevStatus == DOWN && m_Status == HOVER)
{
SetStatus(DOWN);
}
else
{
if (IsInRect(ButtonRect,point))
{
SetStatus(HOVER);
Invalidate(FALSE);
}
else
{
SetStatus(NORMAL);
}
}
CButton::OnMouseMove(nFlags, point);
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = m_hWnd; _TrackMouseEvent(&tme);
}
Normally, in windows if you look at the normal button, when you press the mouse left button down over it and without releasing move the pointer out of the region, it doesnt spare the button's soul, although it draws the normal picture again but when you move the pointer back in the region (with the left button pressed) it draws the "down" image again and if you release the button while the pointer is in the region, it sends the command. To mimic this behavior I have used the help of SetCapture() and ReleaseCapture() in OnLButtonDown() and OnLButtonUp() respectively.
Following is DrawItem.
void CCoolButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC *pthisDC;
CDC bmpDC;
CBitmap *pOldBitmap;
pthisDC = CDC::FromHandle(lpDrawItemStruct->hDC);
bmpDC.CreateCompatibleDC(pthisDC);
switch (m_Status)
{
case NORMAL:
pOldBitmap = bmpDC.SelectObject(&m_bmpNormal);
break;
case HOVER:
pOldBitmap = bmpDC.SelectObject(&m_bmpHover);
break;
case DOWN:
pOldBitmap = bmpDC.SelectObject(&m_bmpDown);
break;
}
pthisDC->BitBlt(0,0,lpDrawItemStruct->rcItem.right-lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom - lpDrawItemStruct->rcItem.top,&bmpDC,0,0,SRCCOPY);
bmpDC.SelectObject(pOldBitmap);
bmpDC.DeleteDC();
}
Here we go, your button is ready. I would like to know your feedback on this article
Alright! :)
I know its not performance efficient and it could be improved in a lots of ways and I have not done the "disable" button state yet. But again this is for those who are looking for a 10 minutes solution to turn all buttons on their forms to cool buttons.