Visual
C++中 的MFC文档视图结构为我们提供了打印和打印预览程序结构框架,使得我们只需在OnPrint或OnDraw等重载函数中添加相关代码就可实现文档内容或 图像的打印和打印预览功能。但是,如果程序仅仅是用来实现ASCII文档内容的显示和打印,那么就没有必要从头开始,若能在CEditView框架基础上 进行程序设计,即可起到事半功倍的效果。
一、 CEditView程序框架的功能特点
在用MFC AppWizard(exe)创建一个单文档或多文档应用程序的过程中,若在向导的第六步将视图类的基类选定为CEditView,那么该应用程序就具有 文档的自动显示、编辑、查找和替换、剪贴板的剪切、复制和粘贴、打印以及打印预览等功能。(作为示例,设这里创建的是单文档应用程序Ex_Prn1)
但是,CEditView也存在下列缺陷:
(1) CEditView不具有所见即所得编辑功能。
(2) CEditView只能将文本作单一字体的显示,不支持特殊格式的字符。
(3) CEditView可以容纳的文本总数有限,在32位 Windows中最多不超过1M。
(4) 打印和打印预览功能还很勉强。
因此,很有必要在CEditView基础上进行更深入的程序设计,尤其是在打印和打印预览方面。
二、 打印和打印预览的程序设计
完整的打印和打印预览设计工作包括控制页边距和行距、设计页眉页脚、控制打印字体、选择打印模式、多页打印以及预览功能实现等。好在CEditView 已经实现了多页打印和预览功能,因此,我们只要在此基础上添加页边距设置、页眉页脚以及控制打印字体等功能,就一定能满足绝大多数ASCII文档打印的需 要。
1.设置页边距
页边距是指打印的文本区域与打印纸边界之间的距离,包括左、右、上和下边距。设置时可参考 CPrintInfo的成员变量m_rectDraw的数值,但m_rectDraw的数值表示的是有效打印区域,它本身与打印纸边界有一定的边距,这个 边距是打印机自身造成的,因此称之为物理边距,并且这些物理边距在不同大小的纸张中是不一样的,因此首先要获取这些数值。这时就需要调用全局函数 GetDeviceCaps,它的原型如下:
int GetDeviceCaps( HDC hdc, int nIndex);
其中,hdc用来指定设备环境句柄,nIndex用来指定要获取的参量索引,对于打印机而言,它常常需要下列的预定义值:
LOGPIXELSX 打印机水平分辨率
LOGPIXELSY 打印机垂直分辨率
PHYSICALWIDTH 打印纸的实际宽度
PHYSICALHEIGHT 打印纸的实际高度
PHYSICALOFFSETX 实际可打印区域的物理左边距
PHYSICALOFFSETY 实际可打印区域的物理上边距
需要说明的是,若一张打印纸的大小为A4(210 x 297毫米),且打印机的分辨率为300 x 300dpi,当指定函数的参数值为PHYSICALWIDTH时,则返回的值不是210毫米,而是2480。这个结果是这样计算来的:首先将毫米单位转 换成英寸,即210毫米变成8.267英寸,然后乘以300dpi。
下面的函数代码就是用来设置页边距,并且还计算页面的物理边距:
需要说明的是,由于CEditView中的设置环境映射模式是MM_TEXT,即逻辑坐标和设备坐标相同,因此需要通过LPtoDP和DPtoLP函数在逻辑坐标(LP)和设备坐标(DP)之间进行转换。
2.页眉和页脚
打印文档时,往往需要打印文档的标题及页码或其他内容的页眉和页脚。我们知道,在视图类的函数OnPrint中处理页 眉和页脚是最合适的,因为每打印一页,就调用该函数一次,且只在打印过程中调用。有时,为了避免与正文重合,还需要调整CPrintInfo中的成员变量 m_rectDraw的值。例如下面的代码:
这样,在用户视图类的构造函数中添加LOGFONT类型的成员变量m_lfHead和m_lfFont的下列初始化代码:
到这里,编译并运行程序后,打开一个文档,选择"文件"|"打印预览"菜单命令就可以看到效果了。但是文档显示的字体还需要进行设置,这比较简单。只需 添加个菜单项(设为ID_VIEW_FONT),然后用ClassWizard在CEx_Prn1View类添加该命令的消息映射函数,并添加下列代码:
3.重置TAB值
在CEditView中,默认的Tab值等于8个字符。但实际情况的Tab值往往是4个字符,所以需要重设这个Tab值。
CEditView::SetTabStops就是这样的函数,但MSDN对其解释令人费解,什么"设置的Tab值是以对话框点为单位的"等等。实际 上,只要打开MFC的源代码文件ViewEdit.cpp就可以看到默认的Tab值为8*4,显然,若设置为4个字符,则SetTabStops的参数值 应为4*4,即16。设置Tab值的代码可直接添加在 CEx_Prn1View::OnInitialUpdate函数中:
再次运行程序,最后的结果如下图所示。
三、 结束语
通过在CEditView中添加设置页边距、页眉页脚以及改变字体和Tab值等功能,不能代码量小,而且更主要的是满足了一般ASCII文档的内容显示和打印的要求。
一、 CEditView程序框架的功能特点
在用MFC AppWizard(exe)创建一个单文档或多文档应用程序的过程中,若在向导的第六步将视图类的基类选定为CEditView,那么该应用程序就具有 文档的自动显示、编辑、查找和替换、剪贴板的剪切、复制和粘贴、打印以及打印预览等功能。(作为示例,设这里创建的是单文档应用程序Ex_Prn1)
但是,CEditView也存在下列缺陷:
(1) CEditView不具有所见即所得编辑功能。
(2) CEditView只能将文本作单一字体的显示,不支持特殊格式的字符。
(3) CEditView可以容纳的文本总数有限,在32位 Windows中最多不超过1M。
(4) 打印和打印预览功能还很勉强。
因此,很有必要在CEditView基础上进行更深入的程序设计,尤其是在打印和打印预览方面。
二、 打印和打印预览的程序设计
完整的打印和打印预览设计工作包括控制页边距和行距、设计页眉页脚、控制打印字体、选择打印模式、多页打印以及预览功能实现等。好在CEditView 已经实现了多页打印和预览功能,因此,我们只要在此基础上添加页边距设置、页眉页脚以及控制打印字体等功能,就一定能满足绝大多数ASCII文档打印的需 要。
1.设置页边距
页边距是指打印的文本区域与打印纸边界之间的距离,包括左、右、上和下边距。设置时可参考 CPrintInfo的成员变量m_rectDraw的数值,但m_rectDraw的数值表示的是有效打印区域,它本身与打印纸边界有一定的边距,这个 边距是打印机自身造成的,因此称之为物理边距,并且这些物理边距在不同大小的纸张中是不一样的,因此首先要获取这些数值。这时就需要调用全局函数 GetDeviceCaps,它的原型如下:
int GetDeviceCaps( HDC hdc, int nIndex);
其中,hdc用来指定设备环境句柄,nIndex用来指定要获取的参量索引,对于打印机而言,它常常需要下列的预定义值:
LOGPIXELSX 打印机水平分辨率
LOGPIXELSY 打印机垂直分辨率
PHYSICALWIDTH 打印纸的实际宽度
PHYSICALHEIGHT 打印纸的实际高度
PHYSICALOFFSETX 实际可打印区域的物理左边距
PHYSICALOFFSETY 实际可打印区域的物理上边距
需要说明的是,若一张打印纸的大小为A4(210 x 297毫米),且打印机的分辨率为300 x 300dpi,当指定函数的参数值为PHYSICALWIDTH时,则返回的值不是210毫米,而是2480。这个结果是这样计算来的:首先将毫米单位转 换成英寸,即210毫米变成8.267英寸,然后乘以300dpi。
下面的函数代码就是用来设置页边距,并且还计算页面的物理边距:
void CEx_Prn1View::SetPageMargin(CDC *pDC, CPrintInfo *pInfo, int l, int t, int r, int b)
// l, t, r, b分别表示左上右下边距, 单位为0.1mm{ int nOldMode = pDC->GetMapMode(); pDC->SetMapMode(MM_LOMETRIC); // 计算一个设备单位等于多少0.1mm double scaleX = 254.0 / (double)GetDeviceCaps( pDC->m_hAttribDC, LOGPIXELSX); double scaleY = 254.0 / (double)GetDeviceCaps( pDC->m_hAttribDC, LOGPIXELSY); int x = GetDeviceCaps(pDC->m_hAttribDC, PHYSICALOFFSETX); int y = GetDeviceCaps(pDC->m_hAttribDC, PHYSICALOFFSETY); int w = GetDeviceCaps(pDC->m_hAttribDC, PHYSICALWIDTH); int h = GetDeviceCaps(pDC->m_hAttribDC, PHYSICALHEIGHT); int nPageWidth = (int)((double)w*scaleX + 0.5); // 纸宽,单位0.1mm int nPageHeight = (int)((double)h*scaleY + 0.5); // 纸高,单位0.1mm m_nPhyLeft = (int)((double)x*scaleX + 0.5); // 物理左边距,单位0.1mm m_nPhyTop = (int)((double)y*scaleY + 0.5); // 物理上边距,单位0.1mm pDC->DPtoLP(&pInfo->m_rectDraw); CRect rcTemp = pInfo->m_rectDraw; rcTemp.NormalizeRect(); m_nPhyRight = nPageWidth - rcTemp.Width() - m_nPhyLeft; // 物理右边距,单位0.1mm m_nPhyBottom = nPageHeight - rcTemp.Height() - m_nPhyTop; // 物理下边距,单位0.1mm // 若边距小于物理边距,则调整它们 if (l < m_nPhyLeft) l = m_nPhyLeft; if (t < m_nPhyTop) t = m_nPhyTop; if (r < m_nPhyRight) r = m_nPhyRight; if (b < m_nPhyBottom) b = m_nPhyBottom; // 计算并调整pInfo->m_rectDraw的大小 pInfo->m_rectDraw.left = l - m_nPhyLeft; pInfo->m_rectDraw.top = - t + m_nPhyTop; pInfo->m_rectDraw.right -= r - m_nPhyRight; pInfo->m_rectDraw.bottom += b - m_nPhyBottom; pDC->LPtoDP(&pInfo->m_rectDraw); pDC->SetMapMode(nOldMode); // 恢复原来的映射模式 } |
需要说明的是,由于CEditView中的设置环境映射模式是MM_TEXT,即逻辑坐标和设备坐标相同,因此需要通过LPtoDP和DPtoLP函数在逻辑坐标(LP)和设备坐标(DP)之间进行转换。
2.页眉和页脚
打印文档时,往往需要打印文档的标题及页码或其他内容的页眉和页脚。我们知道,在视图类的函数OnPrint中处理页 眉和页脚是最合适的,因为每打印一页,就调用该函数一次,且只在打印过程中调用。有时,为了避免与正文重合,还需要调整CPrintInfo中的成员变量 m_rectDraw的值。例如下面的代码:
void CEx_Prn1View::OnPrint(CDC* pDC, CPrintInfo* pInfo) { SetPageMargin(pDC, pInfo, 250, 250, 250, 250); // 页边距均为25毫米 int nOldMode = pDC->GetMapMode(); pDC->SetMapMode(MM_LOMETRIC); pDC->DPtoLP(&pInfo->m_rectDraw); // 先设置页眉字体,然后打印页眉 CFont font; font.CreateFontIndirect(&m_lfHead); CFont *oldFont = pDC->SelectObject(&font); // 计算页眉绘制的区域 int nHeadMargin = 200; // 设置页眉边距为20mm CRect rc(pInfo->m_rectDraw); rc.top = -nHeadMargin + m_nPhyTop; rc.bottom = pInfo->m_rectDraw.top; // 设页眉内容为打印的文档名 CEx_Prn1Doc* pDoc = GetDocument(); CString str = pDoc->GetPathName(); // 获取文档全名 pDC->DrawText(str, rc, DT_TOP|DT_CENTER); // 先设置页脚字体,然后打印页脚 font.Detach(); font.CreateFontIndirect(&m_lfFoot); pDC->SelectObject(&font); // 计算页脚绘制的区域 int nFootMargin = 200; // 设置页脚边距为20mm rc.top = pInfo->m_rectDraw.bottom; rc.bottom = rc.top - (nFootMargin - m_nPhyBottom); // 设页脚内容为打印的页码 str.Format("- %d -", pInfo->m_nCurPage); pDC->DrawText(str, rc, DT_BOTTOM | DT_SINGLELINE | DT_RIGHT); pDC->SelectObject(oldFont); // 恢复原来的字体 pDC->LPtoDP(&pInfo->m_rectDraw); pDC->SetMapMode(nOldMode); // 恢复原来映射模式 CEditView::OnPrint(pDC, pInfo); } |
这样,在用户视图类的构造函数中添加LOGFONT类型的成员变量m_lfHead和m_lfFont的下列初始化代码:
CEx_Prn1View::CEx_Prn1View() { memset(&m_lfHead, 0, sizeof(LOGFONT)); // 成员为0 double fontScale = 254.0/72.0; // 一个点相当于多少0.1mm // 页眉字体 m_lfHead.lfHeight = -(int)(9 * fontScale + 0.5); // 9号字 m_lfHead.lfWeight = FW_NORMAL; m_lfHead.lfCharSet = GB2312_CHARSET; strcpy((LPSTR)&(m_lfHead.lfFaceName), "楷体_GB2312"); // 页脚字体 m_lfFoot = m_lfHead; } |
到这里,编译并运行程序后,打开一个文档,选择"文件"|"打印预览"菜单命令就可以看到效果了。但是文档显示的字体还需要进行设置,这比较简单。只需 添加个菜单项(设为ID_VIEW_FONT),然后用ClassWizard在CEx_Prn1View类添加该命令的消息映射函数,并添加下列代码:
void CEx_Prn1View::OnViewFont() { CFontDialog dlg; if (dlg.DoModal() == IDOK) { LOGFONT lf; dlg.GetCurrentFont(&lf); HFONT hFont; hFont = ::CreateFontIndirect(&lf); if (hFont != NULL) SendMessage(WM_SETFONT, (WPARAM)hFont); } } |
3.重置TAB值
在CEditView中,默认的Tab值等于8个字符。但实际情况的Tab值往往是4个字符,所以需要重设这个Tab值。
CEditView::SetTabStops就是这样的函数,但MSDN对其解释令人费解,什么"设置的Tab值是以对话框点为单位的"等等。实际 上,只要打开MFC的源代码文件ViewEdit.cpp就可以看到默认的Tab值为8*4,显然,若设置为4个字符,则SetTabStops的参数值 应为4*4,即16。设置Tab值的代码可直接添加在 CEx_Prn1View::OnInitialUpdate函数中:
void CEx_Prn1View::OnInitialUpdate() { CEditView::OnInitialUpdate(); SetTabStops(4 * 4); // 设置一个停止位等于4个字符 } |
再次运行程序,最后的结果如下图所示。
三、 结束语
通过在CEditView中添加设置页边距、页眉页脚以及改变字体和Tab值等功能,不能代码量小,而且更主要的是满足了一般ASCII文档的内容显示和打印的要求。