友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第92部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
…………………………………………………………Page 748……………………………………………………………
第篇 深入 MFC 程式設計
CPrintInfo 成员名称 参考到的打印页
GetMinPage/SetMinPage Document 中的第一页
GetMaxPage/SetMaxPage Document 中的最后一页
GetFromPage 将被印出的第一页(出现在【打印】对话框,图12…1b)
GetToPage 将被印出的最后一页(出现在【打印】对话框)
m_nCurPage 目前正被印出的一页(出现在【打印状态】对话框)
m_nNumPreviewPages 预览窗口中的页数(稍后将讨论之)
注:页码从1 (而不是0 )开始。
CPrintInfo 结构中记录的「页」数,指的是打印机的页数;Framework 针对每一「页」
调用OnPrepareDC 以及OnPrint 时,所指的「页」也是打印机的页。当你改写
OnPreparePrinting 时指定Document 的长度,所用的单位也是打印机的「页」。如果
Document 的一页恰等于打印机的一页(一张纸),事情就单纯了;如果不是,你必须在
两者之间做转换。
Scribble Step5 设定让每一份Document 使用打印机的两页。第一页只是单纯印出文件名
称(文件名称),第二页才是文件内容。假设我利用View 窗口卷动杆在整个Document
四周画一四方圈的话,我希望这一四方圈落入第二页(第二张纸)中。当然,边界留白
必须考虑在内,如图12…6。除此之外,我希望第二页(文件内容)最顶端留一点空间,
做为表头。本例在表头中放的是文件名称。
686
…………………………………………………………Page 749……………………………………………………………
第 12 章 列印與預樱А�
图12…6 Scribble Step5 的每一份文件打印时有两页,第一页是文件名称,
第二页是文件内容, 最顶端留有一个表头。
配置GDI 绘图工具
绘图难免需要各式各样的笔、刷、颜色、字形、工具。这些GDI 资源都会占用内存,
而且是GDI 模块的heap 。虽说Windows 95 对于USER 模块和GDI 模块的heap 已
有大幅改善,使用32 位heap ,不再局限64KB,但我们当然仍然不希望看到浪费的情
况发生,因此最好的方式就是在打印之前配置这些GDI 绘图对象,并在打印后立刻释放。
看看图12…5,配置GDI 对象的最理想时机显然是OnBeginPrinting,两个理由:
1。 每当Framework 开始一份新的打印工作,它就会调用此函数一次,因此不同
打印工作所需的不同工具可在此有个替换。
2。 此函数的参数是一个和「打印机DC 」有附着关系的CDC 对象指针,我们直接
从此一CDC 对象中配置绘图工具即可。
687
…………………………………………………………Page 750……………………………………………………………
第篇 深入 MFC 程式設計
配置得来的GDI 对象可以储存在View 的成员变量中,供整个打印过程使用。使用时
机当然是OnPrint。如果你必须对不同的打印页使用不同的GDI 对象,CPrintInfo 中的
m_nCurPage 可以帮你做出正确的决定。
释放GDI 对象的最理想时机当然是在OnEndPrinting,这是每当一份打印工作结束后,
Application Framework 会调用的函数。
Scribble 没有使用什么特殊的绘图工具,因此下面这两个虚拟函数也就没有修改,完全
保留AppWizard 当初给我们的样子:
void CScribbleView::OnBeginPrinting(CDC* /*pDC*/; CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CScribbleView::OnEndPrinting(CDC* /*pDC*/; CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}
尺寸与方向:关于映射模式(坐标系统)
回忆所谓的坐标系统,我已经在上一章描述过CScrollView 如何为了卷动效果而改变座
标系统的原点。除了改变原点,我们甚至可以改变坐标系统的单位长度,乃至于改变座
标系统的横纵比例(scale)。这些就是这一节要讨论的重点。
Document 有大小可言吗?有的,在打印过程中,为了计算Document 对应到打印机的页
数,我们需要Document 的尺寸。CScribbleDoc 的成员变量m_sizeDoc ,就是用来记录
Document 的大小。它是一个CSize 对象:
688
…………………………………………………………Page 751……………………………………………………………
第 12 章 列印與預樱А�
800
9
void CScribbleDoc::InitDocument()
void CScribbleDoc::InitDocument()
0
0
{{ Document
。。。
。。。
m_sizeDoc = CSize(800;900);
m_sizeDoc = CSize(800;900);
}
}
事实上,所谓「逻辑坐标」原本是没有大小的,如果我们说一份Document 宽800 高
900;那么若逻辑坐标的单位是英寸;这就是 8 英寸宽 9 英寸高;若逻辑座标的单位是
公分,这就是8 公分宽9 公分高。如果逻辑单位是图素(Pixel )呢?那就是800 个图
素宽900 个图素高。图素的大小随着输出装置而改变,在14 Super VGA (1024x768)
显示器上,800x900 个图素大约是21。1 公分宽23。6 公分高,而在一部300 DPI (Dot Per
Inch,每英嫉闶'y )的激光打印机上,将是2…2/3 英伎3 英几摺
预设情况下GDI 绘图函数使用MM_TEXT 映射模式(Mapping Mode ,也就是坐标系
统,注),于是逻辑坐标等于装置坐标,也就是说一个逻辑单位是一个图素。如果不重
新设定映射模式,可以想见屏幕上的图形一放到300 DPI 打印机上都嫌太小。
解决的方法很简单:设定一种与真实世界相符的逻辑坐标系统。Windows 提供的八种映
像模式中有七种是所谓的metric 映射模式,它们的逻辑单位都建立在公分或英嫉幕
上,这正是我们所要的。如果把OnDraw 内的绘图动作都设定在MM_LOENGLISH 映射
模式上(每单位0。01 英迹敲床宦凼涑龅狡聊簧匣虻酱蛴』隙蓟竦孟嗤某叨取
真正要为「多少图点才能画出一英汲ぁ股四越畹氖亲爸们'y动程序,不是我们。
689
…………………………………………………………Page 752……………………………………………………………
第篇 深入 MFC 程式設計
注:GDI 的八种映射模式及其意义如下:
MM_TEXT :以图素(pixel )为单位,Y 轴向下为正,X 轴向右为正。
MM_LOMETRIC :以0。1 公分为单位,Y 轴向上为正,X 轴向右为正。
MM_HIMETRIC :以0。01 公分为单位,Y 轴向上为正,X 轴向右为正。
MM_LOENGLISH :以0。01 英嘉5 ノ唬Y 轴向上为正,X 轴向右为正。
MM_HIENGLISH :以0。001 英嘉5 ノ唬Y 轴向上为正,X 轴向右为正。
MM_TWIPS:以1/1440 英嘉5 ノ唬Y 轴向上为正,X 轴向右为正。
MM_ISOTROPIC :单位长度可任意设定,Y 轴向上为正,X 轴向右为正。
MM_ANISOTROPIC :单位长度可任意设定,且X 轴单位长可以不同于Y 轴
单位长(因此圆可能变形)。Y 轴向上为正,X 轴向右为正。
回忆上一章为了卷动窗口,曾有这样的动作:
void CScribbleView::OnInitialUpdate()
{
SetScrollSizes(MM_TEXT; GetDocument()…》GetDocSize());
CScrollView::OnInitialUpdate();
}
映射模式可以在SetScrollSizes 的第一个参数指定。现在我们把它改为:
void CScribbleView::OnInitialUpdate()
{
SetScrollSizes(MM_LOENGLISH; GetDocument()…》GetDocSize());
CScrollView::OnInitialUpdate();
}
注意,OnInitialUpdate 更在OnDraw 之前被调用,也就是说我们在真正绘图动作
OnDraw 之前完成了映射模式的设定。
映射模式不仅影响逻辑单位的尺寸,也影响Y 轴坐标方向。MM_TEXT 是Y 轴向下,
MM_LOENGLISH (以及其它任何映射模式)是Y 轴向上。但,虽然有此差异,我们的
Step5 程序代码却不需为此再做更动,因为DPtoLP 已经完成了这个转换。别忘了,鼠标
690
…………………………………………………………Page 753……………………………………………………………
第 12 章 列印與預樱А�
左键传来的点坐标是先经过DPtoLP 才储存到CStroke 对象并且然后才由LineTo 画
出的。
然而,程序的某些部份还是受到了Y 轴方向改变的冲击。映射模式只会改变GDI 各相
关函数,不使用DC 的地方,就不受映射模式的影响,例如CRect 的成员函数就不知
晓所谓的映射模式。于是,本例中凡使用到CRect 的地方,要特别注意做些调整:
1。 修正「线条外围四方形」的计算方式。原计算方式是在FinishStroke 中这么做:
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));
新的计算方式是:
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 = max(m_rectBounding。top; pt。y);
m_rectBounding。bottom = min(m_rectBounding。bottom; pt。y);
}
m_rectBounding。InflateRect(CSize(m_nPenWidth; …(int)m_nPenWidth));
这是因为在Y 轴向下的系统中,四方形的最顶点位置应该是找Y 坐标最小者;而在
Y 轴向上的系统中,四方形的最顶点位置应该是找Y 坐标最大者;同理,对于四方
形的最底点亦然。
691
…………………………………………………………Page 754……………………………………………………………
第篇 深入 MFC 程式設計
0 x y
min y max y
min y
max y
0
y x
2。 我们在OnDraw 中曾经以IntersectRect 计算两个四方形是否有交集。这个函数
也是CRect 成员函数,它假设:一个四方形的底坐标Y 值必然大于顶坐标的
Y 值(这是从装置坐标,也就是MM_TEXT ,的眼光来看) ;如果事非如此,它
根本不可能找出两个四方形的交集。因此我们必须在OnDraw 中做以下修改,
把逻辑坐标改为装置坐标:
void CScribbleView::OnDraw(CDC* pDC)
{
CScribbleDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// Get the invalidated rectangle of the view; or in the case
// of printing; the clipping region of the printer dc。
CRect rectClip;
CRect rectStroke;
pDC…》GetClipBox(&rectClip);
pDC…》LPtoDP(&rectClip);
rectClip。InflateRect(1; 1); // avoid rounding to nothing
// Note: CScrollView::OnPaint() will have already adjusted the
// viewport origin before calling OnDraw(); to reflect the
// currently scrolled position。
// The view delegates the drawing of individual strokes to
// CStroke::DrawStroke()。
CTypedPtrList& strokeList = pDoc…》m_strokeList;
POSITION pos = strokeList。GetHeadPosition();
while (pos != NULL)
{
CStroke* pStroke = strokeList。GetNext(pos);
rectStroke = pStroke…》GetBoundingRect();
692
…………………………………………………………Page 755……………………………………………………………
第 12 章 列印與預樱А�
pDC…》LPtoDP(&rectStroke);
rectStroke。InflateRect(1; 1); // avoid rounding to nothing
if (!rectStroke。IntersectRect(&rectStroke; &rectClip))
continue;
pStroke…》DrawStroke(pDC);
}
}
分页
Scribble 程序的Document 大小固定是800x900,而且我们让它填满打印机的一页。因
此Scribble 并没有「将Document 分段打印」这种困扰。如果真要分段打印,Scribble 应
该改写OnPrepareDC,在其中视打印的页数调整DC 的原点和截割区域。
即便如此,Scribble 还是在分页方面加了一些动作。本例一份Document 打印时被视为
一张标题和一张图片的组合,因此打印一份Document 固定要耗掉两张打印纸。我们可
以这么设计:
BOOL CScribbleView::OnPreparePrinting(CPrintInfo* pInfo)
{
pInfo…》SetMaxPage(2); //文件总共有两页经线:
//第一页是标题页 (title page)
//第二页是文件页 (图形)
BOOL bRet = DoPreparePrinting(pInfo); // default preparation
pInfo…》m_nNumPreviewPages = 2; // Preview 2 pages at a time
// Set this value after calling DoPreparePrinting to override
// value read from 。INI file
return bRet;
}
接下来打算设计一个函数用以输出标题页,一个函数用以输出文件页。后者当然应该由
OnDraw 负责# 蛭U 馕募th页不是单纯的Document 内容,还有所谓的表头,而这
是打印时才做的东西,屏幕显示时并不需要的,所以我们希望把列打印头的工作独立于
OnDraw 之外,那么最好的安置地点就是OnPrint 了(请参考图12…5 之后的补充说明
的最后一点)。
693
…………………………………………………………Page 756……………………………………………………………
第篇 深入 MFC 程式設計
Scribble Step5 把列打印头的工作独立为一个函数。总共这三个额外的函数应该声明于
SCRIBBLEVIEW。H 中,其中的PrintPageHeader 在下一节列出。
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!