14.4.3 使用鼠标绘图(3)
使用完全相同的过程可以添加其他元素类。因为其他元素类把CElement而不是MFC类作为基类,所以应当把类类别选作C++(www.cppentry.com),把模板选作C++(www.cppentry.com) class。选择Virtual destructor选项。对于CLine类,Class Wizard窗口应当如图14-14所示。

默认情况下,Class Wizard为头文件和.cpp文件提供的名称分别是Line.h和Line.cpp,不过可以修改这些名称,对这些文件使用不同的名称,或者把代码添加到已有的文件中。在基类定义中给CElement.h添加一个#include指令。在创建了CLine类定义以后,对CRectangle、CCircle和CCurve进行相同的操作。当完成这些工作以后,在.h文件中应当看到CElement类的所有4个子类的定义,针对每个子类都声明了一个构造函数和一个虚析构函数。
在视图中存储临时元素
在前面讨论如何绘制形状时已经介绍过,按下鼠标左键以后,拖动鼠标将创建和绘制一系列临时元素对象。所有元素的基类都是CElement,就可以添加std::shared_ptr<CElement>类型的智能指针,来指向这个视图类,用以存储临时元素的地址。可以把unique_ptr看作它的替代项,但我们最终需要在鼠标事件处理函数中访问指向元素的指针,而这个指针存储在文档对象的一个容器中,而unique_ptr不允许这么做。
首先,在#pragma once指令的后面给memory头文件和Element.h添加#include指令。再右击CSketcherView类,选择Add | Add Variable选项。m_pTempElement变量的类型应当是std::shared_ ptr<CElement>,和以前添加的两个数据成员一样,它应当是protected。可以在WM_MOUSEMOVE消息处理程序中使用m_pTempElement指针,测试以前的临时元素是否存在,因为在没有临时元素时,将这个指针安排为空。
3. CElement类
现在可以逐步填写这个元素类的定义,为Sketcher应用程序添加越来越多的功能-- 不过现在需要做什么呢?有些数据项(如颜色和位置)显然对于所有类型的元素都是通用的,所以可以把它们放在CElement基类中,以便在每个派生类中继承它们。但是,在定义特定元素属性的类中,有些数据成员却极其不同,所以需要在它们所属的特定派生类中声明这些成员。
CElement类包含要在派生类中实现的虚函数,以及在所有派生类中都相同的数据和函数成员。虚函数是通过CElement*指针自动为特定对象选择的函数。这时可以使用Add Member Wizard在CElement类中添加这些成员,但需要手动修改。目前,可以把CElement类定义修改为:
- class CElement: public CObject
- {
- protected:
- CPoint m_StartPoint; // Element position
- int m_PenWidth; // Pen width
- COLORREF m_Color; // Color of an element
- CRect m_EnclosingRect; // Rectangle enclosing an element
- public:
- virtual ~CElement();
- virtual void Draw(CDC* pDC) {} // Virtual draw operation
- // Get the element enclosing rectangle
- const CRect& GetEnclosingRect() const { return m_EnclosingRect; }
- protected:
- // Constructors protected so they cannot be called outside the class
- CElement();
- CElement(const CPoint& start, COLORREF color, int penWidth = 1);
- };
这段代码把构造函数CElement的访问方式从public修改为protected,以防止从CElement类的外部调用这个函数,新构造函数只能在派生类中调用,它有3个参数,笔宽使用默认值,因为笔宽大多为1。现在,派生类继承的成员是存储元素的位置、颜色和笔宽的数据成员,以及界定元素占用区域的边界矩形。
在类中还有两个成员函数定义:
GetEnclosingRect()返回m_EnclosingRect,需要使元素占用的区域失效时,就使用这个函数。
虚函数Draw()在派生类中实现,用于绘制元素。Draw()函数要求将一个指向CDC对象的指针传递给它,以访问需要在设备环境中绘图的函数。
您可能想把Draw()成员声明为CElement类中的纯虚函数,让派生类定义它-- 毕竟,它在这个类中没有任何有意义的内容。虽然通常可以这么做,但是CElement类将从CObject继承一个称为序列化的工具,以后在文件中存储元素对象时将使用它。序列化要求,CElement不是抽象类,以便创建这种类型的实例。如果想使用MFC的序列化能力,那么类一定不能是抽象的,还必须有无参数的构造函数。
可以在Element.cpp中添加新构造函数的定义:
- CElement::CElement(const CPoint& start, COLORREF color, int penWidth) :
- m_StartPoint(start), m_PenWidth(penWidth), m_Color(color) {}
所有成员都在初始化列表中定义了,所以构造函数体中不需要任何代码。