65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MFC 进行面向对象编程

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.65/5 (11投票s)

2009年4月10日

CPOL

6分钟阅读

viewsIcon

40514

downloadIcon

769

本文旨在解释使用 MFC 进行面向对象编程的一般原理。

引言

面向对象程序可以被看作是一系列相互协作的对象集合,而不是传统的将程序视为要执行的任务(子例程)列表。在 OOP 中,每个对象都能接收消息、处理数据并向其他对象发送消息,可以被视为一个独立的“机器”,具有明确的角色或职责。对这些对象的操作(或“运算符”)与对象紧密关联。例如,数据结构倾向于携带自己的运算符(或至少从相似对象或类“继承”它们)。Microsoft Foundation Class Library(也称为 Microsoft Foundation Classes 或 MFC)是一个将 Windows API 的一部分封装在 C++ 类中的库,包括支持它们使用默认应用程序框架的功能。为许多句柄管理的 Windows 对象以及预定义的 Windows 和通用控件定义了类。Windows API 是 Microsoft 操作系统中可用的 Microsoft 核心应用程序编程接口 (API) 集合。它以前称为 Win32 API;但是,“Windows API”这个名称更准确地反映了它源于 16 位 Windows 并在 64 位 Windows 上得到支持。几乎所有 Windows 程序都与 Windows API 交互。MFC 窗口是 C++ 和 Windows API 调用的混合体。实际上,MFC 窗口为您提供了大量(但并非全部)Windows API 的 C++ 包装器。本文试图通过 MFC 框架(至少)来描述面向对象编程的基本原则——多态性、继承和封装。因此,本文将从一个定义类的基本 C++ 程序开始,确定类的成员是公共 (public)、私有 (private) 还是受保护 (protected),并介绍构造函数和析构函数的使用。

基本原理

如果您是 .NET 开发人员,那么您可能知道所有类型都派生自根类 System.Object。这意味着以下两个定义是等效的

// implicitly derived from Object
class  Employee {
  .   .   .
}
// explicitly derived from Object
class Employee : System.Object {
 .   .   .
}

System.Object 命名空间类由四个公共方法组成——EqualsGetHashCodeToStringGetType。此外,Object 还有两个受保护的方法:MemberWiseCloneFinalize。由于所有类型最终都派生自 System.Object,因此可以保证每种类型的每个对象都继承这些成员。但这属于托管代码,而非 MFC。事实上,分配在堆上的对象会被跟踪以查看它们是否超出范围,因为没有托管堆。这是一个演示类及其成员用法的 C++ 程序

#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;
}

请注意,基类称为 Mammal。此类“封装”其数据并定义了一个名为 Speak() 的成员方法。现在请注意,另一个类被定义为 Dog。冒号表示此类派生自 Mammal 类。这意味着 Mammal 中定义的所有方法都可被 Dog 访问。

MFC 程序中 OOP 的解释和示例

如果您开始使用 Visual Studio 构建 MFC 应用程序,那么可以创建一个新项目,选择“MFC 应用程序”,然后(在此情况下)单击“完成”接受默认设置。然后使用“通用 C++ 类向导”添加一个类。将类命名为 CShape,并选中“内联”复选框和“虚拟析构函数”框。我们将注意到向导生成了大量的源代码和头文件。CShape 类现在存在于 Shapes.h 头文件中。现在我们必须向基类添加方法。以下是编写代码之前的类的外观

#pragma once
class CShape
{
public:
         CShape(void)
    {
    }
             virtual ~CShape(void)
    {
    }

我们有一个在创建类实例时调用的构造函数,以及在类销毁时调用的析构函数。现在我们必须添加一个接收设备上下文指针的方法

#pragma once 
class CShape { 
public: 
CShape(void) 
{ 
} 
virtual ~CShape(void) 
{ 
}
virtual void Draw(CDC *pDC) = 0; 

Draw 方法设置为零。这意味着 CShape 不会实现此方法,即使该方法已在 CShape 中定义。派生类将继承该方法。以下是完整的 Shape.h 头文件

#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 是一个 MFC 类,它是受保护的,因此该类中的任何其他代码都无法访问它。我们将看到派生类可以访问它。如果它是私有的,那么派生类就无法访问它。这通过创建一个将从基类 CShape 继承的专用形状来演示继承。因此,通过从“通用 C++ 类向导”中选择 C++ 选项添加其他类,并将它们命名为 CCircleCSquare。将它们保留在同一个文件 Shape.h 中,这意味着您在基类文本框中填写 CShape 作为基类。选中内联复选框。请注意,每当一个类继承另一个类时,您都会拥有与基类相同的​​方法:所以您有一个 virtual void Draw(CDC *pDC) pDC->Ellipse(m_rc);,您会自动拥有一个 Draw 方法、一个 setRect 方法和一个 Offset 方法。以下是 Shape 头文件的其余部分

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);
    }
};

调用 Ellipse 方法,并将我们在基类中拥有的矩形传递过去(现在,我们不必在 CCircle 类中定义这个矩形,因为它已经在我们的基类中定义了)。尽管 Rect 类是 protected,派生类仍然可以访问它。如果我们将其声明为 private,那么派生类将无法访问它。这是头文件的最后一部分

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);
    }
};

另一个值得一看的文件是 ShapesView.cpp 文件。MFC 中的窗口是视图,由我们的 CView 类处理;出现的一个方法是 OnDraw 方法:它有一个指向设备上下文的指针;dc 是用于绘制到图形设备的nsics对象;在 MFC 中,我们将 DC 封装在设备上下文类中。在 ShapesView.h 文件中,我们希望包含 Shape.h,以便我们的视图类可以使用这些形状。当我们在实际的 ShapesView.cpp 中实现包含在 ShapesView.h 头文件中的类时,我们希望创建一些形状。将此添加到该文件以创建形状

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;
    }
}

ShapesView.h 头文件中,我们创建一个集合

protected:

CArray<CShape*, CShape*> m_Shapes;

由于这是一个集合,我们必须确保清理以释放使用的内存。我们在 ShapesView.cpp 的析构函数代码中做到这一点

CShapesView::~CShapesView()
{
    for (int i = 0; i < m_Shapes.GetCount(); i++)
        delete m_Shapes[i];
}

这是完整的头文件。要运行此程序,请将 zip 文件下载到您的 Visual Studio 2008(已安装 VC++ 功能包或 VS 2008 的服务包)项目文件夹中。只需转到“组织”并选择“新建文件夹”,然后将其命名为 shapes。将 zip 文件解压到 Shapes 文件夹中,然后双击解决方案文件。当您检查代码时,您会看到多态性的一个有力示例。Draw 函数不知道它将绘制什么形状,因为它是一个预定义的形状类型类。输出形状不同(椭圆、正方形等),但方法执行相同的操作。

#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);
}

输出如下

Capture.JPG

© . All rights reserved.