Introduction
Stated loosely, writing MFC console applications help us to better gain experience with the MFC utility classes and the C++ class concepts in a familiar setting. Rather than creating a dialog-based, SDI, or an MDI application, this paper will focus on the C++ constructs that are used in MFC. The reason for this is that if we were to truly explain how windows are created in MFC, the reader’s focus could leave the “class” and look out the window. A Microsoft Foundation Classes (MFC) window is a hybrid of C++ and Windows API calls. In effect, an MFC window gives you a C++ wrapper to much (but not all) of the Windows API, but an MFC window does not have any more direct control over a window object than you do in the API world. That is, if you can’t do something in the API world, then you can’t do it in the MFC world. Creating an MFC window can be tricky, as you first create an instance of CWnd
to then call a member function of CWnd
, which calls CreateWindow()
in the API. CreateWindow()
is a function contained in user32.dll. The returned window handle (which is just an indirect pointer to the window object) is stored in the CWnd
member variable m_hWnd
. But, take care to note that because qindows are created in memory that is constantly being churned, its address may constantly change. Therefore, a window handle, instead of pointing directly to the window object, points to another pointer that keeps track of the window object location. So far, this sounds complicated and unnecessary, but there is a strong logic to it. In order to shed light on how to use MFC, I will develop a simple console program by writing a simple class called the CIndicator
.
An indicator shows the status of something. When we are told that a flag is set, then we understand that a flag is really an algorithm to indicate the status of an operation, as indicated by a bit setting. Indicators can be gauges, thermometers, speedometers, and so on. The minimum and maximum values on these various types of indicators can therefore vary widely. A circular gauge such as a direction indicator would have values that may vary from 0 to 360; for an odometer from 0.0 to 99,999.9. So let’s try a fuel gauge. This will require an Indicator.h file that not only contains the predefined functions and so forth but also acts as an interface for the implementation of the CIndicator
class.
The Default Constructor and Default Parameters
By definition, the default constructor in the interface (header) file is the constructor that is to be invoked when no parameters are specified. Normally, the declaration of the default constructor could look like this:
public:
CIndicator::CIndicator();
CIndicator::CIndicator()
: mMaxLimit(100.0),
mCurValue(0.0),
mMinLimit(0.0)
{
}
MFC makes use of default parameters in many of its functions, so it’s a good idea to try and see how we might use the default parameters in the constructor:
CIndicator FuelGauge();
CIndicator DirectionIndicator(359.9)
CIndicator FuelGauge(25.0, 10.0)
CIndicator TempGauge(225.0, 50.0, 31.0)
If an invocation of a function does not have the full complement of parameters, the default parameter values starting from the right side are used. Here is the Indicator.h header file:
#include <afxwin.h >
#include <iostream > // notice that we do not use the .h file extension
using namespace std;
#if !defined(AFX_INDICATOR_H__9BA950E2_5320_11D3_B00B_ECA1B8D52B36__INCLUDED_)
#define AFX_INDICATOR_H__9BA950E2_5320_11D3_B00B_ECA1B8D52B36__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif
class CIndicator
{
private:
double mMaxLimit;
double mCurValue;
double mMinLimit;
public:
CIndicator(double MaxLimit = 100.0,
double startValue = 0.0,
double MinLimit = 0.0);
~CIndicator();
CIndicator (const CIndicator& ind);
void Display (CString s) const;
double SetValue (double value);
double GetValue () const;
BOOL IncreaseBy(double value = 1.0);
BOOL DecreaseBy(double value = 1.0);
};
#endif
And, here is the Indicator.cpp file, which is the implementation of the CIndicator
class. Note that this is an important distinction in COM programming. An implementation of an interface does not necessarily mean the implementation of a class.
#include "Indicator.h"
CIndicator::CIndicator(double MaxLimit,
double startValue,
double MinLimit)
: mMaxLimit(MaxLimit),
mCurValue(startValue),
mMinLimit (MinLimit)
{
if ((mMinLimit <= mCurValue) && (mCurValue <= mMaxLimit))
{
}
else
{
mMaxLimit = 100.0;
mCurValue = 0.0;
mMinLimit = 0.0;
}
cout << "\n Construct Indicator";
}
CIndicator::CIndicator (const CIndicator& ind)
: mMaxLimit (ind.mMaxLimit),
mCurValue (ind.mCurValue),
mMinLimit (ind.mMinLimit)
{
cout << "\n Copy construct Indicator";
}
CIndicator::~CIndicator()
{ cout << "\n Destruct Indicator"; }
void CIndicator::Display(CString prefix) const
{ CString s;
s.Format (s+" Current value:%4.1f Max. limit:%4.1f"
" Min. limit: %4.1f",
mCurValue, mMaxLimit, mMinLimit);
cout << prefix << s;
}
double CIndicator::SetValue (double value)
{ double temp = mCurValue;
mCurValue = value;
return temp;
}
double CIndicator::GetValue() const
{ return mCurValue; }
BOOL CIndicator::IncreaseBy (double value)
{ if (mMaxLimit < mCurValue + value)
return FALSE;
else
{ mCurValue = mCurValue + value;
return TRUE;
}
}
BOOL CIndicator::DecreaseBy (double value)
{ if (mCurValue + value < mMinLimit)
return FALSE;
else
{ mCurValue = mCurValue - value;
return TRUE;
}
}
Here is the main.cpp program that builds the solution. Notice that I use the getc(stdin)
function on the bottom to force the console window to remain after running this project. In fact, to build using Visual Studio, you must first start a new C++ Win32 Console project that has the “empty project” check box checked in the application settings box. Then, go to the configuration properties and ensure that “Use MFC as a Shared DLL” is checked. The code already references the Standard Template Library, but now the project can behave and be built as an MFC project. From there, add the three new items: the header file, and the two source code files.
#include <afxwin.h>
#include <iostream >
#include "Indicator.h"
void DoSomething (CIndicator x)
{
x.Display("\nIn DoSomething:");
return;
}
int main()
{
CString prefix("\nFuel gauge:");
CIndicator FuelGauge(10.0, 3.0);
FuelGauge.Display(prefix);
FuelGauge.IncreaseBy(2.0);
FuelGauge.Display(prefix);
FuelGauge.DecreaseBy(1.4);
FuelGauge.Display(prefix);
CIndicator Temp;
Temp = FuelGauge;
Temp.Display("\nValue of Temp: ");
DoSomething (FuelGauge);
getc(stdin);
return 0;
}
The header file and the two source files will do it in an empty Win32 console project that has the "Use MFC in a Shared DLL" property set in the project's settings. MFC is a powerful framework that can get complicated, and sometimes can leave any application or business logic to the programmer. It is different from the good old .NET Framework, but if we take the time to learn it, we can use MFC when it is actually the best framework for certain situations.