友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第85部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
窗口的产生导至WM_PAINT 产生,于是OnDraw 发生效用,把文件内容画出来:
图11…2 一份Document 连结两个Views , 没有同步修正画面。
但是, 此后如果你在Scrib1:1 窗口上绘图而未缩放其尺寸的话(也就是不产生
WM_PAINT ),Scrib1:2 窗口内看不到后续绘图内容。我们并不希望如此,不幸的是上
一章的Scribble Step3 正是如此。
不能同步更新的关键在于,没有人通知所有的兄弟们(Views )一起动手…动手调用
OnDraw 。你是知道的,只有当WM_PAINT 产生,OnDraw 才会被调用。因此,解决方
式是对每一个兄弟都发出WM_PAINT ,或任何其它方法…只要能通知到就好。也就是
说,让附属于同一Document 的所有Views 都能够立即反应Document 内容变化的方法
就是,始作俑者(被使用者用来修改Document 内容的那个View )必须想办法通知其
他兄弟。
629
…………………………………………………………Page 692……………………………………………………………
第篇 深入 MFC 程式設計
经由CDocument::UpdateAllViews,MFC 提供了这样的一个标准机制。
让所有的Views 同步更新资料的关键在于两个函数:
1。 CDocument::UpdateAllViews 这个函数会巡访所有隶属同一份Document 的各
个Views ,找到一个就通知一个,而所谓「通知」就是调用其OnUpdate 函数。
2。 CView::OnUpdate 我们可以在这个函数中设计绘图动作。或许是全部重绘(这
比较笨一点),或许想办法只绘必要的一小部份(这比较聪明一些)。
Document
View:1 View:2 View:3
o1 使用者在View:1 做动作(View 扮演使用者接口的第一线)。
o2 View:1 调用GetDocument ,取得Document 指针,更改资料内容。
o3 View:1 调用Document 的UpdateAllViews 。
o4 View:2 和View:3 的OnUpdate 一一被调用起来,这是更新画面的时机。
如果想让绘图程序聪明一些,不要每次都全部重绘,而是只择「必须重绘」的区域重绘,
那么OnUpdate 需要被提示什么是「必须重绘的区域」,这就必须借助于UpdateAllViews
的参数:
virtual void UpdateAllViews(CView* pSender;
LPARAM lHint;
CObject* pHint);
第一个参数代表发出此一通牒的始作俑者。这个参数的设计无非是希望避免重
复而无谓的通牒,因为始作俑者自己已经把画面更新过了(在鼠标消息处理常
式中),不需要再被通知。
630
…………………………………………………………Page 693……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
■后面两个参数lHint 和pHint 是所谓的提示参数(Hint ),它们会被传送到同
一Document 所对应的每一个Views 的OnUpdate 函数去。lHint 可以是一些
特殊的提示值,pHint 则是一个衍生自CObject 的对象指针。靠着设计良好的
「提示」,OnUpdate 才有机会提高绘图效率。要不然直接通知OnDraw 就好
了,也不需要再搞出一个OnUpdate。
另一方面,OnUpdate 收到三个参数(由CDocument:: UpdateAllViews 发出):
virtual void OnUpdate(CView* pSender;
LPARAM lHint;
CObject* pHint);
因此,一旦Document 资料改变,我们应该调用CDocument::UpdateAllViews 以通知所有
相关的Views 。而在CMyView::OnUpdate 函数中我们应该以效率为第一考量,利用参数
中的hint 设定重绘区,使后续被唤起的OnDraw 有最快的工作速度。注意,通常你不
应该在OnUpdate 中执行绘图动作,所有的绘图动作最好都应该集中在OnDraw;你在
OnUpdate 函数中的行为应该是计算哪一块区域需要重绘, 然后调用
CWnd::InvalidateRect ,发出WM_PAINT 让OnDraw 去画图。
结论是,改善同步更新以及绘图效率的前置工作如下:
1。 定义hint 的数据类型,用以描述已遭修改的资料区域。
2。 当使用者透过View 改变了Document 内容,程序应该产生一个hint ,描述此
一修改,并以它做为参数,调用UpdateAllViews。
3。 改写CMyView::OnUpdate,利用hint 设计高效率绘图动作,使hint 描述区之
外的区域不要重画。
在View 中定义一个hint
以Scribble 为例,当使用者加上一段线条,如果我们计算出包围此一线条之最小四方
形,那么只有与此四方形有交集的其它线条才需要重画,如图11…3。因此在Step4 中
把hint 设计为RECT 类型,差堪为用。
631
…………………………………………………………Page 694……………………………………………………………
第篇 深入 MFC 程式設計
效率考量上,当然我们还可以精益求精取得各线条与此四方形的交点,然后只重绘四方
形内部的那一部份即可,但这么做是否动用太多计算,是否使工程太过复杂以至于不划
算,你可得谨慎评估。
#5
#1
#2 #4
#3
图11…3 在rect。SCB:1 窗口中新增一线条#5 ,那么,只有与虚线四方形 (此
四方形将#5 包起来) 有交集之其它线条, 也就是#1 和#4 , 才
有必要在rectSCB:2 窗口中重画。
前面曾说UpdateAllViews 函数的第三个参数必须是CObject 衍生对象之指针。由于本例
十分单纯,与其为了Hint 特别再设计一个类别,勿宁在CStroke 中增加一个变量(事
实上是一个CRect 对象),用以表示前述之hint 四方形,那么每一条线条就外罩了一
个小小的四方壳。但是我们不能把CRect 对象指针直接当做参数来传,因为CRect 并
不衍生自CObject。稍后我会说明该怎么做。
可以预期的是,日后一定需要一一从每一线条中取出这个「外围四方形」,所以现在先
声明并定义一个名为GetBoundingRect 的函数。另外再声明一个FinishStroke 函数,其
作用主要是计算这四方形尺寸。稍后我会详细解释这些函数的行为。
632
…………………………………………………………Page 695……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
// in SCRIBBLEDOC。H
class CStroke : public CObject
{
。。。
public:
UINT m_nPenWidth;
CDWordArray m_pointArray;
CRect m_rectBounding; // smallest rect that surrounds all
// of the points in the stroke
public:
CRect& GetBoundingRect() { return m_rectBounding; }
void FinishStroke();
。。。
};
我想你早已熟悉了Scribble Document 的数据结构。为了应付Step4 的需要,现在每一
线条必须多一个成员变量,那是一个CRect 对象,如图11…4 所示。
CScribble Step4 Document ::
::
CObList 对象
CObList 的每个元素都是一个CObject 指针。
我们令它指向CStroke 对象。CStroke 衍生自
文件内含一个COblist 串行 CObject。
CObList m_strokeList
CObList m_strokeList
包围整个线条的
“外围四方形”
UINT m_nPenWidth CArray 对象
(代表笔的宽度)
(线条可以CPoint 数组表示)
图11…4 CScribbleDoc 对象中的各项资料
633
…………………………………………………………Page 696……………………………………………………………
第篇 深入 MFC 程式設計
设计观念分析完毕,现在动手吧。我们必须在SCRIBDOC。CPP 中的Document 初始化
动作以及文件读写动作都加入m_rectBounding 这个新成员:
// in SCRIBDOC。CPP
IMPLEMENT_SERIAL(CStroke; CObject; 2) //
注意schema no。 改变为2
CStroke::CStroke(UINT nPenWidth)
{
m_nPenWidth = nPenWidth;
m_rectBounding。SetRectEmpty();
}
void CStroke::Serialize(CArchive& ar)
{
if (ar。IsStoring())
{
ar m_rectBounding;
WORD w;
ar 》》 w;
m_nPenWidth = w;
m_pointArray。Serialize(ar);
}
}
如果我们改变了文件读写的格式,我们就应该改变schema number (可视为版本号码)。
由于Scribble 资料文件(。SCB)格式改变了,多了一个m_rectBounding ,所以我们在
IMPLEMENT_SERIAL 宏中改变Document 的Schema no。 , 以便让不同版本的
Scribble 程序识得不同版本的文件档。如果你以Scribble Step3 读取Step4 所产生的文
件,会因为Schema 号码的不同而得到这样的消息:
634
…………………………………………………………Page 697……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
我们还需要一个函数,用以计算「线条之最小外包四方形」,这件事情当然是在线条完
成后进行之,所以此一函数命名为FinishStroke。每当一笔画结束(鼠标左键放开,产生
WM_LBUTTONUP ),OnLButtonUp 就调用FinishStroke 让它计算边界。计算方法很直
接,取出线条中的坐标点,比大小而已:
// in SCRIBDOC。CPP
void CStroke::FinishStroke()
{
计算外围四方形。为了灵巧而高效率的重绘动作,这是必要的。
//
if (m_pointArray。GetSize()==0)
{
m_rectBounding。SetRectEmpty();
return;
}
CPoint pt = m_pointArray'0';
m_rectBounding = CRect(pt。x; pt。y; pt。x; pt。y);
for (int i=1; i 《 m_pointArray。GetSize(); i++)
{
//
如果点在四方形之外,那么就将四方形膨胀,以包含该点。
pt = m_pointArray'i';
m_rectBounding。left = min(m_rectBounding。left; pt。x);
m_rectBounding。right = max(m_rectBounding。right; pt。x);
m_rectBounding。top = min(m_rectBounding。top; pt。y);
m_rectBounding。bottom = max(m_rectBounding。bottom; pt。y);
}
// 在四方形之外再加上笔的宽度。
m_rectBounding。InflateRect(CSize(m_nPenWidth; m_nPenWidth));
return;
}
把hint 传给OnUpdate
下一步骤是想办法把hint 交给UpdateAllViews。让我们想想什么时候Scribble 的资料
开始产生改变?答案是鼠标左键按下时!或许你会以为要在OnLButtonDown 中调用
CDocument::UpdateAllViews。这个猜测的论点可以成立但是结果错误,因为左键按下后还
635
…………………………………………………………Page 698……………………………………………………………
第篇 深入 MFC 程式設計
有一连串的鼠标轨迹移动,每次移动都导至资料改变,新的点不断被加上去。如果我们
等左键放开,线条完成,再来调用UpdateAllViews,事情会比较单纯。因此Scribble Step4
是在OnButtonUp 中调用UpdateAllViews。当然我们现在就可以预想得到,一笔画完成
之前,同一Document 的其它Views 没有办法实时显示最新资料。
下面是OnButtonUp 的修改内容:
void CScribbleView::OnLButtonUp(UINT; CPoint point)
{
。。。
m_pStrokeCur…》m_pointArray。Add(point);
// 已完成加点的动作,现在可以计算外围四方形了
m_pStrokeCur…》FinishStroke();
// 通知其它的views,使它们得以修改它们的图形。
pDoc…》UpdateAllViews(this; 0L; m_pStrokeCur);
。。。
return;
}
程序逻辑至为简单,唯一需要说明的是UpdateAllViews 的第三个参数(hint ),原本我
们只需设此参数为m_rectBounding ,即可满足需求,但MFC 规定,第三参数必须是一个
CObject 指针,而CRect 并不衍生自CObject,所以我们干脆就把目前正作用中的整个
线条(也就是m_pStrokeCur )传过去算了。CStroke 的确是衍生自CObject !
// in SCRIBBLEVIEW。H
class CScribbleView : public CScrollView
{
protected:
CStroke* m_pStrokeCur; // the stroke in progress
。。。
};
// in SCRIBBLEVIEW。CPP
void CScribbleView::OnLButtonDown(UINT; CPoint point)
{
。。。
m_pStrokeCur = GetDocument()…》NewStroke();
m_pStrokeCur…》m_pointArray。Add(point);
636
…………………………………………………………Page 699……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
。。。
}
void CScribbleView::OnMouseMove(UINT; CPoint point)
{
。。。
m_pStrokeCur…》m_pointArray。Add(point);
。。。
}
void CScribbleView::OnLButtonUp(UINT; CPoint point)
{
。。。
m_pStrokeCur…》m_pointArray。Add(point);
m_pStrokeCur…》FinishStroke();
pDoc…》UpdateAllViews(this; 0L; m_pStrokeCur);
。。。
}
UpdateAllViews 会巡访CScribbleDoc 所连接的每一个Views (始作俑者那个View 除
外),调用它们的OnUpdate 函数,并把hint 做为参数之一传递过去。
利用 hint 增加重绘效率
预设情况下,OnUpdate 所收到的无效区(也就是重绘区),是Do
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!