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.
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:
Either way, the current answer value from the calculator control can be 'got' at any time via a call to
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.
Most of these functions are used in the sample program's dialog class:
void SetDblEqualsReturns(BOOL bDblEqualsReturns)
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
Returns the current double equals status (see
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
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
Returns the current answer from the calculator. Call in your handler for the
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
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
COLORREF SetResultsWndTxtClr(COLORREF rgbNewColour);
Sets the text color of the calculator's results window. Call this function in the dialog's
COLORREF SetResultsWndBkClr(COLORREF rgbNewColour);
Sets the background color of the calculator's results window.
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
CButtons 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
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
ONCANCEL the dialog.
To ensure ESCAPE and RETURN aren't passed up the chain, we implement the following
BOOL CCalculatorCtrl::PreTranslateMessage(MSG* pMsg)
if( pMsg->message == WM_KEYDOWN )
if(pMsg->wParam == VK_RETURN
|| pMsg->wParam == VK_ESCAPE )
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!
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.
Software designer and developer of Prophecy, a multi-user solution for sales forecasting and the Software Update Wizard, an automatic updates component that lets software developers add automatic updates to their Windows applications with a single line of code and which supports automatic updates, even where the logged in user does not have (write) rights to Program Files etc. folders.
I started working life as full time sales forecaster in the CPG sector. Transitioned to IT as I had an interest in software development and felt combining this with my previous business experience would be good.
After many years working for a company I developed Prophecy as a spare-time project and became self-employed in 1998, with Prophecy and the Software Update Wizard as my sole sources of income. It's been an interesting and challenging ride!