轉(zhuǎn)帖|其它|編輯:郝浩|2011-09-05 14:20:29.000|閱讀 983 次
概述:Silverlight 4 在 Silverlight 功能列表中添加了打印,我想通過(guò)向您介紹令我欣慰的小程序來(lái)探討這一點(diǎn)。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門(mén)軟控件火熱銷(xiāo)售中 >>
Silverlight 4 在 Silverlight 功能列表中添加了打印,我想通過(guò)向您介紹令我欣慰的小程序來(lái)探討這一點(diǎn)。
該程序稱(chēng)為 PrintEllipse,名稱(chēng)就是它要執(zhí)行的所有操作。 MainPage 的 XAML 文件包含一個(gè)按鈕,圖 1 中完整地顯示了 MainPage 代碼隱藏文件。
圖 1 PrintEllipse 的 MainPage 代碼
請(qǐng)注意 System.Windows.Printing 的 using 指令。 在單擊此按鈕時(shí),該程序?qū)?chuàng)建一個(gè)類(lèi)型為 PrintDocument 的對(duì)象,并為 PrintPage 事件分配一個(gè)處理程序。 當(dāng)程序調(diào)用 Print 方法時(shí),將顯示標(biāo)準(zhǔn)打印對(duì)話框。 用戶(hù)可借此機(jī)會(huì)設(shè)置要使用的打印機(jī),并設(shè)置各種打印屬性,例如縱向或橫向模式。
當(dāng)用戶(hù)單擊打印對(duì)話框中的“打印”時(shí),該程序?qū)⒔邮盏綄?duì) PrintPage 事件處理程序的調(diào)用。 此特殊程序會(huì)通過(guò)創(chuàng)建 Ellipse 元素并將該元素設(shè)置為事件參數(shù)的 PageVisual 屬性來(lái)進(jìn)行響應(yīng)。 (我故意選擇淡彩色以便程序不會(huì)使用太多油墨。)很快,將從打印機(jī)中出來(lái)一頁(yè),且該頁(yè)中填充了一個(gè)非常大的橢圓。
您可以從網(wǎng)站 bit.ly/dU9B7k 運(yùn)行此程序并親自檢驗(yàn)。 當(dāng)然,本文中的所有源代碼也是可下載的。
如果您的打印機(jī)與大多數(shù)打印機(jī)一樣,則內(nèi)部硬件將禁止打印機(jī)打印到紙張的每個(gè)邊緣。 打印機(jī)通常具有固有的內(nèi)置邊距,不會(huì)在邊距內(nèi)打印任何內(nèi)容;打印內(nèi)容限制在小于頁(yè)面全部大小的“可打印區(qū)域”上。
關(guān)于此程序,您將注意到的是:橢圓整體顯示在頁(yè)面的可打印區(qū)域中,很顯然,程序可以輕松達(dá)到此目的。 頁(yè)面可打印區(qū)域的行為方式與屏幕上的容器元素非常類(lèi)似:它僅在元素大小超出此區(qū)域時(shí)才對(duì)子項(xiàng)進(jìn)行剪輯。 一些更復(fù)雜的圖形環(huán)境(例如 Windows Presentation Foundation (WPF))未必有如此好的表現(xiàn)(當(dāng)然,與 Silverlight 相比,WPF 可提供更多打印控制和靈活性)。
除了 PrintPage 事件,PrintDocument 還定義了 BeginPrint 和 EndPrint 事件,但這些事件并非與 PrintPage 一樣重要。 BeginPrint 事件表明打印作業(yè)的開(kāi)始。 當(dāng)用戶(hù)通過(guò)按“打印”按鈕退出標(biāo)準(zhǔn)打印對(duì)話框并給程序機(jī)會(huì)執(zhí)行初始化時(shí),將觸發(fā)該事件。 調(diào)用 BeginPrint 處理程序之后,將對(duì) PrintPage 處理程序進(jìn)行首次調(diào)用。
要在特殊打印作業(yè)中打印多頁(yè)的程序?qū)⑦@樣操作。 在對(duì) PrintPage 處理程序的每次調(diào)用中,PrintPageEventArgs 的 HasMorePages 屬性初始將設(shè)置為 false。 當(dāng)處理程序完成一頁(yè)后,它只需將該屬性設(shè)置為 true 即可表明至少必須再打印一頁(yè)。 然后再次調(diào)用 PrintPage。 PrintDocument 對(duì)象維護(hù) PrintedPageCount 屬性,該屬性在每次對(duì) PrintPage 處理程序執(zhí)行調(diào)用后遞增。
如果 PrintPage 處理程序退出時(shí) HasMorePages 設(shè)置為其默認(rèn)值 false,打印作業(yè)將結(jié)束并觸發(fā) EndPrint 事件,這樣,程序?qū)⒂袡C(jī)會(huì)執(zhí)行清理任務(wù)。 當(dāng)打印過(guò)程中出現(xiàn)錯(cuò)誤時(shí)也會(huì)觸發(fā) EndPrint 事件;EndPrintEventArgs 的 Error 屬性的類(lèi)型為 Exception。
圖 1 中顯示的代碼將 Ellipse 的 StrokeThickness 設(shè)置為 24,如果您度量打印結(jié)果,您將發(fā)現(xiàn)這是四分之一英寸寬。 如您所知,Silverlight 程序通常以像素為單位從整體上調(diào)整圖形對(duì)象和控件的大小。 但是,涉及打印機(jī)時(shí),坐標(biāo)和大小都采用與設(shè)備無(wú)關(guān)的單位,即 1/96 英寸。 不論打印機(jī)的實(shí)際分辨率如何,在 Silverlight 程序中,打印機(jī)始終顯示為 96 DPI 設(shè)備。
您可能知道,在整個(gè) WPF 中都使用這種 96 個(gè)單位為一英寸的坐標(biāo)系,其中,單位有時(shí)稱(chēng)為“與設(shè)備無(wú)關(guān)的像素”。此 96 DPI 值不是隨意選擇的:默認(rèn)情況下,Windows 假定您的視頻顯示器的一英寸具有 96 個(gè)點(diǎn),因此,在很多情況下,WPF 程序?qū)嶋H上以像素為單位進(jìn)行繪制。 CSS 規(guī)范假定視頻顯示器的分辨率為 96 DPI,該值用于在像素、英寸和毫米之間進(jìn)行轉(zhuǎn)換。 值 96 還是一個(gè)便于轉(zhuǎn)換字體大小的數(shù)字,字體大小通常用磅(即 1/72 英寸)來(lái)指定。 一磅是一個(gè)與設(shè)備無(wú)關(guān)像素的四分之三。
PrintPageEventArgs 具有兩個(gè)有用的只讀屬性,這兩個(gè)屬性也以 1/96 英寸為單位報(bào)告大小:類(lèi)型為 Size 的 PrintableArea 提供頁(yè)面的可打印區(qū)域的尺寸,類(lèi)型為 Thickness 的 PageMargins 是位于左側(cè)、頂部、右側(cè)和底部的不可打印邊緣的寬度。 以正確的方式將這兩個(gè)屬性加到一起,您就會(huì)得到紙張的完整大小。
我的打印機(jī)裝載的是標(biāo)準(zhǔn) 8.5 x 11 英寸的紙張并設(shè)置為縱向模式,報(bào)告 PrintableArea 為 791 x 993。 PageMargins 屬性的四個(gè)值為 12(左側(cè))、6(頂部)、12(右側(cè))和 56(底部)。 如果將水平方向的值 791、12 和 12 相加,將得到 815。 垂直方向的值為 994、6 和 56,加起來(lái)是 1,055。 我不確定為什么這些值與將頁(yè)面大小(以英寸為單位)與 96 相乘所得的值 816 和 1,056 之間存在一個(gè)單位的差異。
當(dāng)打印機(jī)設(shè)置為橫向模式時(shí),PrintableArea 和 PageMargins 報(bào)告的水平尺寸和垂直尺寸值將交換。 實(shí)際上,查看 PrintableArea 屬性是 Silverlight 程序確定打印機(jī)是縱向模式還是橫向模式的唯一方式。 該程序打印的任何內(nèi)容將根據(jù)此模式自動(dòng)對(duì)齊和旋轉(zhuǎn)。
通常,當(dāng)您打印現(xiàn)實(shí)生活中的內(nèi)容時(shí),定義的邊距將比不可打印邊距稍大些。 在 Silverlight 中如何做到這一點(diǎn)呢? 首先,這與設(shè)置要打印元素的 Margin 屬性一樣容易。 此 Margin 是這樣計(jì)算的:從所需總邊距(以 1/96 英寸為單位)中減去 PrintPageEventArgs 中提供的 PageMargins 屬性的值。 該方法的效果不是很好,但正確的解決方案幾乎一樣簡(jiǎn)單。 PrintEllipseWithMargins 程序(可以在 bit.ly/fCBs3X 上運(yùn)行)與第一個(gè)程序相同,只不過(guò)對(duì) Ellipse 設(shè)置了 Margin 屬性,然后將 Ellipse 設(shè)置為將填充可打印區(qū)域的 Border 的子項(xiàng)。 或者,您也可以對(duì) Border 設(shè)置 Padding 屬性。 圖 2 顯示新的 OnPrintPage 方法。
圖 2 用于計(jì)算邊距的 OnPrintPage 方法
沒(méi)有與打印機(jī)相關(guān)聯(lián)的特殊圖形方法或圖形類(lèi)。 您可以按照在視頻顯示器上“繪制”對(duì)象的相同方式在打印機(jī)頁(yè)面上“繪制”對(duì)象,方法是組合從 FrameworkElement 派生的對(duì)象可視樹(shù)。 此樹(shù)可以包含面板元素,其中包括畫(huà)布。 若要打印該可視樹(shù),請(qǐng)將最上面的元素設(shè)置為 PrintPageEventArgs 的 PageVisual 屬性。 (PageVisual 定義為 UIElement,該元素是 FrameworkElement 的父類(lèi),但實(shí)際上,將設(shè)置為 PageVisual 的一切對(duì)象都將從 FrameworkElement 派生。)
出于布局的目的,從 FrameworkElement 派生的幾乎所有類(lèi)都包含 MeasureOverride 和 ArrangeOverride 方法的重要實(shí)現(xiàn)。 在類(lèi)的 MeasureOverride 方法中,有一個(gè)元素決定類(lèi)的所需大小,有時(shí)通過(guò)調(diào)用子項(xiàng)的 Measure 方法來(lái)確定子項(xiàng)的所需大小。 在 ArrangeOverride 方法中,有一個(gè)元素通過(guò)調(diào)用子項(xiàng)的 Arrange 方法來(lái)排列子項(xiàng)相對(duì)于類(lèi)本身的位置。
將某個(gè)元素設(shè)置為 PrintPageEventArgs 的 PageVisual 屬性時(shí),Silverlight 打印系統(tǒng)將使用 PrintableArea 大小在該最上面的元素上調(diào)用 Measure。 這就是(舉例來(lái)說(shuō))Ellipse 或 Border 的大小自動(dòng)調(diào)整為頁(yè)面的可打印區(qū)域的方式。
但是,您也可以將該 PageVisual 屬性設(shè)置為已屬于程序窗口中所顯示的可視樹(shù)的元素。 這種情況下,打印系統(tǒng)不會(huì)對(duì)該元素調(diào)用 Measure,而是使用已為視頻顯示器確定的度量和布局。 這可使您在從程序窗口打印內(nèi)容時(shí)保持合理的保真度,還意味著所打印的內(nèi)容可能裁剪為頁(yè)面大小。
當(dāng)然,您可以對(duì)打印的元素設(shè)置明確的 Width 和 Height 屬性,并且可以使用 PrintableArea 大小來(lái)幫助解決問(wèn)題。
我要探討的下一個(gè)程序比我預(yù)期更具挑戰(zhàn)性。 目標(biāo)是存儲(chǔ)在用戶(hù)本地計(jì)算機(jī)上允許用戶(hù)打印 Silverlight 支持的任何圖像文件(即 PNG 和 JPEG 文件)的程序。 此程序使用 OpenFileDialog 類(lèi)加載這些文件。 出于安全考慮,OpenFileDialog 僅返回讓程序打開(kāi)文件的 FileInfo 對(duì)象。 不提供文件名或目錄。
我希望此程序在頁(yè)面(不包括預(yù)設(shè)邊距)上盡可能大地打印位圖,而不改變位圖的長(zhǎng)寬比。 通常情況下,這非常簡(jiǎn)單:Image 元素的默認(rèn) Stretch 模式為 Uniform,這意味著位圖將被盡可能大地拉伸而不會(huì)扭曲。
但是,我決定不需要用戶(hù)在相應(yīng)打印機(jī)上針對(duì)特殊圖像專(zhuān)門(mén)設(shè)置縱向或橫向模式。 如果打印機(jī)設(shè)置為縱向模式,且圖像的寬度大于高度,則我希望圖像在縱向頁(yè)面上橫著打印。 這個(gè)小功能立即會(huì)使程序更復(fù)雜。
如果我編寫(xiě)一個(gè)實(shí)現(xiàn)此功能的 WPF 程序,程序本身可將打印機(jī)切換為縱向或橫向模式。 但是這在 Silverlight 中無(wú)法實(shí)現(xiàn)。 打印機(jī)接口的定義使只有用戶(hù)可以更改此類(lèi)設(shè)置。
同樣,如果我編寫(xiě) WPF 程序,則可以對(duì) Image 元素設(shè)置 LayoutTransform 以將其旋轉(zhuǎn) 90 度。 隨后將調(diào)整旋轉(zhuǎn)后的 Image 元素的大小以適合頁(yè)面,而且位圖本身也會(huì)調(diào)整大小以適合該 Image 元素。
但是 Silverlight 不支持 LayoutTransform。 Silverlight 僅支持 RenderTransform,因此如果必須旋轉(zhuǎn) Image 元素以適合在縱向模式下打印的橫向圖像,還必須將 Image 元素的大小手動(dòng)調(diào)整為橫向頁(yè)面的尺寸。
您可以在 bit.ly/eMHOsB 上試驗(yàn)我最初的嘗試。 OnPrintPage 方法創(chuàng)建一個(gè) Image 元素并將 Stretch 屬性設(shè)置為 None,這意味著 Image 元素按位圖的像素大小顯示位圖,這在打印機(jī)上意味著每個(gè)像素假定為 1/96 英寸。 程序然后將旋轉(zhuǎn)該 Image 元素、調(diào)整其大小,并通過(guò)計(jì)算適用于該 Image 元素的 RenderTransform 屬性的變換來(lái)轉(zhuǎn)換該元素。
此類(lèi)代碼的難點(diǎn)當(dāng)然是數(shù)學(xué)計(jì)算,因此,看到該程序可以在打印機(jī)設(shè)置為縱向和橫向模式的情況下處理縱向和橫向圖像,將是一件非常高興的事。
但是,如果由于圖像太大而導(dǎo)致程序失敗,又將是非常不愉快的事。 您可以親自試驗(yàn)尺寸(除以 96 時(shí))稍大于頁(yè)面大小(以英寸為單位)的圖像。 圖像將按正確大小顯示,但顯示不完整。
這行代碼起什么作用呢? 哦,我以前在視頻顯示器上看到過(guò)該代碼。 請(qǐng)記住,RenderTransform 僅影響元素的顯示方式,不影響元素在布局系統(tǒng)上的外觀。 對(duì)于布局系統(tǒng),我在 Stretch 設(shè)置為 None 的 Image 元素中顯示了一個(gè)位圖,意味著 Image 元素與位圖本身一樣大。 如果位圖大于打印機(jī)頁(yè)面,則無(wú)法呈現(xiàn) Image 元素的某些部分,實(shí)際上將剪輯該元素,而與相應(yīng)縮小 Image 元素的 RenderTransform 無(wú)關(guān)。
我的第二個(gè)嘗試(您可在 bit.ly/g4HJ1C 上試用)采取了不同的策略。 圖 3 中顯示了 OnPrintPage 方法。 為 Image 元素提供了顯式 Width 和 Height 設(shè)置,使該元素正好符合計(jì)算的顯示區(qū)域的大小。 由于該元素全部位于頁(yè)面的可打印區(qū)域中,因此不會(huì)剪輯任何內(nèi)容。 Stretch 模式設(shè)置為 Fill,這意味著不論長(zhǎng)寬比如何,位圖都將填充該 Image 元素。 如果不旋轉(zhuǎn) Image 元素,正確調(diào)整了一個(gè)尺寸的大小,另一個(gè)尺寸必須應(yīng)用可減小大小的比例因子。 如果還必須旋轉(zhuǎn) Image 元素,則這些比例因子必須適合旋轉(zhuǎn)后 Image 元素的不同長(zhǎng)寬比。
圖 3 在 PrintImage 中打印圖像
代碼實(shí)在是太雜亂了(我懷疑可能有一些簡(jiǎn)化,但對(duì)我來(lái)說(shuō)不是很明顯),但它適合所有大小的位圖。
另一種方法是旋轉(zhuǎn)位圖本身而不是 Image 元素。 從加載的 BitmapImage 對(duì)象創(chuàng)建一個(gè) WriteableBitmap,并使用交換的水平尺寸和垂直尺寸創(chuàng)建另一個(gè) WritableBitmap。 然后將第一個(gè) WriteableBitmap 中的所有像素復(fù)制到具有交換的行和列的第二個(gè) WriteableBitmap。
在 Silverlight 編程中,從 UserControl 派生是一項(xiàng)相當(dāng)常用的技術(shù),可用來(lái)創(chuàng)建可重用控件而不會(huì)增加很多麻煩。 UserControl 的大部分是在 XAML 中定義的可視樹(shù)。
還可以通過(guò)從 UserControl 派生來(lái)定義用于打印的可視樹(shù)! PrintCalendar 程序中闡釋了此技術(shù),您可以在 bit.ly/dIwSsn 上進(jìn)行試驗(yàn)。 輸入開(kāi)始月份和結(jié)束月份后,程序?qū)⒋蛴≡摲秶械乃性路荩粋€(gè)月份打印一頁(yè)。 您可以將頁(yè)面裝訂為掛歷并進(jìn)行標(biāo)記,就好像真實(shí)的日歷掛歷一樣。
體驗(yàn) PrintImage 程序后,我不想為邊距或方向而費(fèi)心;我增加了一個(gè)按鈕,通過(guò)它將此職責(zé)交給了用戶(hù),如圖 4 所示。
圖 4 PrintCalendar 按鈕
定義日歷頁(yè)面的 UserControl 稱(chēng)為 CalendarPage,圖 5 中顯示了 XAML 文件。 頂部附近的 TextBlock 顯示月份和年份。 然后是另一個(gè)網(wǎng)格,其中包含七列(用于表示星期幾)和六行(用于表示一月中的最多六周或部分 周)。
圖 5 CalendarPage 布局
與大多數(shù) UserControl 派生項(xiàng)不同,CalendarPage 定義了一個(gè)包含參數(shù)的構(gòu)造函數(shù),如圖 6 所示。
圖 6 CalendarPage 代碼隱藏構(gòu)造函數(shù)
該參數(shù)是 DateTime 類(lèi)型,構(gòu)造函數(shù)使用 Month 和 Year 屬性創(chuàng)建一個(gè)邊框,其中包含月份中每一天的 TextBlock。 每個(gè) TextBlock 都被分配了一個(gè) Grid.Row 和 Grid.Column 附加屬性,然后添加到網(wǎng)格中。 如您所知,月份通常跨越五周,有時(shí)候二月僅有四周,因此,如果不需要 RowDefinition 對(duì)象,實(shí)際上會(huì)將它們從網(wǎng)格中刪除。
UserControl 派生項(xiàng)通常不具有包含參數(shù)的構(gòu)造函數(shù),因?yàn)樗鼈兺ǔ?gòu)成大型可視樹(shù)的部分。 但是,CalendarPage 的使用并非如此。 實(shí)際上,PrintPage 處理程序只是將 CalendarPage 的新實(shí)例分配給 PrintPageEventArgs 的 PageVisual 屬性。 下面是該處理程序的完整主體,清晰地闡釋了 CalendarPage 執(zhí)行的工作量:
因此,向程序中添加打印選項(xiàng)經(jīng)常被視為涉及大量代碼的令人精疲力盡的工作。 能夠在 XAML 文件中定義大部分打印頁(yè)面使整個(gè)工作變得不那么可怕。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:網(wǎng)絡(luò)轉(zhuǎn)載