Introduction
An object-oriented program may be viewed as a collection of cooperating objects, as opposed to the conventional model, in which a program is seen as a list of tasks (subroutines) to perform. In OOP, each object is capable of receiving messages, processing data, and sending messages to other objects, and can be viewed as an independent 'machine' with a distinct role or responsibility. The actions (or "operators") on these objects are closely associated with the object. For example, the data structures tend to carry their own operators around with them (or at least "inherit" them from a similar object or class). The Microsoft Foundation Class Library (also Microsoft Foundation Classes or MFC) is a library that wraps portions of the Windows API in C++ classes, including functionality that enables them to use a default application framework. Classes are defined for many of the handle-managed Windows objects and also for predefined Windows and common controls. The Windows API is Microsoft's core set of application programming interfaces (APIs) available in the Microsoft Windows Operating Systems. It was formerly called the Win32 API; however, the name Windows API more accurately reflects its roots in 16-bit Windows and its support on 64-bit Windows. Almost all Windows programs interact with the Windows API. An MFC window is a hybrid of C++ and Windows API calls. In effect, an MFC window gives you a C++ wrapper over a lot (but not all) of the Windows API. This article is an attempt to (at least) describe the basic tenets of object-oriented programming - polymorphism, inheritance, and encapsulation - via the MFC framework. Thus this paper will begin with a basic C++ program that defines a class, determines whether members of the class are public, private, or protected, amongst the use of constructors and destructors.
Underlying Principles
If you are a .NET developer, then you probably know all types are derived from the root class System.Object
. This means that the following two definitions are equal:
class Employee {
. . .
}
class Employee : System.Object {
. . .
}
The System.Object
namespace class is comprised of four public methods -- Equals
, GetHashCode
, ToString
, and GetType
. In addition, Object
has two protected methods: MemberWiseClone
and Finalize
. Because all types ultimately derive from System.Object
, you are guaranteed that every object of every type inherits these members. But this is managed code, not MFC. In fact, objects that are allocated on the heap are tracked to see if they are out of scope, as there is no managed heap. Here is a C++ program that demonstrates the use of classes and their members:
#include <iostream>
using std::cout;
using std::endl;
enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
class Mammal
{
public:
Mammal():itsAge(2), itsWeight(5){}
~Mammal(){}
int GetAge() const { return itsAge; }
void SetAge(int age) { itsAge = age; }
int GetWeight() const { return itsWeight; }
void SetWeight(int weight) { itsWeight = weight; }
void Speak()const { cout << "Mammal sound!\n"; }
void Sleep()const { cout << "shhh. I'm sleeping.\n"; }
protected:
int itsAge;
int itsWeight;
};
class Dog : public Mammal
{
public:
Dog():itsBreed(GOLDEN){}
~Dog(){}
BREED GetBreed() const { return itsBreed; }
void SetBreed(BREED breed) { itsBreed = breed; }
void WagTail() const { cout << "Tail wagging...\n"; }
void BegForFood() const { cout << "Begging for food...\n"; }
private:
BREED itsBreed;
};
int main()
{
Dog Fido;
Fido.Speak();
Fido.WagTail();
cout << "Fido is " << Fido.GetAge() << " years old" << endl;
return 0;
}
Notice that the base class is called Mammal
. This class "encapsulates" its data and defines a member method called Speak()
. Now notice that another class is defined called Dog
. The colon marks indicate that this class derives from the Mammal
class. This means that all of the methods defined in Mammal
are accessible to Dog
.
An Explanation and an Example of OOP in an MFC Program
If you start building an MFC application with Visual Studio, then start a new project, choose MFC Application, and (in this case) accept the defaults by clicking Finish. Then add a class using the Generic C++ Class Wizard. Name the class CShape
and check the inline check box and the virtual destructor box. We will notice that the wizard generates a lot of source code and header files. The CShape
class now resides in the Shapes.h header file. Now we must add methods to our base class. Here is how the class appears before writing the code:
#pragma once
class CShape
{
public:
CShape(void)
{
}
virtual ~CShape(void)
{
}
We have a constructor that is called when an instance of the class is created and a destructor when the class is destroyed. Now we must add a method that takes a pointer to the Device Context:
#pragma once
class CShape {
public:
CShape(void)
{
}
virtual ~CShape(void)
{
}
virtual void Draw(CDC *pDC) = 0;
The Draw
method is set to zero. This means that CShape
will not implement this method even though the method is defined in CShape
. A derived class will inherit the method. Here is the Shape.h header file in its entirety:
#pragma once
class CShape
{
public:
CShape(void)
{
}
virtual ~CShape(void)
{
}
virtual void Draw(CDC *pDC) = 0;
void SetRect(int left, int top, int right, int bottom)
{
m_rc.SetRect(left, top, right, bottom);
}
void Offset(int x, int y) {
m_rc.OffsetRect(x, y); }
protected:
CRect m_rc;
};
CRect
is an MFC class and it is protected so no other code in this class can access it. We will see that derived classes can access it. If it were private, then derived classes could not access it. This demonstrates inheritance by creating a specialized shape that will inherit from the base class CShape
. So add others by choosing the C++ choice from the Generic C++ Class Wizard and name them CCircle
and CSquare
. Keep it the same file Shape.h, which means you fill CShape
as the base class in the base class text box. Check the inline checkbox. Note that whenever a class inherits from another, you have the same methods as the base: so you have a virtual void Draw(CDC *pDC) pDC->Ellipse(m_rc);
, you automatically have a Draw
method, a setRect
method, and an Offset
method. Here is the rest of the Shape
header file:
class CCircle : public CShape
{
public:
CCircle(void)
{
}
~CCircle(void)
{
}
virtual void Draw(CDC *pDC)
{
pDC->Ellipse(m_rc);
}
};
Call the Ellipse
method and we pass the rectangle that we have in our base class (now, we don't have to define this rectangle in our CCircle
class because it is already defined in our base class). Though the Rect
class is protected
, the derived class still has access to it. If we had declared it private
, then the derived
class could not access it. Here is the final portion of the header file:
class CSquare : public CShape
{
public:
CSquare(void)
{
}
~CSquare(void)
{
}
virtual void Draw(CDC *pDC)
{
pDC->Rectangle(m_rc);
}
};
class CRoundSquare : public CShape
{
public:
CRoundSquare(void)
{
}
~CRoundSquare(void)
{
}
virtual void Draw(CDC *pDC)
{
POINT pt = { 10, 10 };
pDC->RoundRect(m_rc, pt);
}
};
Another file that warrants a look at is the ShapesView.cpp file. Windows in MFC are views and are handled by our CView
class; a method that appears is the OnDraw
method: it has a pointer to a device context; dc
is the object that is used to draw to the graphics device; in MFC, we have the DC wrapped in the device context class. In the ShapesView.h file, we want to add Shape.h so our view class can make use of the shapes. When we implement this class contained in the ShapesView.h header file in the actual ShapesView.cpp, we want to create some shapes. Add this to this file to create shapes:
CShapesView::CShapesView()
{
m_Shapes.Add(new CCircle());
m_Shapes.Add(new CSquare());
m_Shapes.Add(new CRoundSquare());
m_Shapes.Add(new CCircle());
m_Shapes.Add(new CSquare());
m_Shapes.Add(new CRoundSquare());
m_Shapes.Add(new CCircle());
m_Shapes.Add(new CSquare());
m_Shapes.Add(new CRoundSquare());
Now we want to initialize the shapes:
int x = 10, y = 10;
for (int i = 0; i < m_Shapes.GetCount(); i++)
{
m_Shapes[i]->SetRect(x, y, x + 100, y + 100);
x += 50;
y += 25;
}
}
In the header file ShapesView.h we create a collection:
protected:
CArray<CShape*, CShape*> m_Shapes;
Because it is a collection, we have to make sure and clean up to free up the memory used. We do this with the destructor code in ShapesView.cpp:
CShapesView::~CShapesView()
{
for (int i = 0; i < m_Shapes.GetCount(); i++)
delete m_Shapes[i];
}
Here is the entire header file. To run this program, download the zip file into your Visual Studio 2008 (with the VC++ Feature pack installed or the service pack for VS 2008) projects folder. Just go to "Organize" and select "Make new folder" and call the folder shapes. Extract the zip files into the Shapes folder and then double click the solution file. When you examine the code, you will see a strong example of polymorphism. The Draw
function does not know what shape it is going to draw, as it is a predefined shape-type class. The output shape is different (ellipse, square, etc.) but the method performs the same operation.
#include "stdafx.h"
#include "Shapes.h"
#include "ShapesDoc.h"
#include "ShapesView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
IMPLEMENT_DYNCREATE(CShapesView, CView)
BEGIN_MESSAGE_MAP(CShapesView, CView)
ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
CShapesView::CShapesView()
{
m_Shapes.Add(new CCircle());
m_Shapes.Add(new CSquare());
m_Shapes.Add(new CRoundSquare());
m_Shapes.Add(new CCircle());
m_Shapes.Add(new CSquare());
m_Shapes.Add(new CRoundSquare());
m_Shapes.Add(new CCircle());
m_Shapes.Add(new CSquare());
m_Shapes.Add(new CRoundSquare());
int x = 10, y = 10;
for (int i = 0; i < m_Shapes.GetCount(); i++)
{
m_Shapes[i]->SetRect(x, y, x + 100, y + 100);
x += 50;
y += 25;
}
}
CShapesView::~CShapesView()
{
for (int i = 0; i < m_Shapes.GetCount(); i++)
delete m_Shapes[i];
}
BOOL CShapesView::PreCreateWindow(CREATESTRUCT& cs)
{
return CView::PreCreateWindow(cs);
}
void CShapesView::OnDraw(CDC* pDC)
{
CShapesDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CBrush brush(RGB(0, 255, 0));
CBrush *pOldBrush = pDC->SelectObject(&brush);
for (int i = 0; i < m_Shapes.GetCount(); i++)
m_Shapes[i]->Draw(pDC);
pDC->SelectObject(pOldBrush);
}
BOOL CShapesView::OnPreparePrinting(CPrintInfo* pInfo)
{
return DoPreparePrinting(pInfo);
}
void CShapesView::OnBeginPrinting(CDC* , CPrintInfo* )
{
}
void CShapesView::OnEndPrinting(CDC* , CPrintInfo* )
{
}
#ifdef _DEBUG
void CShapesView::AssertValid() const
{
CView::AssertValid();
}
void CShapesView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CShapesDoc* CShapesView::GetDocument() const {
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CShapesDoc)));
return (CShapesDoc*)m_pDocument;
}
#endif
void CShapesView::OnLButtonDown(UINT nFlags, CPoint point)
{
for (int i = 0; i < m_Shapes.GetCount(); i++)
m_Shapes[i]->Offset(10, 10);
Invalidate();
CView::OnLButtonDown(nFlags, point);
}
Here is the output: