用WPF實現打印及打印預覽


應該說,WPF極大地簡化了我們的打印輸出工作,想過去使用VC++做開發的時候,打印及預覽可是一件極麻煩的事情,而現在我不會再使用C++來做Windows的桌面應用了——性價比實在太低。

WPF的打印功能是很強大而簡便的,它甚至能夠直接打印界面上的內容,包括各種控件的顯示內容,例如你在界面上擺放了一個datagrid控件,畫了一個五角星,或寫了一段文字,都可以直接打印出來,這里有一篇文章很簡單明了地說明了這個功能:

http://www.cnblogs.com/gnielee/archive/2010/07/02/wpf-print-sample.html

這種做法是非常直截了當的,但恐怕不是很適合我們一般的應用,我們更多的時候需要自適應紙張,表格輸出,自動分頁,還有分頁預覽……

自己設計分頁是非常麻煩的事情,沒做過的人恐怕沒法理解為什么,我這里插點題外話提一下,為什么分頁難做?那是因為:你不真正把文檔打印出來,你就不知道到底要在什么地方分頁。舉個最簡單的例子,就一大段文本,給你打印,你認為到第幾個字要另開一頁?你估算了一下:一行20個字,我的打印紙一共20行,所以到第400個字的時候分頁。——Too simple,你沒考慮一行的字數根本就是不確定的(字符非等寬),也沒考慮回車換行所產生的空行,更沒考慮字體大小,行間距等影響因素,另外還有單詞自適應因素,最后還有紙張大小……Oh,my god,這簡直沒法做,是的,自己很難做的,一個比較笨但有效的方法是“模擬打印”,用二分法找到開始分頁的那個點,我以前做過的一個手機看書軟件就是這么干的,而真實的分頁算法是很復雜的,所幸的是這次不需要我們來做了。下面是我寫的一個demo。

 

這是打印預覽效果:

代碼並不多。設計的思路就是:文檔模版(xaml)+數據(.net對象)=打印輸出

文檔模版可以單獨創建,右擊你的WPF工程,Add - New Item - Flow Document(WPF),Visual Studio並沒有提供這個xaml的預覽,這點不得不說是個缺陷,微軟的理由是這種Flow Document的顯示需要一個容器,單獨的Flow Document(流文檔)是沒法預覽的,你必須把它放在一個容器中才可以,流文檔的容器有FlowDocumentScrollViewer,FlowDocumentPageViewer,FlowDocumentReader,另外還有DocumentViewer,這個只支持固定流文檔(只讀)。關於流文檔及其打印方面的技術在《WPF編程寶典》一書中都有具體講述,建議大家要詳細了解的話先去閱讀一下此書,下面主要是一些書中沒有的內容。

打印預覽,我們這次選擇了DocumentViewer,因為它直接就帶有很好的分頁功能,我們只需要生成固定文檔(XPS),然后交給它,它就能很好的將內容預覽出來——太棒了。

現在我們大致看看這個流文檔模版的內容:

    <Table FontSize="16">
        <Table.Columns>
            <TableColumn Width="200"></TableColumn>
            <TableColumn Width="600"></TableColumn>
        </Table.Columns>
        <TableRowGroup>
            <TableRow>
                <TableCell>
                    <Paragraph>
                        訂單號
                    </Paragraph>
                </TableCell>
                <TableCell>
                    <Paragraph>
                        <Run Text="{Binding OrderNo}"></Run>
                    </Paragraph>
                </TableCell>
            </TableRow>
            <TableRow>
                <TableCell>
                    <Paragraph>
                        客戶名稱
                    </Paragraph>
                </TableCell>
                <TableCell>
                    <Paragraph>
                        <Run Text="{Binding CustomerName}"></Run>
                    </Paragraph>
                </TableCell>
            </TableRow>
            <!-- 省略一大段 -->
        </TableRowGroup>
    </Table>

我把多余的內容去掉了,現在注意看“<Run Text="{Binding OrderNo}"></Run>”這個地方,我將這個Run的Text屬性綁定到DataContext的OrderNo去了,也就是說,它會根據數據的內容,渲染出不同的結果。

這里一切OK,但最大的問題來了:流文檔的Table卻不能跟UIElement的DataGrid控件那樣能動態地根據數據的條目數渲染出相應的行!也就是說Table的行數是固定的,流文檔上的對象是靜態的,所以我們只能用后台代碼來手工改變它了,這是相當不方便的地方……我定義了這么一個接口來做這種工作:

    public interface IDocumentRenderer
    {
        void Render(FlowDocument doc, Object data);
    }

創建一個對象,實現這個接口,然后根據data的內容,往doc里對應的地方插入行。

另外還需要特別說明的是代碼中使用了一些BeginInvoke,也許大家不太了解那是什么意思,為什么需要這么麻煩?其實,那是因為你給Document的DataContext賦值的時候,Document的內容並不是馬上改變的,不信你可以把我寫的這些BeginInvoke改為直接調用,然后看看打印預覽的文檔內容,是不是哪些binding的地方還是空白的?所以需要一個“延后”調用。關於BeginInvoke的內容可以看我這篇blog:

http://www.cnblogs.com/guogangj/archive/2013/01/22/2870590.html

最后,主界面上的“直接打印”為了防止用戶連續點擊,需要在點了一下之后把它變灰,然后過幾秒鍾之后再把它變亮。

最后的最后:完整代碼下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM