友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第118部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
//预设Document 大小为800 x 900 个屏幕图素
m_sizeDoc = CSize(800;900);
}
void CScribbleDoc::Serialize(CArchive& ar)
{
if (ar。IsStoring())
{
ar 》 m_sizeDoc;
}
m_strokeList。Serialize(ar);
}
2 将CScribbleView 的父类别由CView 改变为CScrollView。同时准备改写其虚拟函数
OnInitialUpdate,为的是稍后我们要在其中,根据Document 的大小,设定卷动范围。
// in SCRIBBLEVIEW。H
class CScribbleView : public CScrollView
{
public:
virtual void OnInitialUpdate();
。。。
643
…………………………………………………………Page 706……………………………………………………………
第篇 深入 MFC 程式設計
};
// in SCRIBBLEVIEW。CPP
IMPLEMENT_DYNCREATE(CScribbleView; CScrollView)
BEGIN_MESSAGE_MAP(CScribbleView; CScrollView)
。。。
END_MESSAGE_MAP()
3 改写OnInitialUpdate,在其中设定滚动条范围。这个函数的被调用时机是在View 第
一次附着到Document 但尚未显现时,由Framework 调用之。它会调用OnUpdate,不
带任何Hint 参数(于是lHint 是0 而pHint 是NULL )。如果你需要做任何「只做一
次」的初始化动作,而且初始化时需要Document 的资料,那么在这里做就最合适了:
// in SCRIBVW。CPP
void CScribbleView::OnInitialUpdate()
{
SetScrollSizes(MM_TEXT; GetDocument()…》GetDocSize());
//这是CScrollView 的成员函数。
}
SetScrollSizes 总共有四个参数:
int nMapMode :代表映射模式(Mapping Mode)
SIZE sizeTotal:代表文件大小
const SIZE& sizePage :代表一页大小(预设是文件大小的1/10)
const SIZE& sizeLine :代表一行大小(预设是文件大小的1/100)
本例的文件大小是固定的。另一种比较复杂的情况是可变大小,那么你就必须在文件大
小改变之后立刻调用SetScrollSizes 。
窗口上增加滚动条并不会使View 的OnDraw 负担加重。我们并不因为滚动条把观察镜头移
动到Document 的中段或尾段,而就必须在OnDraw 中重新计算绘图原点与平移向量,
原因是绘图坐标与我们所使用的DC 有关。当滚动条移动了DC 原点,CScrollView 自动
644
…………………………………………………………Page 707……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
会做调整,让资料的某一部份显示而某一部份隐藏。
让我做更详细的说明。「GDI 原点」是DC (注)的重要特征,许许多多CDC 成员函
式的绘图结果都会受它的影响。如果我们想在绘图之前(也就是进入OnDraw 之前)调
整DC , 我们可以改写虚拟函数OnPrepareDC , 因为Framework 是先调用
OnPrepareDC,然后才调用OnDraw 并把DC 传进去。好,窗口由无滚动条到增设滚动条的
过程中,之所以不必修改OnDraw 函数内容,就是因为CScrollView 已经改写了CView
的OnPrepareDC 虚拟函数。Framework 先调用CScrollView::OnPrepareDC 再调用
CScribbleView::OnDraw,所有因为滚动条而必须做的特别处理都已经在进入OnDraw 之前
完成了。
注意上面的叙述,别把CScrollView 和CSribbleView 混淆了。下图是个整理。
CWnd
CWnd
CView
CView
x CScrollView 此类别的OnPrepareDC 虚拟函数
CScrollView
会因滚动条的位置而调整DC 原点。
CScribbleView 此类别原针对「无滚动条窗口」而设计,;所以Step4 之前
CScribbleView
的View 类别是直接衍生自CView。彼时所写的OnDraw
函数内容在如今改变了继承关系后(改继承自
CScrollView),依然完全适用,原因是所有的差异性早
都在进入OnDraw 之前便由更早被Framework 调用的
CScrollView::OnPrepare 处理掉了。
DC 就是Device Context ,在Windows 中凡绘图动作之前一定要先获得一个DC ,它可
能代表屏幕,也可能代表一个窗口,或一块内存,或打印机。。。。DC 中有许多绘图所需
的元素,包括坐标系统(映射模式)、原点、绘图工具(笔、刷、颜色。。。)等等。它还
连接到低阶的输出装置驱动程序。由于DC ,我们在程序中对屏幕作画和对打印机作画
的动作才有可能完全相同。
645
…………………………………………………………Page 708……………………………………………………………
第篇 深入 MFC 程式設計
4 修正鼠标坐标。虽说OnDraw 不必因为坐标原点的变化而有任何改变,但是幕后出
力的CScrollView::OnPrepareDC 却不知道什么是Windows 消息!这话看似牛头和马
嘴,但我一点你就明白了。CScrollView::OnPrepareDC 虽然能够因着滚动条行为而改变GDI
原点,但「改变GDI 原点」这个动作却不会影响你所接收的WM_LBUTTONDOWN 或
WM_LBUTTONUP 或WM_MOUSEMOVE 的坐标值,原因是Windows 消息并非DC 的
一个成份。因此,我们作画的基础,也就是鼠标移动产生的轨迹点坐标,必须由「以视
窗绘图区左上角为原点」的窗口坐标系统,改变为「以文件左上角为原点」的逻辑坐标
系统。文件中储存的,也应该是逻辑坐标。
下面是修改坐标的程序动作。其中调用的OnPrepareDC 是哪一个类别的成员函数?想
想看,CScribbleView 衍生自CScrollView,而我们并未在CScribbleView 中改写此一函
式,所以程序中调用的是CScrollView::OnPrepareDC。
// in SCRIBVW。CPP
void CScribbleView::OnLButtonDown(UINT; CPoint point)
{
//由于CScrollView 改变了DC 原点和映射模式,所以我们必须先把
//装置坐标转换为逻辑坐标,再储存到Document 中。
CClientDC dc(this);
OnPrepareDC(&dc);
dc。DPtoLP(&point);
m_pStrokeCur = GetDocument()…》NewStroke();
m_pStrokeCur…》m_pointArray。Add(point);
SetCapture(); // 抓住鼠标
m_ptPrev = point; //做为直线绘图的第一个点
return;
}
void CScribbleView::OnLButtonUp(UINT; CPoint point)
{
。。。
if (GetCapture() != this)
return;
CScribbleDoc* pDoc = GetDocument();
CClientDC dc(this);
646
…………………………………………………………Page 709……………………………………………………………
第 11 章 View 功能之加強與重繪效率之提昇
OnPrepareDC(&dc); // 设定映射模式和DC 原点
dc。DPtoLP(&point);
。。。
}
void CScribbleView::OnMouseMove(UINT; CPoint point)
{
。。。
if (GetCapture() != this)
return;
CClientDC dc(this);
OnPrepareDC(&dc);
dc。DPtoLP(&point);
m_pStrokeCur…》m_pointArray。Add(point);
。。。
}
除了上面三个函数,还有什么函数牵扯到坐标?是的,线条四周有一个外围四方形,那
将在OnUpdate 中出现,也必须做坐标系统转换:
void CScribbleView::OnUpdate(CView* /* pSender */; LPARAM /* lHint */;
CObject* pHint)
{
if (pHint != NULL)
{
if (pHint…》IsKindOf(RUNTIME_CLASS(CStroke)))
{
// hint 的确是一个CStroke 对象。现在将其外围四方形设为重绘区
CStroke* pStroke = (CStroke*)pHint;
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectInvalid = pStroke…》GetBoundingRect();
dc。LPtoDP(&rectInvalid);
InvalidateRect(&rectInvalid);
return;
}
}
//无法识别hint;只好假设整个画面都需重绘。
Invalidate(TRUE);
return;
}
647
…………………………………………………………Page 710……………………………………………………………
第篇 深入 MFC 程式設計
注意,上面的LPtoDP 所受参数竟然不是CPoint*,而是CRect*,那是因为LPtoDP 有
多载函数(overloaded function ),既可接受点,也可接受四方形。DPtoLP 也有类似的多
载能力。
线条的外围四方形还可能出现在CStroke::FinishStroke 中,不过那里只是把线条数组中
的点拿出来比大小,决定外围四方形罢了;而你知道,线条数组的点已经在加入时做过
坐标转换了(分别在OnLButtonDown、OnMouseMove、OnLButtonUp 函数中的AddPoint
动作之前)。
至此,Document 的资料格式比起Step1,有了大幅的变动。让我们再次分析文件档的格
式,以期获得更深入的认识与印证。我以图11…6a 为例,共四条线段,宽度分别是2; 5;
10; 20 (十进制)。分析内容显示在图11…6b。
CArchive::WriteObject
CArchive::ReadObject
Turbo Dump Version 3。1 Copyright (c) 1988; 1992 Borland International
Display of File PENWIDTH。SCB
000000: 20 03 00 00 84 03 00 00 04 00 FF FF 02 00 07 00 。。。。。。。。。。。。。。。
000010: 43 53 74 72 6F 6B 65 28 00 00 00 15 00 00 00 2C CStroke(。。。。。。。;
000020: 00 00 00 19 00 00 00 02 00 02 00 2A 00 00 00 17 。。。。。。。。。。。*。。。。
000030: 00 00 00 2A 00 00 00 17 00 00 00 01 80 24 00 00 。。。*。。。。。。。。。。。
000040: 00 26 00 00 00 2E 00 00 00 30 00 00 00 05 00 02 。&。。。。。。。0。。。。。。
000050: 00 29 00 00 00 2B 00 00 00 29 00 00 00 2B 00 00 。)。。。+。。。)。。。+。。
000060: 00 01 80 1F 00 00 00 3D 00 00 00 33 00 00 00 51 。。。。。。。=。。。3。。。Q
000070: 00 00 00 0A 00 02 00 29 00 00 00 47 00 00 00 29 。。。。。。。)。。。G。。。)
000080: 00 00 00 47 00 00 00 01 80 15 00 00 00 54 00 00 。。。G。。。。。。。。。T。。
000090: 00 3D 00 00 00 7C 00 00 00 14 00 02 00 29 00 00 。=。。。|。。。。。。。)。。
0000A0: 00 68 00 00 00 29 00 00 00 68 00 00 00 00 00 00 。h。。。)。。。h。。。。。。
图11…6a 四条线
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!