m_EndPoint.y = c; }
//绘制矩形区域
dc.Rectangle(m_StartPoint.x,m_StartPoint.y, m_EndPoint.x+1,m_EndPoint.y+1); }
代码中先修改m_StartPoint和m_EndPoint的坐标值,然后在调用Rectangle函数绘制矩形区域时,将传入的m_EndPoint的x和y坐标值分别加1。
运行应用程序,现在我们可以任意绘制提供的四种图形了。但是如果绘制完图形后,将应用程序窗口最小化,然后再恢复,我们会发现刚才绘制的图形已经没有了。这是因为我们没有在OnDraw函数中把这些图形重画出来。为了能够重画图形,就需要将绘制的图形的信息存储起来。下面我们将介绍如何定义图形的存储结构,以及如何在OnDraw函数中将它们重画出来。
2.3 图元定义及重画
基于面向对象的程序设计方法,可以将用户绘制的每一个图形都看作一个对象,我们可以称之为图元。在面向对象编程中,对象用类进行定义。我们可以根据图形类型的不同定义不同的类。在本章的绘图应用程序中,提供了四种可绘制的图形:直线段,椭圆,椭圆区域和矩形区域。可以定义四个类分别与之对应,即直线段类,椭圆类,椭圆区域类和矩形区域类。这样每一个图元(用户绘制的图形)都是其对应的类的实例。同时,不同类型的图元之间又有相同的部分,比如图元都是用指定的点坐标来控制其形状与位置的。所以我们可以定义一个图元类,作为具体的图形类的基类。在其父类中定义图元共有的成员变量和成员函数,而在每个子类中定义具有自己特色的成员变量和成员函数。 2.3.1 图元基类CMapElement
我们将图元基类的名称定义为CMapElement。类名以大写的字母C开头是MFC对类名的要求。
打开类面板,用鼠标右键单击类面板中树形列表的根节点“DrawMap Classes”(Classes前面是项目名称),会弹出一个快捷菜单,如图2.10所示。在快捷菜单中选择“New Classs…”,将会出现“New Class”(创建新类)对话框,如图2.11所示。该对话框用于添加新类到项目中。
对话框中的“Class type”下拉框用于选择要创建的类的类型,可选的类型有三种:MFC Class,创建一个以MFC类为基类的类;Generic Class,创建一个普通类;Form Class,创建一个窗口类。图2.11是选择MFC Class时的对话框样式。此时“Name”输入框输入类的名称,根据输入的类的名称,系统自动生成类文件的名称,并显示在“File name”标签框中。在“Base class”下拉框中可以选择一个MFC类作为要创建的类的基类,该处必须选择一个类作为基类。如果要创建的是一个对话框类,可以在“Dialog ID”下拉框中选择一个对话框资源ID。系统将自动把创建的对话框类和对话框资源进行关联。
如果在“Class type”中选择Form class,则对话框样式如图2.12所示。此时可以选择的基类只有四个:CFormView(默认),CDaoRecordView,CRecordView,CDialog。
创建CMapElement类,我们选择“Class type”为Generic Class,此时对话框样式如图2.13所示。此时新建类的基类在“Base class(es)”表格中输入,因为C++中子类的基类可以有多个。我们在“Name”输入框中输入类名为CMapElement,此时系统默认的类文件名为MapElement.cpp,我们可以点击“Change…”按钮来修改类文件名和头文件名,但是建议采用系统默认的文件名,这里我们采用系统
默认的文件名。我们输入CMapElement类的基类为CObject类,该类是大多数MFC类的最根本的基类。我们选择它作为图元类的基类,是想要利用该类所提供的序列化能力,这在以后我们持久化图元时是非常有用的。点击OK按钮确认创建CMapElement类,此时可能会出现消息框,提示使用CObject类作为基类需要自己添加头文件,点击消息框的“确定”按钮后,系统创建CMapElement类并将该类加入到当前项目中。我们在类面板中可以看到该类。
在类向导对话框中也有一个“Add class…”按钮,点击后在出现的快捷菜单中选择“New…”,也可以打开一个新建类的对话框,该对话框只用于新建MFC类,不能选择创建其它两种类。
在类面板中,用鼠标左键双击CMapElement类节点,打开头文件。在类声明之前添加如下代码:
#include
CObject类是在afxtempl.h头文件中定义的,所以需要包含该头文件。我们可以看到系统自动在CMapElement类中添加了构造函数和析构函数。
现在来看一下在图元基类中应该有哪些成员变量和成员函数。在CMapElement类中添加如下私有成员变量:
private:
CPoint m_StartPoint;//图元起始控制点 CPoint m_EndPoint;//图元终止控制点
这两个CPoint变量就可以控制图元的位置了。现在用户可以绘制的四种图形都可以用这两个点来确定图形所在的位置。
在CMapElement类中添加如下公有成员函数: public:
//设置图元起始控制点
void SetStartPoint(CPoint point); //设置图元终止控制点
void SetEndPoint(CPoint point); //获得图元起始控制点 CPoint GetStartPoint(); //获得图元终止控制点 CPoint GetEndPoint();
//绘制图元,由具体的图元子类覆盖实现 virtual void draw(CDC* pDC);
//获得图元类型,由具体的图元子类覆盖实现 virtual int GetType();
各个函数的具体实现代码如下:
void CMapElement::SetStartPoint(CPoint point) {
m_StartPoint = point; }
void CMapElement::SetEndPoint(CPoint point) {
m_EndPoint = point; }
CPoint CMapElement::GetStartPoint() {
return m_StartPoint; }
CPoint CMapElement::GetEndPoint() {
return m_EndPoint; }
void CMapElement::draw(CDC *pDC) { }
int CMapElement::GetType() {
return 0; }
其中成员函数SetStartPoint和SetEndPoint用于设置图元的起始控制点和终止控制点,而函数GetStartPoint和GetEndPoint用于获得图元的起始控制点和终止控制点。
成员函数draw用于完成图元的绘制。该函数定义为虚函数,即由图元的子类来完成具体的实现,也就是说由每个图元子类自己来决定如何进行绘制。这样做的好处是在重画的时候不需要区分图元是哪种图元,只需要调用draw方法进行绘制即可。同时绘图代码在一个函数中,如果绘图方式发生改变,只需要修改此函数即可。该函数传入设备环境对象指针。
成员函数GetType用于返回代表图元类型的整型值。本函数也定义成虚函数,由图元子类来完成具体实现。在编辑图元的时候,需要知道要编辑的图元的种类,以便绘制相应的橡皮线。
图元基类其实还需要其它的成员变量和成员函数,我们将在后面用到时逐步进行介绍。
2.3.2 直线段图元子类CLine
我们定义直线段图元子类的类名为CLine,其基类为CMapElement。该子类需要实现基类定义的两个虚函数draw和GetType。在子类中添加公有成员函数:
public: