再上一篇:15.2 文档与视图结构
上一篇:15.3 MFC的数组类
主页
下一篇:15.5 GDI与文本处理
再下一篇:15.6 CString类
文章列表

15.4 鼠标使用实例

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

本节用一个实例来说明如何设计一个应用程序,使用鼠标在视图上画出若干条折线。15.4.1 创建应用程序的基本框架

用MFC AppWizard创建应用程序的基本框架,设输入的项目名为ch1001,在弹出的对话框中选择“Single document”,生成SDI应用程序,单击“Finish”按钮,生成应用程序源文件的基本框架。

15.4.2 处理视图类

画折线的过程规定为:按下鼠标左键,确定画一条线的开始点;仍按下鼠标左键,并拖动鼠标时,在开始点与鼠标当前位置点之间画一条红色的虚线(过渡线);当松开鼠标左键时,确定了画线的终点,则在开始点与终点之间画出一条红色的实线。

首先要在视图类中增加数据成员,即在文件Ch1001View.h的类CCh1001View中增加:

int m_nDraw; //1为画线状态,0为非画线状态

HCURSOR m_hCursor;//光标句柄,视图窗口内为十字形,否则为箭头

CPoint m_posOld; //当前鼠标位置(画线的终点坐标)

CPoint m_posOrigin;// 鼠标开始位置(画线的开始点坐标)

应用程序要知道当前是否处于画图状态,用m_nDraw来表示这种状态。在产生窗口时,缺省的光标形状为“箭头”,而在画图时必须使用“十字”形状的光标,以便精确定位。为此,须用m_hCursor来保存画图时所用“十字”光标的句柄。

在 VC++集成环境下,编辑 Ch1001View.h文件的最快捷的方法是单击项目文件中的ClassView标签,打开文件夹ch1001 classes,然后双击ch1001View类名,则在编辑窗口中自动打开文件ch1001View.h。按如下形式加入数据成员(黑体字部分)。

class CCh1001View : public CView

{

protected: // create from serialization only

CCh1001View();

DECLARE_DYNCREATE(CCh1001View)

// Attributes

//add new members

int m_nDraw;

HCURSOR m_hCursor;

CPoint m_posOld;

CPoint m_posOrigin;

……

}

在类CCh1001View的构造函数中完成对新增加的数据成员初始化。打开工程文件窗口中的文件夹CCh1001View,双击CCh1001View函数,则打开Ch1001View.cpp文件。增加如下黑体字部分。

CCh1001View::CCh1001View()

{

// TODO: add construction code here

m_nDraw =0; //A

m_hCursor = AfxGetApp()->LoadStandardCursor (IDC_CROSS); //B}

开始时处于不画线状态,为此将m_nDraw置为0。B行代码产生一个十字光标,并保存该光标的名柄赋给m_hCursor,以便画图时使用该光标。注意,AfxGetApp返回指向应用程序对象的一个指针,并通过该指针调用其成员函数得到一种标准的十字形光标图形对象。在windows.h文件中定义了11种标准光标的宏,表15-6给出了光标宏名所代表的光标形状。 表15-6 标准的光标形状

光标宏 光标的形状

IDC_ARROW 缺省的箭头光标

IDC_CROSS 十字形光标

IDC_WAIT 沙漏形光标

IDC_IBEAM 垂直I形光标

IDC_UPARROW 垂直箭头光标

IDC_SIZE 装入方框光标,右下角带有一个较小的方框

IDC_SIZEALL 四向箭头光标,常用于缩放窗口

IDC_ICON 空图标光标

IDC_SIZENWSE 指向左上角和右下角的双向箭头光标

IDC_SIZENSW 指向左下角和右上角的双向箭头光标

IDC_SIZEWE 水平双向箭头光标

IDC_SIZENS 垂直双向箭头光标

根据画线过程,要增加按下鼠标左键、移动鼠标、松开鼠标左键的鼠标消息处理函数。增加一个鼠标消息处理函数可分成两步完成:首先利用ClassWizard向导映射鼠标消息,生成消息处理函数的框架;然后修改消息处理函数。生成消息处理函数的框架的步骤为:

图 15-7 MFC类向导对话框

(1) 选View菜单中的ClassWizard命令。

(2)在弹出的“MFC ClassWizard”对话框中选择“Message Maps”标签,表示要进行消息映射。

(3)从“Class name:”下拉式列表框中选择类名“CCh1001View”(参见图15-7),表示将鼠标消息映射到视图类中。

(4)在“Object Ids:”选择框中选择(单击)视图对象“CCh1001View”,表示在视图对象中增加消息处理函数。

(5)在“Messages:”选择框中,选择要映射的鼠标消息WM_LBUTTONDOWN,并双击该消息,系统产生了该消息处理函数的框架。也可以单击消息 WM_LBUTTONDOWN,然后单击按钮“Add Function”,在弹出的对话框中单击“OK”按钮。

(6)重复第5步,分别映射鼠标消息WM_LBUTTONUP和WM_MOUSEMOVE,为这两个消息建立消息处理函数。

经以上处理后,MFC在文件Ch1001View.h的类CCh1001View 中增加了三个鼠标事件的处理函数,在如下的程序片段中用黑体字标出。

class CCh1001View : public CView

{

……

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

……

};

在文件ch1001View.cpp中,增加了以下黑体字标出的三行消息映射宏:

BEGIN_MESSAGE_MAP(CCh1001View, CView)

//{{AFX_MSG_MAP(CCh1001View)

ON_WM_LBUTTONDOWN()

ON_WM_MOUSEMOVE()

ON_WM_LBUTTONUP()

//}}AFX_MSG_MAP

……

也在文件 ch1001View.cpp中增加了三个消息处理函数的框架,下面给出了处理按下鼠标左键的消息处理函数。

void CCh1001View::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

CView::OnLButtonDown(nFlags, point);

}

根据画图的要求来增加消息处理代码,在函数OnLButtonDown中要将当前鼠标点作为画线的开始坐标,也作为终点坐标。置开始画线标志。将该函数修改为:

void CCh1001View::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

m_posOld=point; //当前点作为结束点

m_posOrigin = point; //当前点作为开始点

SetCapture() ; //CWnd的成员函数,捕获鼠标

m_nDraw = 1; //开始画线,置标志

RECT theRect;

GetClientRect(&theRect); // CWnd的成员函数

ClientToScreen(&theRect); // CWnd的成员函数

ClipCursor(&theRect); // WINDOWS API函数

CView::OnLButtonDown(nFlags, point);

}

成员函数SetCapture的作用是捕获鼠标,表示将要开始连续跟踪移动鼠标的消息。若要释放对鼠标的捕获,并恢复正常的输入,可调用函数ReleaseCapture。成员函数GetClientRect获取视图窗口客户区的坐标,数据类型RECT的定义为:

typedef struct tagRECT{

int left, top, right, bottom;

}RECT;

它定义了左上角点和右下角点的坐标,即定义了一个窗口的矩形区域。GetClientRect函数将视图窗口的左上角点和右下角点的坐标保存到theRect中,函数ClientToScreen将客户区的坐标变换为屏幕坐标,最后调用WINDOWS API函数ClipCursor将光标限定在窗口的客户区域内,即视图窗口内。因ClipCursor使用的参数为屏幕坐标,前两个函数的调用是为调用该函数服务的。

修改处理鼠标移动事件的消息处理函数OnMouseMove。在鼠标移动期间,系统连续产生鼠标移动事件。对每一个鼠标移动事件,要先将点m_posOrigin与点m_posOld之间已画的虚线删除,并在点m_posOrigin与鼠标当前位置point之间画一条虚线。为此,进行以下操作:

(1)设置用于画图的十字形光标SetCursor(m_hCursor)。

(2)要在视图窗口上画线,必须产生当前视图窗口的设备场境对象 dc,即 CClientDCdc(this)。

(3) 为了画线,必须产生一支新的画笔,即

CPen NewPen;

NewPen.CreatePen(PS_DASH,1,RGB(255,0,0));

产生新笔画可以通过成员函数CreatePen来实现,也可以通过构造函数来实现,即以上二个语句等同于:

CPen NewPen(PS_DASH,1,RGB(255,0,0));

函数CreatePen包含了三个参数,第一个参数为选择画笔的笔型,它可以是表15-7中给出的六种笔型中的一种;第二个参数为画笔的宽度,通常为像素的个数;第三个参数为笔的颜色,它是三种颜色分量的三元组(Red,Green,Blue),每一种分量的取值为 0~255。本例产生的笔型为虚线,一个像素宽的红色画笔。

表 15-7 画笔的缺省笔型

笔型 说明

PS_SOLID 实线

PS_DASH 虚线

PS_DOC 点线

PS_DASHDOC 点划线

PS_DASHDOCDOC 双点划线

PS_NULL 空

(4)保存设备场境的当前画笔,并设置新的画笔,即

pOldPen=dc.SelectObject(&NewPen);

该成员函数返回指向原画笔的指针,并设置新的画笔,参数为指向新画笔的指针。(5)为了在已存在的图上画上新的图(重迭),必须设置绘图模式,即规定当前画笔与显示画面上已存在的像素之间的颜色是如何组合的。它们可以采用二进位运算符&、| 、~、^来进行组合。通过成员函数dc.SetROP2 (int nDrawMode)来实现,其中缩写ROP为光栅操作,参数nDrawMode指定绘图模式,它可以取表15-8中给出的16种模式之一的值。

表15-8 光栅操作的绘图模式

宏(模式值) 含义

R2_BLACK 像素为黑色

R2_WHITE 像素为白色

R2_NOP 像素为原来颜色(不变)

R2_NOT 像素为屏幕颜色的反色

R2_COPYPEN 像素为画笔颜色

R2_NOT COPYPEN 像素为画笔颜色的反色

R2_MERGEPENNOT 像素为(NOT(屏幕))OR(画笔)

R2_MASKPENNOT 像素为(NOT(屏幕))AND(画笔)

R2_MERGEPEN 像素为(屏幕)OR(画笔)

R2_ MASKNOTPEN 像素为(NOT(画笔))AND(屏幕)

R2_MERGENOTPEN 像素为(NOT(画笔))OR(屏幕)

R2_NOT MERGEPEN 像素为(NOT((画笔)OR(屏幕))

R2_ MASKPEN 像素为(画笔)AND(屏幕)

R2_NOT MASKPEN 像素为(NOT((画笔)AND(屏幕)))

R2_XORPEN 像素为(屏幕)XOR(画笔)

R2_ NOTXOR PEN 像素为(NOT((屏幕)XOR(画笔))

(6)要删除已画的虚线,并画出起点到光标位置的红线,调用函数 dc.MoveTo,把画笔移到开始画图的起始点,然后调用函数 dc.LineTo,从起点到终点之间用底色(白色)画一条直线来实现删除已画的虚线,即

dc.SetROP2 (R2_WHITE); //用底色(白色)画线

dc.MoveTo(m_posOrigin); //画笔移到起点

dc.LineTo (m_posOld); //在起点与终点之间画一条底色线

(7)从起点到当前光标点之间画一条红色的虚线,即

dc.SetROP2 (R2_COPYPEN);//使用画笔的颜色画线

dc.MoveTo(m_posOrigin);

dc.LineTo (point);

(8)把当前光标点作为终点,并恢复原来的画笔,即

m_posOld=point;

dc.SelectObject (pOldPen);

处理鼠标移动事件的完整的消息处理函数为:

void CCh1001View::OnMouseMove(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

SetCursor(m_hCursor);

if(m_nDraw){

CClientDC dc(this);//create a device context object

CPen NewPen, *pOldPen;

NewPen.CreatePen(PS_DASH,1,RGB(255,0,0));

pOldPen=dc.SelectObject(&NewPen);

dc.SetROP2 (R2_WHITE);

dc.MoveTo(m_posOrigin);

dc.LineTo (m_posOld);

dc.SetROP2 (R2_COPYPEN);

dc.MoveTo(m_posOrigin);

dc.LineTo (point);

m_posOld=point;

dc.SelectObject (pOldPen);

UpdateWindow();

}

CView::OnMouseMove(nFlags, point);

}

弹起鼠标左键的消息处理函数首先要在起点与终点之间画一条红色实线,置画线结束标志,然后释放对鼠标的捕获,以恢复正常输入。完整的消息处理函数为:

void CCh1001View::OnLButtonUp(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

if(m_nDraw){

CClientDC dc(this);//create a device context object

CPen NewPen(PS_SOLID,1,RGB(255,0,0));

CPen *pOldPen;

pOldPen=dc.SelectObject(&NewPen);

dc.MoveTo(m_posOrigin);

dc.LineTo (point);

m_posOld=point;

dc.SelectObject (pOldPen);

m_nDraw =0;

ReleaseCapture( );

ClipCursor(NULL);

}

CView::OnLButtonUp(nFlags, point);

}

在缺省的情况下,视图窗口的背景颜色为当前窗口的背景颜色光标为箭头形状。若背景颜色为黑色,则所画的线基本上是看不见的。为此要修改窗口的特性(风格),打开文件ch1001View.cpp,在函数CCh1001View::PreCreateWindow中添加以下程序代码:

BOOL CCh1001View::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

cs.lpszClass =AfxRegisterWndClass(

CS_HREDRAW | CS_VREDRAW | CS_PARENTDC,//类的风格

0, //使用缺省的光标

(HBRUSH)::GetStockObject (WHITE_BRUSH),//置背景式为白色

0 ); //没有图标

return CView::PreCreateWindow(cs);

}

图15-8 修改窗口标题的对话框

编译、连接并执行这个应用程序时,已可实现画线的功能。应用程序窗口的标题为Untitled,可以将窗口标题改为“画图试验”。其步骤为:双击项目工作区窗口的“ResourceView” 标签;打开文件夹“ch1001 resources”,双击“String Table”文件夹;双击“abc String Table”资源,双击编辑窗口中ID为“IDR_MAINFRAME”表目(通常在第一行),弹出如图15-8所示的对话框;在Caption文本框中的一行字符串修改为“ch1001\n画图试验\nCh1001 Document”。该字符串的第一部分“ch1001”为应用程序名,第二部分“画图试验”为窗口标题名,第三部分“Ch1001 Document”表示文档资源,各部分用“\n”隔开。按Esc键,并编译、连接、执行程序,检查窗口的标题是否已修改为“画图试验”。15.4.3 处理文档

以上的应用程序在执行期间,当视图窗口被复盖后重新显示时,所画的图形会全部消失,原因是没有保存画线的数据。为此要在文档类中增加存放直线坐标的数据,以便在重画窗口时恢复图形。

增加类CLine来保存一条直线的二个端点的坐标,根据保存的坐标值就能画一条直线。在文档类Ch1001Doc.h中添加以下程序代码(插在class CCh1001Doc : public CDocument之前)。

class CLine:public CObject{

protected:

int m_nStartX,m_nStartY; //起点坐标

int m_nEndX,m_nEndY; //终点坐标

public:

CLine(int=0,int=0,int=0,int=0); //构造函数,初始化坐标值

void DrawLine(CDC *); //在两个坐标点之间画一条直线

};

在文档类文件 ch1001Doc.cpp 中增加以上两个成员函数的实现部分,并插在END_MESSAGE_MAP()之后。

CLine::CLine(int x1,int y1,int x2,int y2)

{

m_nStartX=x1; m_nStartY=y1;

m_nEndX=x2; m_nEndY=y2;

}

void CLine::DrawLine(CDC *pDC)

{

CPen NewPen(PS_SOLID,1,RGB(255,0,0));

CPen *pOldPen =pDC->SelectObject(&NewPen);

pDC->SetROP2 (R2_COPYPEN);

pDC->MoveTo(m_nStartX,m_nStartY);

pDC->LineTo (m_nEndX,m_nEndY);

pDC->SelectObject (pOldPen);

}

为了保存已画线段的所有坐标,要定义一个CObArray类的动态数组,每画一条直线,将该线的对象存放在动态数组中,以便根据数组中的数据重画图形。在文件ch1001Doc.h的类CCh1001Doc中增加以下成员函数和成员数据。

class CCh1001Doc : public CDocument

{……

// Attributes

protected:

CObArray m_cObArray; //每一个元素为CLine对象的指针

// Operations

public:

void AddLine(int,int,int,int); //把一条线段的坐标加入数组中

CLine * GetLineAt(int); //获取数组中一个元素的值

int GetNumberOfAllLines(void); // 获取线数组中的线段数

……

}

在 文 件 ch1001Doc.cpp 中 增 加 这 三 个 成 员 函 数 的 实 现 , 插 在CCh1001Doc::CCh1001Doc()之前。

void CCh1001Doc::AddLine(int x1,int y1,int x2,int y2)

{

CLine *pLine = new CLine(x1,y1,x2,y2); //构造一个动态的Cline对象

m_cObArray.Add(pLine); //将一线段的对象加入数组中

SetModifiedFlag(); //设置数组中的数据已修改标志

}

CLine * CCh1001Doc::GetLineAt(int nIndex)

{ if(nIndex < 0 || nIndex >m_cObArray.GetUpperBound())

return 0; //数组的下标出界

return (CLine *) m_cObArray.GetAt (nIndex);//返回指向一条线段对象的指针

}

int CCh1001Doc::GetNumberOfAllLines(void)

{ return m_cObArray.GetSize();} //返回数组中的线段条数

每当画完一条直线后,要将该直线的数据存入动态数组中,为此必须修改处理鼠标左键的消息处理函数,增加以下两行代码。

void CCh1001View::OnLButtonUp(UINT nFlags, CPoint point)

{ ……

if(m_nDraw){

……

}

CCh1001Doc *pDoc = GetDocument(); //获取指向文档对象的指针

pDoc->AddLine(m_posOrigin.x,m_posOrigin.y,point.x,point.y);

CView::OnLButtonUp(nFlags, point);

}

其中调用文档对象的成员函数AddLine将该直线坐标数据加入到动态数组对象中。当要窗口重画时,系统产生重画消息,并根据m_cObArray中保存的数据进行重画操作。为此,要修改视图窗口的成员函数OnDraw(在文件ch1001View.cpp中),增加以下代码:

void CCh1001View::OnDraw(CDC* pDC)

{ ……

// TODO: add draw code for native data here

int nIndex;

nIndex = pDoc->GetNumberOfAllLines();

while (nIndex--)

pDoc->GetLineAt(nIndex)->DrawLine (pDC);

}

首先得到数组中的元素个数,然后依次从数组中取出一个线段对象,并根据该对象中的坐标值画出一条直线,直至取完数组中的直线为止。

在退出应用程序时,要删除动态数组中的指针所指向的CLine对象的存储空间。 在文件ch1001Doc.h的类CCh1001Doc中增加以下成员函数说明。

class CCh1001Doc : public CDocument

{ ……

int GetNumberOfAllLines(void);

virtual void DeleteContents(void); //依次删除数组中的CLine对象

……

}

在文件ch1001Doc.cpp中,完成该成员函数的实现。

void CCh1001Doc::DeleteContents()

{

int nIndex;

nIndex=m_cObArray.GetSize(); //获取数组中的线段数

while(nIndex--)

delete m_cObArray.GetAt(nIndex);//删除数组中的第nIndex个线段对象

m_cObArray.RemoveAll(); //最后,释放动态数组占用的空间

}

修改文件ch1001Doc.cpp中的类CCh1001Doc的析构函数:

CCh1001Doc::~CCh1001Doc()

{

DeleteContents();//结束应用程序时,释放动态数组占用的空间

}

编译、连接并执行程序,当视图窗口被覆盖后重新显示时,已能正确显示出已画的所有线段。

15.4.4 处理菜单

在这应用程序中再增加一个新的功能:使用“Edit”菜单中的“Undo”命令撤消最后画的一条直线。应用程序向导所产生的应用程序框架中,已将该命令的 ID 设置为“ID_EDIT_UNDO”,但没有建立该命令的消息处理函数。我们只要增加该函数,并修改该函数就可实现删除一条线的功能。用类向导来映射该命令消息,步骤为:

(1)选择View菜单中的ClassWizard命令,弹出类向导对话框;并单击标签“MessageMaps”。

(2)把命令消息“ID_EDIT_UNDO”映射到文档边框窗口上。在“Class name:”下拉式列表框中选择类名“CCh1001Doc”,在“Object Ids:”选择框中选择(单击)命令对象“ID_EDIT_UNDO”,此时在“Messages:”列表框中显示该菜单命令可对应两个消息:COMMAND命令消息,当选择该菜单时要发送该命令消息;UPDATE_COMMAND_UI命令消息,当打开该命令菜单时要发送该命令消息。

(3)在“Messages:”选择框中,双击COMMAND,MFC为该消息产生一个消息处理函数的框架,函数名为“OnEditUndo”。类同地,双击 UPDATE_COMMAND_UI,为该消息产生函数名为“OnUpdateEditUndo”的消息处理函数框架。

修改文件ch1001Doc.cpp中的这两个消息处理函数:

void CCh1001Doc::OnEditUndo()

{

// TODO: Add your command handler code here

int nIndex =m_cObArray.GetUpperBound();//返回数组中最后一个元素下标

if( nIndex >=0){

delete m_cObArray.GetAt(nIndex);//删除最后一条线的CLine对象

m_cObArray.RemoveAt(nIndex); //从数组中删除最后一个元素

}

UpdateAllViews(NULL); //更新视图

SetModifiedFlag(); //设置修改标志

}

文档修改标志由类Cdocument对象来维护的,每当创建一个新文档文件、打开一个文档文件或退出系统时,将要检查文档修改标志,若指明已修改,则要给出提示信息,提示用户尚未保存数据。将文档中的数据存入数据文件后,将复位修改标志。

修改另一个消息处理函数OnUpdateEditUndo为:

void CCh1001Doc::OnUpdateEditUndo(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

pCmdUI->Enable(m_cObArray.GetSize());

}

成员函数pCmdUI->Enable只有一个取逻辑值的参数。若参数值为0时,使该菜单项成为灰色(不能使用);其值为非0时,使该菜单命令可用。当动态数组中有一条直线数据时,函数m_cObArray.GetSize返回的值大于0,此时可使用“Undo”命令来删除动态数组中的最后一线段。

重新编译、连接并执行应用程序,看看编辑命令“Undo”是否起作用。

15.4.5 文档数据串行化

文档数据串行化要实现将文档中的数据写入文件或从文件中读取数据到文档对象中。MFC AppWizard已在文档类中产生成员函数Serialize的基本框架,只要略作修改就可实现这一功能。

本例中使用了MFC提供的动态数组对象m_cObArray,该对象提供了该类的成员函数Serialize,可用它来实现文档文件的读或写操作。最简单的方法是在文档类的成员函数Serialize中调用对象m_cObArray的成员函数Serialize,即

void CCh1001Doc::Serialize(CArchive& ar)

{

if (ar.IsStoring()) { // TODO: add storing code here

m_cObArray.Serialize(ar);

}

else { // TODO: add loading code here

m_cObArray.Serialize(ar);

}

}

其中参数ar是文本文件流对象。因m_cObArray.Serialize(ar)并不知道该数组中每一个指针所指向的对象中包含那些数据成员,因些要编写CLine对象本身的串行化代码。使用MFC提供的工具已无法实现这种串行化操作,只能用手工方法实现。具体做法是:首先在类CLine中添加宏DECLARE_SERIAL(CLine),指明要对CLine对象串行化,并在CLine类中添加实现串行化的成员函数Serialize(CArchive& ar)的说明,即在类CLine中增加如下的黑体字部分。

class CLine:public CObject{

protected:

int m_nStartX,m_nStartY; //起点坐标

int m_nEndX,m_nEndY; //终点坐标

DECLARE_SERIAL(CLine)

public:

CLine(int=0,int=0,int=0,int=0);

void DrawLine(CDC *);

virtual void Serialize(CArchive& );

};

其次,要建立实现类CLine串行化的映射关系,并完成CLine成员函数Serialize的实现代码。在文件ch1001Doc.cpp中,增加以下程序代码。

IMPLEMENT_SERIAL(CLine,CObject,1)

void CLine::Serialize(CArchive &ar)

{

if(ar.IsStoring())

ar<

<

else

ar>>m_nStartX>>m_nStartY

>>m_nEndX>>m_nEndY;

}

宏 IMPLEMENT_SERIAL(CLine,CObject,1) 和 DECLARE_SERIAL(CLine) 允 许CObArray的成员函数Serialize读写出一个对象时调用CLine中的成员函数Serialize。前一个宏的第一个参数为类名,第二个参数是基类名,第三个参数用于表示应用程序的版本号。

经编译连接后,重新执行应用程序时,就可实现把文档对象中保存的直线数据写入文件或将保存在文件中的直线数据读入文档对象的动态数据中。