Introduction
I needed a button for a skinnable user-interface, where the user could change the visual aspects. My first idea, a button that is skinned by bitmaps, was a little unhandy for my interface, so I searched for a button that could draw itself using an internal formula and some user-given parameters. A long story told short, I found no class that fitted my needs, so I started coding.
My first attempt was a disaster ;-). An application using about 15 buttons started in 10 secs - because the buttons needed the time to be drawn. The second attempt was much better. The time critical graphics are drawn once in a universal way and are then used by the buttons to draw their own images. With this structure it was also possible to generate a global button-style for the complete project.
Introducing the used classes and structs
CRoundButton2
needs two classes: "CRoundButton2
" and "CRoundButtonStyle
". Each button needs an object of type "CRoundButton2
". This class represents the button and its functionality. Only one object (OK, OK, one per used button-style) is needed of type "CRoundButtonStyle
". This object controls the style of all buttons using it. Let's start with it...
CRoundButtonStyle
CRoundButtonStyle
has some public functions, but the two that are of interest to us besides the constructor and the destructor are:
Both functions need a pointer to an object of type tButtonStyle
.
tButtonStyle
tButtonStyle
is a structure containing all changeable options of the style. The image shows the main parameters of the button-style:
The button's geometry is calculated by the shown function.
The parameters are given in detail:
double m_dSizeAA
The size in pixels of the used Anti-Aliasing zone to make the image smooth.
double m_dRadius
The radius of a button-edge. If the button is too small for the selected radius, it is scaled down, but be careful, the scaling routine is not very good. If you have to use very small buttons along with big ones, use a second button-scale which is different only in this parameter.
double m_dBorderRatio
The ratio (between 0.0 and 1.0) between the border and the radius. If you are given a radius of 16.0 and m_dBorderRatio
is 0.25, the button's border would be 4.0 pixels wide.
double m_dHeightBorder
The color of the border is calculated by the function y=x^2, with this height. You have to experiment a little to find a good value for your button.
double m_dHeightButton
The color of the button is calculated by the function y=x^2, with this height. You have to experiment a little to find a good value for your button.
double m_dHighLightX
The position of the button's highlight in X-direction. Use this and the next value to change the 3D-effect.
double m_dHighLightY
The position of the button's highlight in X-direction. Use this and the previous value to change the 3D-effect.
double m_dRadiusHighLight
The radius of the used highlight. You have to experiment a little to find a good looking value.
double m_dPowerHighLight
The power of the used highlight. More of this and the highlighted area turns to white ;-)
tColorScheme m_tColorBack
This is the color for the button's background. Because the button doesn't draw itself transparent, you can give the color of your dialog face here, so the button looks round rather than rectangular.
tColorScheme m_tColorBorder
This is the color shown at the highest point of the button's border.
tColorScheme m_tColorFace
This is the color shown on the button's face, the area in the middle of the button.
tColorScheme
The coloring of the button is done by this structure, which includes colors for all different states of the button.
These are given in detail here:
COLORREF m_tDisabled
This color is used if the button is disabled.
COLORREF m_tEnabled
This color is used if the button is enabled, but not clicked.
COLORREF m_tClicked
This color is used if the button is clicked. This means checked, if the button is a checkbox or selected if the button is a radio button.
COLORREF m_tPressed
This color is used if the button is pressed. This means, the mouse is over the button and the left mouse button is clicked.
COLORREF m_tHot
This color is used if the button is a hot-button and the mouse pointer is hovering over the button.
So each button uses an object of class....
CRoundButton2
This class includes some Set
and Get
routines which are needed to control the behavior of the button. These are given in detail here:
bool SetRoundButtonStyle(CRoundButtonStyle* _ptRoundButtonStyle)
This routine sets a pointer in the button object to the global button-style object. With this, you can control which button uses which button-style.
bool GetFont(LOGFONT* _ptLogFont)
You have to provide the address of an object of type LOGFONT
, which is filled by the function with the actual set font-data. LOGFONT
is the structure used by MFC's CreateFontIndirect
.
bool SetFont(LOGFONT* _ptLogFont)
You have to provide the address of an object of type LOGFONT
, which is used by the function to set the actual font of the button. LOGFONT
is the structure used by MFC's CreateFontIndirect
.
bool GetTextColor(tColorScheme* _ptTextColor)
This routine fills the object at _ptTextColor
with the current set color of the button's text. This color is given as tColorScheme
, so you can use a different color for a disabled button, or you can mark a checked button by another font color.
bool GetTextColor(tColorScheme* _ptTextColor)
This routine sets the font color of the button to the values given in _pttextColor
. This color is given as tColorScheme
, so you can use a different color for a disabled button, or you can mark a checked button by another font color.
void SetCheckButton(bool _bCheckButton)
Changes the status of the button to check button or back. If _bCheckbutton
is true
, the button is a check button. With each mouse click, the checked status is changed.
bool GetCheckButton()
Returns true
if the button is a check button, otherwise false
.
void SetRadioButton(bool _bRadioButton)
Changes the status of the button to radio button or back. If _bRadiobutton
is true
, the button is a radio button. Each mouse click will check a radio-button. The un-checking of other radio buttons of the group has to be done manually (for now). (See an example on this later.)
bool GetRadioButton()
Returns true
if the button is a radio button, otherwise false
.
void SetHotButton(bool _bHotButton)
Changes the status of the button to hot-button or back. If _bRadiobutton
is true
, the button is a hot-button. If the mouse hovers over the button's face, the button will change its appearance to the hot-button style so it is more noticeable.
bool GetHotButton()
Returns true
if the button is a hot-button, otherwise false
.
void SetCheck(bool _bIsChecked)
Checks or unchecks the button.
bool GetCheck()
Get the actual check-status of the button. true
means the button is checked.
Using the code
First of all, you have to include these four files in your project:
- RoundButton2.h
- RoundButton2.cpp
- RoundButtonStyle.h
- RoundButtonStyle.cpp
The easiest way to use the code is to generate variables for each used button. The first step to do this: go to your dialog's header-file and include:
.
.
.
#include "..."
#include "RoundButton2.h"
#include "RoundButtonStyle.h"
class CMyDialog : public CDialog
{
.
.
.
The next step would be to go to the dialog-editor. First we need a button-style to use. Right-click onto the dialog-title and select "Add variable...". In the dialog, change the variable type to "CRoundButtonStyle
" and give a name to the variable. This example uses m_tMyButtonStyle
. Press "OK" and your first global button-style is generated.
Now, we need a variable for each used button. To get them, follow these steps for each button:
Right-click on the button you want to change and select "Add variable...". In the upcoming dialog, select your variable-name (this example uses m_tMyButton1
and so on) and change the variable-type to CRoundButton2
. Press "OK" and the button's variable is included.
The last step needed is to associate the style with the buttons. Go to your dialog's OnInitDialog()
routine and set the following line for each button:
BOOL CMyDialog::OnInitDialog()
{
.
.
.
All other initialization
m_tMyButton1.SetRoundButtonStyle(&m_tMyButtonStyle);
m_tMyButton2.SetRoundButtonStyle(&m_tMyButtonStyle);
m_tMyButton3.SetRoundButtonStyle(&m_tMyButtonStyle);
m_tMyButton4.SetRoundButtonStyle(&m_tMyButtonStyle);
m_tMyButton5.SetRoundButtonStyle(&m_tMyButtonStyle);
return TRUE
}
Now you can use your button like any other button from MFC.
Checkbox
If you want to use your button as a checkbox, you simply have to call...
BOOL CMyDialog::OnInitDialog()
{
.
.
.
All other initialization
m_tMyButton1.SetCheckButton(true);
return TRUE
}
...in your OnInitDialog()
routine. The button will change the check-status with each click or you can change the status yourself with a call to void SetCheck(bool)
. You can get the actual check-status by calling bool GetCheck()
.
Radio button
A radio button is a little more complicated to implement. Let's pretend, for this example, that you want to use "MyButton2" to "MyButton4" as a radio button group. First, you have to set the button's status to "radio button" by using the following code:
BOOL CMyDialog::OnInitDialog()
{
.
.
.
All other initialization
m_tMyButton2.SetRadioButton(true);
m_tMyButton3.SetRadioButton(true);
m_tMyButton4.SetRadioButton(true);
return TRUE
}
A radio button is "checked" each time, the user clicks on it. You have to uncheck the unselected buttons of a radio button group by yourself. To do this, implement the OnBnClicked()
function for each button of the radio button group. These functions include the following code:
void CMyDialog::OnBnClickedMyButton2()
{
m_tMyButton3.SetCheck(false);
m_tMyButton4.SetCheck(false);
}
void CMyDialog::OnBnClickedMyButton3()
{
m_tMyButton2.SetCheck(false);
m_tMyButton4.SetCheck(false);
}
void CMyDialog::OnBnClickedMyButton4()
{
m_tMyButton2.SetCheck(false);
m_tMyButton3.SetCheck(false);
}
This will uncheck all the buttons of your radio button group that were not clicked.
Hot Button
To make a button hot, you have to call...
BOOL CMyDialog::OnInitDialog()
{
.
.
.
All other initialization
m_tMyButton5.SetHotButton(true);
return TRUE
}
... in your OnInitDialog()
routine. A hot button will change its appearance if the mouse cursor hovers over the button
Redesign of the button
You have two possible parts in redesign. The first is the...
ButtonStyle
To redesign the style of the button you have to follow this simple concept:
tButtonStyle tStyle;
m_tMyButtonStyle.GetRoundButtonStyle(&tStyle);
tStyle.m_dRadius = 16.0;
tStyle.m_tColorFace.m_tChecked = RGB(0xFF, 0xFF, 0);
m_tMyButtonStyle.SetRoundButtonStyle(&tStyle);
Now, you have changed the button style, so all the buttons using this style will have a changed look.
The second style to change is the...
FontStyle
The FontStyle
is a part of the CRoundButton2
class, because it's more often needed to change the font of a button than its graphical details. Think for example of a button with a symbol on it, like the tall thin button in the demo application. It uses a double arrow of "WingDings". This example shows the changing of both the font and the font-color of a button's caption.
LOGFONT tFont;
m_tMyButton1.GetFont(&tFont);
strcpy(tFont.lfFaceName,"WingDings");
tFont.lfHeight = 14;
m_tMyButton1.SetFont(&tStyle);
tColorScheme tColor;
m_tMyButton1.GetFontColor(&tColor);
tColor.m_tChecked = RGB(0xFF, 0xFF, 0);
m_tMyButton1.SetFontColor(&tColor);
History
- 12.09.2005 - Initial release.
- 29.09.2005 - Added
HotButton
-function (Thanks to AndrewSmirnov) and fixed two bugs (Thanks to Thomas Kayser and Bhalvinder).