# 5.4 使用打印框架
我們已經介紹過,可以直接使用wxPrinterDC來進行打印。不過,一個更靈活的方法是使用wxWidgets提供的打印框架來驅動打印機。要使用這個框架,最主要的任務就是要實現一個wxPrintout的派生類,重載其成員函數以便告訴wxWidgets怎樣打印一頁(OnPrintPage), 總共有多少頁(GetPageInfo),進行頁面設置(OnPreparePrinting)等等。而wxWidgets框架則負責顯示打印對話框,創建打印設備上下文和調用適當的wxPrintout的函數。同一個wxPrintout類將被打印和預覽功能一起使用。
當要開始打印的時候,一個wxPrintout對象實例被傳遞給wxPrinter對象,然后將調用Print函數開始打印過程,并且在準備打印用戶指定的那些頁面前顯示一個打印對話框。如下面例子中的那樣。
```
// 一個全局變量用來存儲打印相關的設置信息
wxPrintDialogData g_printDialogData;
// 用戶從主菜單中選擇打印命令以后
void MyFrame::OnPrint(wxCommandEvent& event)
{
wxPrinter printer(& g_printDialogData);
MyPrintout printout(wxT("My printout"));
if (!printer.Print(this, &printout, true))
{
if (wxPrinter::GetLastError() == wxPRINTER_ERROR)
wxMessageBox(wxT("There was a problem printing.\nPerhaps your current printer
is not set correctly?"), wxT("Printing"), wxOK);
else
wxMessageBox(wxT("You cancelled printing"),
wxT("Printing"), wxOK);
}
else
{
(*g_printDialogData) = printer.GetPrintDialogData();
}
}
```
因為打印函數在所有的頁面都已經被渲染和發送到打印機以后才會返回,因此wxPrintout對象可以以局部變量的方式創建。
wxPrintDialogData對象用來存儲所有打印有關的數據,比如用戶選擇的頁面和份數等。將其保存在一個全局變量中并且在下次調用的時候傳遞給wxPrinter對象是一個不錯的習慣,因此象上面代碼中演示的那樣,在創建wxPrinter的時候傳遞給它一份全局配置數據的指針,并在Print函數成功返回以后將當前的打印數據保存在全局變量里(當然,一個更專業的作法是將其保存在你的應用程序類中)。關于使用打印和頁面設置對話框的詳情請參考第8章,?使用標準對話框?。
如果要創建打印預覽,你需要創建一個wxPrintPreview對象,給這個對象傳遞兩個wxPrintout對象作為參數,一個用于預覽,一個則用于在用戶預覽的時候直接請求打印,同樣的,你可以傳遞一個全局的wxPrintDialogData對象給預覽對象以便預覽類使用用戶以前選擇的打印設置數據。然后將這個預覽類傳遞給wxPreviewFrame,然后調用wxPreviewFrame的Initialize函數和 Show函數顯示這個窗口,如下所示:
```
// 用戶選擇打印預覽菜單命令
void MyFrame::OnPreview(wxCommandEvent& event)
{
wxPrintPreview *preview = new wxPrintPreview(
new MyPrintout, new MyPrintout,
& g_printDialogData);
if (!preview->Ok())
{
delete preview;
wxMessageBox(wxT("There was a problem previewing.\nPerhaps your current printer is
not set correctly?"),
wxT("Previewing"), wxOK);
return;
}
wxPreviewFrame *frame = new wxPreviewFrame(preview, this,
wxT("Demo Print Preview"));
frame->Centre(wxBOTH);
frame->Initialize();
frame->Show(true);
}
```
當打印預覽窗口被初始化的時候,它將禁用所有其它的頂層窗口以確保任何可能導致正在預覽或者打印的文檔內容發生改變的可能。關閉這個窗口將會導致兩個wxPrintout對象自動被釋放。下圖顯示了預覽窗口的樣子,可以看到它內含一個工具條,上面提供了頁面遍歷,打印以及縮放控制等功能。

關于wxPrintout的更多內容
當創建wxPrintout對象的時候,你可以傳遞一個可選的標題參數,在某些操作系統上,這個標題將顯示在打印管理器上。另外,要重載一個 wxPrintout對象,你至少需要重載GetPageInfo, HasPage和OnPrintPage函數,當然,下面介紹的這些函數你都可以視需要進行重載。
首先來介紹GetPageInfo函數,它用來返回最小頁碼,最大頁碼,開始打印頁碼和結束打印頁碼。前兩個參數用來提供一個可以打印的范圍,后兩個參數被設計來反應用戶選擇的頁碼范圍,不過目前沒有用處。最小頁面的默認值為1,最大頁碼的默認值為32000,不過當HasPage函數返回False的時候,wxPrintout類也將停止打印。通常情況下,你的OnPreparePrinting應該計算當前打印內容在當前設置下的打印頁數,將其保存在一個成員變量中,以便GetPageInfo函數返回正確的值,如下所示:
```
void MyPrintout::GetPageInfo(int *minPage, int *maxPage,
int *pageFrom, int *pageTo)
{
*minPage = 1; *maxPage = m_numPages;
*pageFrom = 1; *pageTo = m_numPages;
}
```
而HasPage函數則用來返回是否擁有某個頁碼,如果這個頁碼超出了最大頁碼的范圍則必須返回False。通常你的實現類似下面的樣子:
```
bool MyPrintout::HasPage(int pageNum)
{
return (pageNum >= 1 && pageNum <= m_numPages);
}
```
OnPreparePrinting在預覽或者打印過程剛開始的時候被調用,重載它使得應用程序可以進行各種設置工作,比如計算文檔的總頁數等。OnPreparePrinting可以調用wxPrintout的GetDC,GetPageSizeMM,IsPreview等函數,因此方便獲取這些信息。
OnBeginDocument是在每次每篇文檔即將開始打印的時候被調用的,如果這個函數被重載了,那么必須在重載的函數中調用 wxPrintout::OnBeginDocument。同樣的wxPrintout::OnEndDocument也必須被它的重載函數調用。
OnBeginPrinting和OnEndPrinting函數則是在整個打印過程開始和結束的時候被調用,和當前打印多少份沒有關系。
OnPrintPage會被傳遞一個頁碼參數,應用程序必須重載這個函數并且在成功打印這一頁以后返回true。這個函數應該使用wxPrintout::GetDC來取得打印設備上下文已經進行繪畫(打印)工作。
下面這些函數作為一些工具函數,你可以在你的重載函數中使用它們,而無需重載它們。
IsPreview函數用來檢測當前正處于一個預覽過程中還是一個真的打印過程中。
GetDC函數為當前正在進行的工作返回一個合適的設備上下文。如果是在真實的打印過程中,則返回一個wxPrinterDC,如果是預覽,則返回一個wxMemoryDC,因為預覽其實是在一個位圖上通過內存設備上下文來渲染的。
GetPageSizeMM以毫米為單位返回當前打印頁面的大小。GetPageSizePixels則以象素為單位返回這個值(打印機的最大分辨率)。如果是在預覽過程中,這個大小和wxDC::GetSize返回的值通常是不一樣大的(參見下面的說明),wxDC::GetSize返回用于預覽的位圖的大小。
GetPPIPrinter返回當前設備上下文每一個英寸對應的象素的數目,而GetPPIScreen則返回當前屏幕上每英寸對應的象素的數目。
打印和預覽過程中的縮放
當你在窗口上繪畫的時候,你可能不大關心圖片的縮放,因為顯示器的分辨率大部分都是相同的。然后,在面對一個打印機的時候,有幾方面的因素可能導致你必須關心這個問題:
你需要通過縮放和重新放置圖片的位置來保證圖片位于某個頁面之內,在某種情況下,你甚至可能需要把一個圖片分成兩半。
字體都是基于屏幕分辨率的,因此在打印文本的時候,你需要設置一個合適的縮放的值,以便使打印設備上下文符合屏幕的分辨率。在打印文本的時候,通過應該設置通過用GetPPIPrinter的值除以GetPPIScreen的值計算而得的值作為縮放因子的值。
當渲染預覽圖案的時候,wxWidgets使用了wxMemoryDC在一個位圖上繪畫。這個位圖的大小(wxDC::GetSize)是基于當前預覽的放大倍數的這就需要一個額外的縮放因子。通常這個縮放因子可以通過用GetSize返回的值除以GetPageSizePixels返回的實際頁面的象素值來獲得。通常這個值還應該乘以別的縮放因子定義的值。
你可以調用wxDC::SetUserScale來設置設備上下文的縮放因子,使用wxDC::SetDeviceOrigin來設置平移因子(例如,需要把一個圖片放置在頁面正中的時候)。如果有必要的話,你甚至可以在同一個頁面繪畫的時候反復使用不同的值來調用這兩個函數。
wxWidgets的例子中的samples/printing演示了怎樣在打印過程中使用縮放,下面列出的代碼是其中進行縮放的適配代碼,它將演示了在打印過程中和預覽過程中將一幅大小為200x200象素的圖片進行了縮放和放置,如下所示:
```
void MyPrintout::DrawPageOne(wxDC *dc)
{
// 下面的代碼可以這樣寫只是因為我們知道圖片的大小是200x200
// 如果我們不知道的話,需要先計算圖片的大小
float maxX = 200;
float maxY = 200;
// 讓我們先設置至少50個設備單位的邊框
float marginX = 50;
float marginY = 50;
// 將邊框的大小增加到圖片的周圍
maxX += (2*marginX);
maxY += (2*marginY);
// 獲取象素單位的當前設備上下文的大小
int w, h;
dc->GetSize(&w, &h);
//計算一個合適的縮放值
float scaleX=(float)(w/maxX);
float scaleY=(float)(h/maxY);
// 選擇X或者Y方向上較小的那個
float actualScale = wxMin(scaleX,scaleY);
// 計算圖片在設備上的合適位置以便居中
float posX = (float)((w - (200*actualScale))/2.0);
float posY = (float)((h - (200*actualScale))/2.0);
// 設置設備平移和縮放
dc->SetUserScale(actualScale, actualScale);
dc->SetDeviceOrigin( (long)posX, (long)posY );
// ok,現在開始畫畫
dc.SetBackground(*wxWHITE_BRUSH);
dc.Clear();
dc.SetFont(wxGetApp().m_testFont);
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetBrush(*wxCYAN_BRUSH);
dc.SetPen(*wxRED_PEN);
dc.DrawRectangle(0, 30, 200, 100);
dc.DrawText( wxT("Rectangle 200 by 100"), 40, 40);
dc.SetPen( wxPen(*wxBLACK,0,wxDOT_DASH) );
dc.DrawEllipse(50, 140, 100, 50);
dc.SetPen(*wxRED_PEN);
dc.DrawText( wxT("Test message: this is in 10 point text"),
10, 180);
}
```
在上面的例子中,我們只是簡單的使用wxDC::GetSize來得到打印設備或者預覽圖像的分辨率,以便我們能夠把要打印的圖像放到合適的位置。我們沒有關心類似每一個英寸多少個點這樣的信息,因為我們不需要畫精度很高的文本或者是線段。圖片不需要很高的精度,因此我們只是進行簡單的縮放以便它能夠被合適的放置,不致于太大太小或者越界就可以了。
接下來,我們演示一下怎樣進行精度很高的文本或者線段的打印以便看上去它和顯示在屏幕上的是一致的。而不是只是進行簡單的縮放:
```
void MyPrintout::DrawPageTwo(wxDC *dc)
{
// 你可以使用下面的代碼來設置打印機以便其可以反應出文本在屏幕上的大小
// 另外下面的代碼還將打印一個5cm長的線段。
// 首先獲得屏幕和打印機上各自的1英寸的邏輯象素個數
int ppiScreenX, ppiScreenY;
GetPPIScreen(&ppiScreenX, &ppiScreenY);
int ppiPrinterX, ppiPrinterY;
GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);
// 這個縮放因子用來大概的反應屏幕到實際打印設備的一個縮放
float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);
// 現在,我們還需要考慮頁面縮放
// (比如:我們正在作打印預覽,用戶選擇了一個縮放級別)
int pageWidth, pageHeight;
int w, h;
dc->GetSize(&w, &h);
GetPageSizePixels(&pageWidth, &pageHeight);
// 如果打印設備的頁面大小pageWidth == 當前DC的大小, 就不需要考慮這方面的縮放了
// 但是它們有可能是不一樣的
// 因此,縮放吧.
float overallScale = scale * (float)(w/(float)pageWidth);
dc->SetUserScale(overallScale, overallScale);
// 現在我們來計算每個邏輯單位有多少個毫米
// 我們知道1英寸大概是25.4毫米.而ppi
// 代表的是以英寸為單位的. 因此1毫米就等于ppi/25.4個設備單位
// 另外我們還需要再除以我們的縮放因子scale
// (譯者注:為什么這里是scale而不是overallScale?)
// (其實overallScale比scale而言,多了一個預覽的縮放)
// (而在預覽的時候,我們是希望線段的長度按照用戶設置的比例變化的)
// 因為我們的設備已經被設置了縮放因子
// 現在讓我們來畫一個長度為50mm的L圖案
float logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));
float logUnits = (float)(50*logUnitsFactor);
dc->SetPen(* wxBLACK_PEN);
dc->DrawLine(50, 250, (long)(50.0 + logUnits), 250);
dc->DrawLine(50, 250, 50, (long)(250.0 + logUnits));
dc->SetBackgroundMode(wxTRANSPARENT);
dc->SetBrush(*wxTRANSPARENT_BRUSH);
dc->SetFont(wxGetApp().m_testFont);
dc->DrawText(wxT("Some test text"), 200, 300 );
}
```
在類Unix系統上的GTK+版本上的打印
和Mac OS X以及Windows系統不同,Unix系統沒有提供一個標準的API同時支持在屏幕上顯示文本圖片和在打印機上打印文本和圖片。實際上,在類Unix系統中,屏幕顯示是通過X11庫(被GTK+封裝又被wxWidgets封裝)實現的,而打印要通過發送PostScript命令到打印機來完成。而這兩種情況下使用不同的字體都是一個麻煩。直到最近,才有很少的程序在類Unix上提供了所見即所得的功能。以前wxWidgets提供自己的 PostScript實現,但是它很難和屏幕顯示的內容完全一致。
從版本2.8開始,Gnome項目組開始通過libgnomeprint和libgnomeprintui庫來提供打印支持,這樣大多數的打印問題才算得以解決。從wxWidgets的版本2.5.4開始,GTK+的版本通過合適的配置以后可以支持這兩個庫。你需要使用--with- gnomeprint來配置wxWidgets,這將導致wxWidgets在運行期自動查找GNOME打印庫。如果找的到,就使用它完成打印,否則就使用舊的PostScript打印的代碼。需要說明的是,這并不需要用戶的機器上一定要安裝gnome的打印庫程序才可以運行,因為程序本身并不依賴這些庫。
- 第一章 介紹
- 1.1 為什么要使用wxWidgets?
- 1.2 wxWidgets的歷史
- 1.3 wxWidgets社區
- 1.4 wxWidgets和面向對象編程
- 1.5 wxWidgets的體系結構
- 1.6 許可協議
- 第一章小結
- 第二章 開始使用
- 2.1 一個小例子
- 2.2 應用程序類
- 2.3 Frame窗口類
- 2.4 事件處理函數
- 2.5 Frame窗口的構造函數
- 2.6 完整的例子
- 2.7 wxWidgets程序一般執行過程
- 2.8 編譯和運行程序
- 第二章小結
- 第三章 事件處理
- 3.1 事件驅動編程
- 3.2 事件表和事件處理過程
- 3.3 過濾某個事件
- 3.4 掛載事件表
- 3.5 動態事件處理方法
- 3.6 窗口標識符
- 3.7 自定義事件
- 第三章小結
- 第四章 窗口的基礎知識
- 4.1 窗口解析
- 4.2 窗口類概覽
- 4.3 基礎窗口類
- 4.4 頂層窗口
- 4.5 容器窗口
- 4.6 非靜態控件
- 4.7 靜態控件
- 4.8 菜單
- 4.9 控制條
- 第四章小結
- 第五章繪畫和打印
- 5.1 理解設備上下文
- 5.2 繪畫工具
- 5.3 設備上下文中的繪畫函數
- 5.4 使用打印框架
- 5.5 使用wxGLCanvas繪制三維圖形
- 第五章小節
- 第六章處理用戶輸入
- 6.1 鼠標輸入
- 6.2 處理鍵盤事件
- 6.3 處理游戲手柄事件
- 第六章小結
- 第七章使用布局控件進行窗口布局
- 7.1 窗口布局基礎
- 7.2 窗口布局控件
- 7.3 使用布局控件進行編程
- 7.4 更多關于布局的話題
- 第七章小結
- 第八章使用標準對話框
- 8.1信息對話框
- 8.2 文件和目錄對話框
- 8.3 選擇和選項對話框
- 8.4 輸入對話框
- 8.5 打印對話框
- 第八章小結
- 第九章創建定制的對話框
- 9.1 創建定制對話框的步驟
- 9.2 一個例子:PersonalRecordDialog
- 9.3 在小型設備上調整你的對話框
- 9.4 一些更深入的話題
- 9.5 使用wxWidgets資源文件
- 第九章小結
- 第十章使用圖像編程
- 10.1 wxWidgets中圖片相關的類
- 10.2 使用wxBitmap編程
- 10.3 使用wxIcon編程
- 10.4 使用wxCursor編程
- 10.5 使用wxImage編程
- 10.6 圖片列表和圖標集
- 10.7 自定義wxWidgets提供的小圖片
- 第十章小結
- 第十一章剪貼板和拖放操作
- 11.1 數據對象
- 11.2 使用剪貼板
- 11.3 實現拖放操作
- 第十一章小結
- 第十二章高級窗口控件
- 12.1 wxTreeCtrl
- 12.2 wxListCtrl
- 12.3 wxWizard
- 12.4 wxHtmlWindow
- 12.5 wxGrid
- 12.6 wxTaskBarIcon
- 12.7 編寫自定義的控件
- 第十二章小結
- 第十三章數據結構類
- 13.1 為什么沒有使用STL?
- 13.2 字符串類型
- 13.3 wxArray
- 13.4 wxList和wxNode
- 13.5 wxHashMap
- 13.6 存儲和使用日期和時間
- 13.7 其它常用的數據類型
- 第十三章小結
- 第十四章文件和流操作
- 14.1 文件類和函數
- 14.2 流操作相關類
- 第十四章小結
- 第十五章內存管理,調試和錯誤處理
- 15.1 內存管理基礎
- 15.2 檢測內存泄漏和其它錯誤
- 15.3 構建自防御的程序
- 15.4 錯誤報告
- 15.5 提供運行期類型信息
- 15.6 使用wxModule
- 15.7 加載動態鏈接庫
- 15.8 異常處理
- 15.9 調試提示
- 第十五章小結
- 第十六章編寫國際化程序
- 16.1 國際化介紹
- 16.2 從翻譯說起
- 16.3 字符編碼和Unicode
- 16.4 數字和日期
- 16.5 其它媒介
- 16.6 一個小例子
- 第十六章小結
- 第十七章編寫多線程程序
- 17.1 什么時候使用多線程,什么時候不要使用
- 17.2 使用wxThread
- 17.3 用于線程同步的對象
- 17.4 多線程的替代方案
- 第十七章小結
- 第十八章使用wxSocket編程
- 18.1 Socket類和功能概覽
- 18.2 Socket及其基本處理介紹
- 18.3 Socket標記
- 18.4 使用Socket流
- 18.5 替代wxSocket
- 第十八章小結
- 第十九章使用文檔/視圖框架
- 19.1 文檔/視圖基礎
- 19.2 文檔/視圖框架的其它能力
- 19.3 實現Undo/Redo的策略
- 第十九章小結
- 第二十章完善你的應用程序
- 20.1 單個實例和多個實例
- 20.2 更改事件處理機制
- 20.3 降低閃爍
- 20.4 實現聯機幫助
- 20.5 解析命令行參數
- 20.6 存儲應用程序資源
- 20.7 調用別的應用程序
- 20.8 管理應用程序設置
- 20.9 應用程序安裝
- 20.10 遵循用戶界面設計規范
- 20.11 全書小結