Visual C++中的MFC文檔視圖結(jié)構(gòu)為我們提供了打印和打印預(yù)覽程序結(jié)構(gòu)框架,使得我們只需在OnPrint或OnDraw等重載函數(shù)中添加相關(guān)代碼就可實(shí)現(xiàn)文檔內(nèi)容或圖像的打印和打印預(yù)覽功能。但是,如果程序僅僅是用來(lái)實(shí)現(xiàn)ASCII文檔內(nèi)容的顯示和打印,那么就沒(méi)有必要從頭開(kāi)始,若能在CEditView框架基礎(chǔ)上進(jìn)行程序設(shè)計(jì),即可起到事半功倍的效果。
一、 CEditView程序框架的功能特點(diǎn)
在用MFC AppWizard(exe)創(chuàng)建一個(gè)單文檔或多文檔應(yīng)用程序的過(guò)程中,若在向?qū)У牡诹綄⒁晥D類的基類選定為CEditView,那么該應(yīng)用程序就具有文檔的自動(dòng)顯示、編輯、查找和替換、剪貼板的剪切、復(fù)制和粘貼、打印以及打印預(yù)覽等功能。(作為示例,設(shè)這里創(chuàng)建的是單文檔應(yīng)用程序Ex_Prn1)
但是,CEditView也存在下列缺陷:
(1) CEditView不具有所見(jiàn)即所得編輯功能。
(2) CEditView只能將文本作單一字體的顯示,不支持特殊格式的字符。
(3) CEditView可以容納的文本總數(shù)有限,在32位Windows中最多不超過(guò)1M。
(4) 打印和打印預(yù)覽功能還很勉強(qiáng)。
因此,很有必要在CEditView基礎(chǔ)上進(jìn)行更深入的程序設(shè)計(jì),尤其是在打印和打印預(yù)覽方面。
二、 打印和打印預(yù)覽的程序設(shè)計(jì)
完整的打印和打印預(yù)覽設(shè)計(jì)工作包括控制頁(yè)邊距和行距、設(shè)計(jì)頁(yè)眉頁(yè)腳、控制打印字體、選擇打印模式、多頁(yè)打印以及預(yù)覽功能實(shí)現(xiàn)等。好在CEditView已經(jīng)實(shí)現(xiàn)了多頁(yè)打印和預(yù)覽功能,因此,我們只要在此基礎(chǔ)上添加頁(yè)邊距設(shè)置、頁(yè)眉頁(yè)腳以及控制打印字體等功能,就一定能滿足絕大多數(shù)ASCII文檔打印的需要。
1.設(shè)置頁(yè)邊距
頁(yè)邊距是指打印的文本區(qū)域與打印紙邊界之間的距離,包括左、右、上和下邊距。設(shè)置時(shí)可參考CPrintInfo的成員變量m_rectDraw的數(shù)值,但m_rectDraw的數(shù)值表示的是有效打印區(qū)域,它本身與打印紙邊界有一定的邊距,這個(gè)邊距是打印機(jī)自身造成的,因此稱之為物理邊距,并且這些物理邊距在不同大小的紙張中是不一樣的,因此首先要獲取這些數(shù)值。這時(shí)就需要調(diào)用全局函數(shù)GetDeviceCaps,它的原型如下:
int GetDeviceCaps( HDC hdc, int nIndex);
其中,hdc用來(lái)指定設(shè)備環(huán)境句柄,nIndex用來(lái)指定要獲取的參量索引,對(duì)于打印機(jī)而言,它常常需要下列的預(yù)定義值:
LOGPIXELSX 打印機(jī)水平分辨率
LOGPIXELSY 打印機(jī)垂直分辨率
PHYSICALWIDTH 打印紙的實(shí)際寬度
PHYSICALHEIGHT 打印紙的實(shí)際高度
PHYSICALOFFSETX 實(shí)際可打印區(qū)域的物理左邊距
PHYSICALOFFSETY 實(shí)際可打印區(qū)域的物理上邊距
需要說(shuō)明的是,若一張打印紙的大小為A4(210 x 297毫米),且打印機(jī)的分辨率為300 x 300dpi,當(dāng)指定函數(shù)的參數(shù)值為PHYSICALWIDTH時(shí),則返回的值不是210毫米,而是2480。這個(gè)結(jié)果是這樣計(jì)算來(lái)的:首先將毫米單位轉(zhuǎn)換成英寸,即210毫米變成8.267英寸,然后乘以300dpi。
下面的函數(shù)代碼就是用來(lái)設(shè)置頁(yè)邊距,并且還計(jì)算頁(yè)面的物理邊距:
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); // 計(jì)算一個(gè)設(shè)備單位等于多少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 // 若邊距小于物理邊距,則調(diào)整它們 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; // 計(jì)算并調(diào)整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); // 恢復(fù)原來(lái)的映射模式 } | 需要說(shuō)明的是,由于CEditView中的設(shè)置環(huán)境映射模式是MM_TEXT,即邏輯坐標(biāo)和設(shè)備坐標(biāo)相同,因此需要通過(guò)LPtoDP和DPtoLP函數(shù)在邏輯坐標(biāo)(LP)和設(shè)備坐標(biāo)(DP)之間進(jìn)行轉(zhuǎn)換。
2.頁(yè)眉和頁(yè)腳
打印文檔時(shí),往往需要打印文檔的標(biāo)題及頁(yè)碼或其他內(nèi)容的頁(yè)眉和頁(yè)腳。我們知道,在視圖類的函數(shù)OnPrint中處理頁(yè)眉和頁(yè)腳是最合適的,因?yàn)槊看蛴∫豁?yè),就調(diào)用該函數(shù)一次,且只在打印過(guò)程中調(diào)用。有時(shí),為了避免與正文重合,還需要調(diào)整CPrintInfo中的成員變量m_rectDraw的值。例如下面的代碼:
void CEx_Prn1View::OnPrint(CDC* pDC, CPrintInfo* pInfo) { SetPageMargin(pDC, pInfo, 250, 250, 250, 250); // 頁(yè)邊距均為25毫米 int nOldMode = pDC->GetMapMode(); pDC->SetMapMode(MM_LOMETRIC); pDC->DPtoLP(&pInfo->m_rectDraw); // 先設(shè)置頁(yè)眉字體,然后打印頁(yè)眉 CFont font; font.CreateFontIndirect(&m_lfHead); CFont *oldFont = pDC->SelectObject(&font); // 計(jì)算頁(yè)眉繪制的區(qū)域 int nHeadMargin = 200; // 設(shè)置頁(yè)眉邊距為20mm CRect rc(pInfo->m_rectDraw); rc.top = -nHeadMargin + m_nPhyTop; rc.bottom = pInfo->m_rectDraw.top; // 設(shè)頁(yè)眉內(nèi)容為打印的文檔名 CEx_Prn1Doc* pDoc = GetDocument(); CString str = pDoc->GetPathName(); // 獲取文檔全名 pDC->DrawText(str, rc, DT_TOP|DT_CENTER); // 先設(shè)置頁(yè)腳字體,然后打印頁(yè)腳 font.Detach(); font.CreateFontIndirect(&m_lfFoot); pDC->SelectObject(&font); // 計(jì)算頁(yè)腳繪制的區(qū)域 int nFootMargin = 200; // 設(shè)置頁(yè)腳邊距為20mm rc.top = pInfo->m_rectDraw.bottom; rc.bottom = rc.top - (nFootMargin - m_nPhyBottom); // 設(shè)頁(yè)腳內(nèi)容為打印的頁(yè)碼 str.Format("- %d -", pInfo->m_nCurPage); pDC->DrawText(str, rc, DT_BOTTOM | DT_SINGLELINE | DT_RIGHT); pDC->SelectObject(oldFont); // 恢復(fù)原來(lái)的字體 pDC->LPtoDP(&pInfo->m_rectDraw); pDC->SetMapMode(nOldMode); // 恢復(fù)原來(lái)映射模式 CEditView::OnPrint(pDC, pInfo); } | 這樣,在用戶視圖類的構(gòu)造函數(shù)中添加LOGFONT類型的成員變量m_lfHead和m_lfFont的下列初始化代碼:
CEx_Prn1View::CEx_Prn1View() { memset(&m_lfHead, 0, sizeof(LOGFONT)); // 成員為0 double fontScale = 254.0/72.0; // 一個(gè)點(diǎn)相當(dāng)于多少0.1mm // 頁(yè)眉字體 m_lfHead.lfHeight = -(int)(9 * fontScale + 0.5); // 9號(hào)字 m_lfHead.lfWeight = FW_NORMAL; m_lfHead.lfCharSet = GB2312_CHARSET; strcpy((LPSTR)&(m_lfHead.lfFaceName), "楷體_GB2312"); // 頁(yè)腳字體 m_lfFoot = m_lfHead; }
| 到這里,編譯并運(yùn)行程序后,打開(kāi)一個(gè)文檔,選擇"文件"|"打印預(yù)覽"菜單命令就可以看到效果了。但是文檔顯示的字體還需要進(jìn)行設(shè)置,這比較簡(jiǎn)單。只需添加個(gè)菜單項(xiàng)(設(shè)為ID_VIEW_FONT),然后用ClassWizard在CEx_Prn1View類添加該命令的消息映射函數(shù),并添加下列代碼:
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中,默認(rèn)的Tab值等于8個(gè)字符。但實(shí)際情況的Tab值往往是4個(gè)字符,所以需要重設(shè)這個(gè)Tab值。
CEditView::SetTabStops就是這樣的函數(shù),但MSDN對(duì)其解釋令人費(fèi)解,什么"設(shè)置的Tab值是以對(duì)話框點(diǎn)為單位的"等等。實(shí)際上,只要打開(kāi)MFC的源代碼文件ViewEdit.cpp就可以看到默認(rèn)的Tab值為8*4,顯然,若設(shè)置為4個(gè)字符,則SetTabStops的參數(shù)值應(yīng)為4*4,即16。設(shè)置Tab值的代碼可直接添加在 CEx_Prn1View::OnInitialUpdate函數(shù)中:
void CEx_Prn1View::OnInitialUpdate() { CEditView::OnInitialUpdate(); SetTabStops(4 * 4); // 設(shè)置一個(gè)停止位等于4個(gè)字符 } | 再次運(yùn)行程序,最后的結(jié)果如上圖所示。
三、 結(jié)束語(yǔ)
通過(guò)在CEditView中添加設(shè)置頁(yè)邊距、頁(yè)眉頁(yè)腳以及改變字體和Tab值等功能,不能代碼量小,而且更主要的是滿足了一般ASCII文檔的內(nèi)容顯示和打印的要求。
|