使用 MFC 进行面向对象编程






3.65/5 (11投票s)
本文旨在解释使用 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
命名空间类由四个公共方法组成——Equals
、GetHashCode
、ToString
和 GetType
。此外,Object
还有两个受保护的方法:MemberWiseClone
和 Finalize
。由于所有类型最终都派生自 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++ 选项添加其他类,并将它们命名为 CCircle
和 CSquare
。将它们保留在同一个文件 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);
}
输出如下