Introduction
Here is a simple calculator custom control which you can easily re-use in your application. Simple to re-use, not necessarily to write!
There are no resource file dependencies, you can size the calculator freely in Visual Studio's resource editor, and you can change the fonts and results window text and background colors.
I will try to divide this article into 2:
- Usage: "I need a calculator control in my application - just give me the minimum information on how to use it. As long as it works, I don't care how it was written".
- Analysis of any interesting coding or other features associated with writing an MFC custom control. I shall try to cover the key challenges, problems and gotchas which I experienced along the way.
Usage
Using the MFC Calculator control in your application is easy:
- Add the files CalculatorCtrl.cpp and CalculatorCtrl.h to your project.
- In the Visual Studio .NET resource editor, add a Custom Control object to your dialog of the required calculator size. The control will 'spread' at run time to fill the space you allocate in the resource editor.
- Set the 'Class' field in the object's Properties page in the resource editor to "MFC CalculatorCtrl".
- Create a member variable of type
CCalculatorCtrl
in your dialog class by right-clicking the object in resource editor and selecting 'Add variable' from the popup menu.
- In your dialog's
OnInitDialog()
, change any calculator defaults using the public member functions described below.
- Remember to call
CCalculatorCtrl::SetDblEqualsReturns(TRUE)
if you want your dialog to be notified when the user has pressed '=' twice (to signify they've finished with the calculator and wish to use the result in your application). Then add a message handler for the message _MSG_CALCULATOR_EQUALS
to your dialog class by adding something like the following to your dialog's message map: ON_MESSAGE(_MSG_CALCULATOR_EQUALS, OnCalculatorEquals)
Either way, the current answer value from the calculator control can be 'got' at any time via a call to CCalculatorCtrl::GetCalculatorValue()
.
The control works with mouse-clicks and the keyboard. The keystrokes are the same as Windows Calculator, other than hitting equals a second time after the answer has been calculated to signify that the user wants to use the result of the calculation in your application.
Public Functions
Most of these functions are used in the sample program's dialog class:
void SetDblEqualsReturns(BOOL bDblEqualsReturns)
If bDblEqualsReturns
is TRUE
then control will send a custom message, _MSG_CALCULATOR_EQUALS
, to the dialog if the user hits '=' a second time after completing their calculation. (This is implemented in the sample program with a messagebox acknowledgement from the dialog class.)
The default if you do not call this function is FALSE
- i.e., double equals does not send a message.
Call this function in your dialog's OnInitDialog()
function.
BOOL GetDblEqualsReturns(void)
Returns the current double equals status (see SetDblEqualsReturns()
).
void SetNumFormat(LPCTSTR strNumFormat)
Sets the (double) floating point numeric format string used to display numbers in the calculator's results window. Call this function in the dialog's OnInitDialog()
function, and before you call CCalculatorCtrl::SetCalculatorValue()
.
If you do not call this function, the default format string will be used - "%f" which gives accuracy to 6 decimal places.
void SetCalculatorValue(double fValue);
Sets the initial value of the calculator to fValue
.
double GetCalculatorValue(void);
Returns the current answer from the calculator. Call in your handler for the _MSG_CALCULATOR_EQUALS
message.
void SetResultsWndFont(LOGFONT* pLogFont);
void GetResultWndFont(LOGFONT* pLogFont);
Sets / gets the font used for the results window in the calculator, based on the LOGFONT
structure passed or returned. Call this function in the dialog's OnInitDialog()
function.
void SetButtonFont(LOGFONT* pLogFont);
void GetButtonFont(LOGFONT* pLogFont);
Sets / gets the font used for the calculator buttons, based on the LOGFONT
structure passed or returned. Call this function in the dialog's OnInitDialog()
function.
COLORREF SetResultsWndTxtClr(COLORREF rgbNewColour);
Sets the text color of the calculator's results window. Call this function in the dialog's OnInitDialog()
function.
COLORREF SetResultsWndBkClr(COLORREF rgbNewColour);
Sets the background color of the calculator's results window.
Background
There are other articles on CodeProject which go into more detail about the basics of writing an MFC custom control, and I'm not going to repeat the basics here. See Designing a Windows Control - Part 1 by Andreas Saurwein, and Creating Custom Controls by Chris Maunder.
Here are some points of interest in the code which I have not found covered in the above treatments:
Using Windows controls without a dialog resource
The design goal was to make this control simple to deploy. Whilst it is possible to copy resources between projects, it is clumsy and untidy; so in this case, the controls are created, sized, and laid out by the control's PreSubclassWindow()
member function. Here we get the available space using GetWindowPlacement()
, and create and lay the controls out programmatically.
There are 25 CButton
s in the control and a CEdit
. Rather than creating each one individually, you will see that the buttons are created in a loop. The IDs of each button are set to the constant NUMBERBUTTONS_BASEID
(defined at the top of the CPP file) plus the loop counter variable.
Instead of individual message handlers for each button, I used the ON_COMMAND_RANGE
macro to implement a single handler function (OnCalcButtonPressed()
) to handle messages from the buttons and translate them into calculator behavior.
Keystroke handling in the control
Neither of the articles above covers keyboard handling, which the calculator control needs.
Implementing it was not as trivial as I'd hoped. I implemented a handler for ON_WM_KEYDOWN()
where I converted the virtual keys received into CButton
IDs and then called my OnCalcButtonPressed()
member function.
In addition, it is necessary to add a handler for ON_WM_GETDLGCODE
, as otherwise keystrokes do not get 'used up' in the control and the Windows 'Default Beep' sound gets emitted each time a key is pressed.
To resolve this, in the ON_WM_GETDLGCODE
handler, we do not call the CWnd
default implementation, but instead just return DLGC_WANTALLKEYS
:
UINT CCalculatorCtrl::OnGetDlgCode()
{
return DLGC_WANTALLKEYS;
}
Finally, we need to trap the ESCAPE and RETURN keys, as ESCAPE is the calculator 'CE/C' keyboard equivalent and RETURN is the 'equals' button. If we don't specifically address this issue, these keys will get passed to the dialog and will most likely ONOK
or ONCANCEL
the dialog.
To ensure ESCAPE and RETURN aren't passed up the chain, we implement the following PreTranslateMessage
handler:
BOOL CCalculatorCtrl::PreTranslateMessage(MSG* pMsg)
{
if( pMsg->message == WM_KEYDOWN )
{
if(pMsg->wParam == VK_RETURN
|| pMsg->wParam == VK_ESCAPE )
{
::TranslateMessage(pMsg);
::DispatchMessage(pMsg);
return TRUE;
}
}
return CWnd::PreTranslateMessage(pMsg);
}
I guess some of this is standard fare for any custom control which needs to implement keyboard handling, but it was new for me and I couldn't find too much to help on Code Project or the net, so hopefully some of this will help!
Conclusion
Whether or not the code here is elegant, it seems to work and provides a quick plug-in component for any MFC application. I hope you find it useful.
If you like the control or find this article interesting, I'd be really grateful if you could help me by:
- Voting for the article - I need all the encouragement I can get!
- Please visit my web site and read about my Web Update Wizard product - a low cost component which lets you add 'update over the web' functionality to your application with one line of code.
- If you have any interest in sales forecasting software (or know someone who does!) please look at my other web site, where you can see what I get up to most of my time - developing Prophecy, a sales forecasting application.
Thank you for reading my article.