urn TRUE;
}
文档的清理
在关闭文档的最后一个子窗口时,框架要求文档清理数据。文档清理在文档类的DeleteContents()中完成。同样需要用ClassWizard生成DeleteContents的框架。
void CDrawDoc::DeleteContents()
{
// TODO: Add your specialized code here and/or call the base class
while (!m_strokeList.IsEmpty())
{
delete m_strokeList.RemoveHead();
}
CDocument::DeleteContents();
}
DeleteContents()从头到尾遍里链表中的所有对象指针,并通过指针删除对象,然后用RemoveHead()删除该指针。
文档的串行化
现在设计文档的Serialize函数,实现文档数据的保存和载入:
void CDrawDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_sizeDoc;
}
else
{
ar >> m_sizeDoc;
}
m_strokeList.Serialize(ar);
}
文档的Serialize()函数首先分别保存和载入文档大小,然后调用m_strokeList的Serialize()方法。m_strokeList.Serialize()又会自动调用存放在m_strokeList中的每一个元素CStroke的串行化方法CStroke.Serialize()最终实现文档的串行化即文档所包含的对象的存储和载入。
在DrawDoc.cpp的末尾加上CStroke::Serialize()函数的定义:
void CStroke::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_rectBounding;
ar << (WORD)m_nPenWidth;
m_pointArray.Serialize(ar);
}
else
{
ar >> m_rectBounding;
WORD w;
ar >> w;
m_nPenWidth = w;
m_pointArray.Serialize(ar);
}
}
CStroke的Serialize()依次保存(载入)笔划的矩形边界、线宽度以及点数组。注意m_nPenWidth是UINT类型的,>>和<<操作符并不支持UINT类型但却支持WORD,因此要作UINT和DWORD之间的类型转换。点数组的串行化通过调用数组的每个CPoint类元素的Serialize()完成,CPoint类是MFC类,它本身支持串行化。
8.3.3 设计绘图程序的视图类
视图类数据成员
现在着手设计绘图程序的视图类。首先,需要在视图中增加两个数据成员:
class CDrawView : public CScrollView
{
protected: // create from serialization only
CDrawView();
DECLARE_DYNCREATE(CDrawView)
// Attributes
public:
CDrawDoc* GetDocument();
protected:
CStroke* m_pStrokeCur; // the stroke in progress
CPoint m_ptPrev; // the last mouse pt in the stroke in progress
// 其它数据成员和成员函数......
};
m_pStrokeCur代表正在画的那一个笔划。m_ptPrev保存鼠标上次移动位置。画图时,LineTo从这个点到当前鼠标位置画一条直线。
视图初始化
接下去,要初始化视图。由于是卷滚视图,因此要在OnInitialUpdate()中设置卷滚范围。在用户选择File->New菜单或File->Open菜单时,框架调用OnInitialUpdate函数。
void CDrawView::OnInitialUpdate()
{
SetScrollSizes(MM_LOENGLISH, GetDocument()->GetDocSize());
CScrollView::OnInitialUpdate();
}
注意我们这里将映射模式设置为MM_LOENGLISH,MM_LOENGLISH以0.01英寸为逻辑单位,y轴方向向上递增,同MM_TEXT的y轴递增方向相反。
视图绘制
在CDrawView::OnDraw()内完成视图绘制工作。在以前的文档视结构程序中,在需要绘图的时侯都是绘制整个窗口。如果窗口只有很小的一部分被覆盖,是否可以只绘制那些需要重画的部分?
回答是肯定的,而且大部分程序都这么做了。
比如,象下图这种情况:
图8-5 窗口的重绘
当窗口2从窗口1上移开后,只需要重画阴影线所包围的区域就够了。
当Windows通知窗口要重绘用户区时,并非整个用户区都需要重绘,需要重绘的区域称为“无效矩形区”,如上图中的阴影区域。用户区中出现一个无效矩形提示Windows在应用程序队列中放置WM_PAINT消息。由于WM_PAINT消息优先级最低,可调用UpdateWindows直接立即向窗口发送WM_PAINT消息,从而立即重绘。无效矩形区限制程序只能在该区域中绘图,越界的绘图将被裁剪掉。下面三个函数与无效矩形有关:
InvalidateRect 产生一个无效矩形,并生成WM_PAINT消息
ValidateRect 使无效矩形区有效
GetUpdateRect 获得无效矩形坐标(逻辑)
Windows为每个窗口保留一个PAINTSTRUCT结构,其中包含无效矩形区域的坐标值。
要想在自己的程序高效绘图、只绘制无效矩形,首先需要重载视图的OnUpdate成员函数。
virtual void CView::OnUpdate( CView*pSender, LPARAM lHint, CObject*pHint );
当调用文档的UpdateAllViews时,框架会自动调用OnUpdate函数,也可在视图类中直接调用该函数。OnUpdate函数一般是这样处理的:访问文档,读取文档的数据,然后对视图的数据成员或控制进行更新,以反映文档的改动。可以用OnUpdate函数使视图的某部分无效。以便触发视的OnDraw,利用文档数据重绘窗口。缺省的OnUpdate使窗口整个客户区都无效,在重新设计时,要利用提示信息lHint和pHint定义一个较小的无效矩形。修改后的OnUpdate成员函数如清单8.5。
清单8.5 修改后的OnUpdate成员函数
void CDrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized code here and/or call the base class
// The document has informed this view that some data has changed.
if (pHint != NULL)
{
if (pHint->IsKindOf(RUNTIME_CLASS(CStroke)))
{
// The hint is that a stroke as been added (or changed).
// So, invalidate its rectangle.
CStroke* pStroke = (CStroke*)pHint;
CClientDC dc(this);
OnPr