Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Object-oriented Programming Using MFC

0.00/5 (No votes)
9 Apr 2009 1  
An article to explain the general principles of object-oriented programing using MFC.

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:

// implicitly derived from Object
class  Employee {
  .   .   .
}
// explicitly derived from Object
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:
    // constructors
    Mammal():itsAge(2), itsWeight(5){}
    ~Mammal(){}

    //accessors
    int GetAge() const   { return itsAge; }
    void SetAge(int age) { itsAge = age; }
    int GetWeight() const { return itsWeight; }
    void SetWeight(int weight) { itsWeight = weight; }

    //Other methods
    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:

    // Constructors
    Dog():itsBreed(GOLDEN){}
    ~Dog(){}

    // Accessors
    BREED GetBreed() const { return itsBreed; }
    void SetBreed(BREED breed) { itsBreed = breed; }

    // Other methods
    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) // the Offset method will move the shapes
    {
        m_rc.OffsetRect(x, y); // transfer that over to the CRect
    }

protected:
    CRect m_rc;
                 
};
//and so on.

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) 
        //overrride Draw method and give it an actual implementation
    {
        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()
{
    // TODO: add construction code here
    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


// CShapesView

IMPLEMENT_DYNCREATE(CShapesView, CView)

BEGIN_MESSAGE_MAP(CShapesView, CView)
    // Standard printing commands
    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 construction/destruction

CShapesView::CShapesView()
{
    // TODO: add construction code here
    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)
{
    // TODO: Modify the Window class or styles here by modifying
    //  the CREATESTRUCT cs

    return CView::PreCreateWindow(cs);
}

// CShapesView drawing

void CShapesView::OnDraw(CDC* pDC)
{
    CShapesDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: add draw code for native data here
    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);
}


// CShapesView printing
BOOL CShapesView::OnPreparePrinting(CPrintInfo* pInfo)
{
    // default preparation
    return DoPreparePrinting(pInfo);
}

void CShapesView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: add extra initialization before printing
}

void CShapesView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: add cleanup after printing
}


// CShapesView diagnostics
#ifdef _DEBUG
void CShapesView::AssertValid() const
{
    CView::AssertValid();
}

void CShapesView::Dump(CDumpContext& dc) const
{
    CView::Dump(dc);
}

CShapesDoc* CShapesView::GetDocument() const // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CShapesDoc)));
    return (CShapesDoc*)m_pDocument;
}
#endif //_DEBUG


// CShapesView message handlers

void CShapesView::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: Add your message handler code here and/or call default
    for (int i = 0; i < m_Shapes.GetCount(); i++)
        m_Shapes[i]->Offset(10, 10);
    Invalidate();

    CView::OnLButtonDown(nFlags, point);
}

Here is the output:

Capture.JPG

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here