深入浅出MFC第2版(PDF格式)-第128部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
我们还需要一个函数,用以计算「线条之最小外包四方形」,这件事情当然是在线条完
成后进行之,所以此一函数命名为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 所收到的无效区(也就是重绘区),是Document Frame 窗口的
整个内部。但谁都知道,原已存在而且没有变化的图形再重绘也只是浪费时间而已。上
一节你已看到Scribble 每加上一整个线条, 就在OnLButtonUp 函数中调用
UpdateAllViews 函数,并且把整个线条(内含其四方边界)传过去,因此我们可以想办法
在OnUpdate 中重绘这个四方形小区域就好。
话说回来,如何能够只重绘一个小区域就好呢?我们可以一一取出Document 中每一线
条的四方边界,与新线条的四方边界比较,若有交点就重绘该线条。CRect 有一个
IntersectRect 函数正适合用来计算四方形交集。
但是有一点必须注意,绘图动作不是集中在OnDraw 吗?因此OnUpdate 和OnDraw 之
间的分工有必要厘清。前面数个版本中这两个函数的动作是:
637
…………………………………………………………Page 700……………………………………………………………
第篇 深入 MFC 程式設計
OnUpdate 啥也没做。事实上CScribbleView 原本根本没有改写此一函数。
OnDraw 迭代取得Document 中的每一线条,并调用CStroke::DrawStroke 将线条
绘出。
Scribble Step4 之中,这两个函数的动作如下:
OnUpdate 判断Framework 传来的hint 是否为CStroke 对象。如果是,设定
无效区域(重绘区域)为该线条的外围四方形;如果不是,设定无效区域为整
个窗口区域。「设定无效区域」( 也就是调用CWnd::InvalidateRect ) 会引发
WM_PAINT ,于是引发OnDraw 。
OnDraw 迭代取得Document 中的每一线条,并调用CStroke::GetBoundingRect
取线条之外围四方形,如果与「无效区域」有交集,就调用CStroke::DrawStroke
绘出整个线条。如果没有交集,就跳过不画。
以下是新增的OnUpdate 函数:
// in SCRIBVW。CPP
void CScribbleView::OnUpdate(CView* /* pSender */; LPARAM /* lHint */;
CObject* pHint)
{
// Document 通知View 说,某些资料已经改变了
if (pHint != NULL)
{
if (pHint…》IsKindOf(RUNTIME_CLASS(CStroke)))
{
// hint提示我们哪一线条被加入(或被修改),所以我们要把该线条的
// 外围四方形设为无效区。
CStroke* pStroke = (CStroke*)pHint;
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectInvalid = pStroke…》GetBoundingRect();
dc。LPtoDP(&rectInvalid);
InvalidateRect(&rectInvalid);
return;
}
}
//如果我们不能解释hint 内容(也就是说它不是我们所预期的
638
…………………………………………………………Page 701……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
对象),那就让整个窗口重绘吧(把整个窗口设为无效区)。
// CStroke
Invalidate(TRUE);
return;
}
为什么OnUpdate 之中要调用OnPrepareDC?这关系到滚动条,我将在介绍分裂窗口时再
说明。另,GetBoundingRect 动作如下:
CRect& GetBoundingRect() { return m_rectBounding; }
OnDraw 函数也为了高效能重绘动作之故,做了以下修改。阴影部份是与Scribble Step3
不同之处:
// SCRIBVW。CPP
void CScribbleView::OnDraw(CDC* pDC)
{
CScribbleDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// 取得窗口的无效区。如果是在打印状态情况下,则取
// printer DC 的截割区(clipping region)。
CRect rectClip;
CRect rectStroke;
pDC…》GetClipBox(&rectClip);
//
// 注意:CScrollView::OnPrepare 已经在OnDraw 被调用之前先一步
// 调整了DC 原点,用以反应出目前的卷动位置。关于CScrollView,
// 下一节就会提到。
//调用CStroke::DrawStroke 完成无效区中各线条的绘图动作
CTypedPtrList& strokeList = pDoc…》m_strokeList;
POSITION pos = strokeList。GetHeadPosition();
while (pos != NULL)
{
CStroke* pStroke = strokeList。GetNext(pos);
rectStroke = pStroke…》GetBoundingRect();
if (!rectStroke。IntersectRect(&rectStroke; &rectClip))
continue;
pStroke…》DrawStroke(pDC);
}
}
639
…………………………………………………………Page 702……………………………………………………………
第篇 深入 MFC 程式設計
可卷动的窗口:CScrollView
到目前为止我们还没有办法观察一张比窗口还大的图,因为我们没有滚动条。
一个View 窗口没有滚动条,是很糟糕的事,因为通常Docume