WPF的打印预览
本文基本借鑒了用WPF實現打印及打印預覽
WPF的文檔使用基本使用文檔模版Flow Document,但是單獨的Flow Document(流文檔)是沒法預覽的,你必須把它放在一個容器中才可以,流文檔的容器有FlowDocumentScrollViewer,FlowDocumentPageViewer,FlowDocumentReader,另外還有DocumentViewer,這個只支持固定流文檔(只讀)。
本次博客就決定界面使用DocumentViewer,DocumentViewer有很好的分頁功能,我們只需要生成固定文檔(XPS),然后交給它,它就能很好的將內容預覽出來。
<Table FontSize="16"><TableRowGroup><TableRow><TableCell><Paragraph>班級</Paragraph></TableCell><TableCell><Paragraph><Run Text="{Binding ClassName}"></Run></Paragraph></TableCell></TableRow></TableRowGroup> </Table>但最大的問題來了:流文檔的Table的行數是固定的,流文檔上的對象是靜態的,所以我們只能用后臺代碼來手工改變它了,這是相當不方便的地方……我定義了這么一個接口來做這種工作。
public interface IDocumentRenderer {void Render(FlowDocument doc, Object data); }就比如我下面需要獲取班級所有學生各門成績
<Table><Table.Columns><TableColumn Width="200"/><TableColumn Width="150"/><TableColumn Width="150"/><TableColumn Width="150"/></Table.Columns><TableRowGroup Name="Student" Background="LightGray" FontWeight="Bold"><TableRow><TableCell style="{StaticResource CellStyle}"><Paragraph>Name</Paragraph></TableCell><TableCell style="{StaticResource CellStyle}"><Paragraph>ID</Paragraph></TableCell><TableCell style="{StaticResource CellStyle}"><Paragraph>Chinese</Paragraph></TableCell><TableCell style="{StaticResource CellStyle}"><Paragraph>Math</Paragraph></TableCell></TableRow></TableRowGroup> </Table>定義一個繼承IDocumentRenderer接口的類,實現Render方法
public class ClassDocumentRender : IDocumentRenderer {public void Render(FlowDocument doc, Object data){TableRowGroup group = doc.FindName("Student") as TableRowGroup ;Style cellStyle = doc.Resource("CellStyle") as Style;foreach(Student student in ((ClassData)data).StudentList){TableRow row = new TableRow();TableCell cell = new TableCell(new Paragraph(new Run(student.Name)));cell.Style = cellStyle;row.Cells.Add(cell);cell = new TableCell(new Paragraph(new Run(student.ID.ToString())));cell.Style = cellStyle;row.Cells.Add(cell);cell = new TableCell(new Paragraph(new Run(student.Chinese.ToString())));cell.Style = cellStyle;row.Cells.Add(cell);cell = new TableCell(new Paragraph(new Run(student.Math.ToString())));cell.Style = cellStyle;row.Cells.Add(cell);group.Rows.Add(row);}} }先定義一個能接受流文檔與IDocumentRenderer接口的方法。這里的DataContext必須要有,因為流文檔中的數據都是以binding形式寫的,UI只是用來顯示數據的,而不是存儲數據的。
public FlowDocument LoadDocumentAndRender(string strTmplName, Object data, IDocumentRenderer renderer = null) {FlowDocument doc = (FlowDocument)Application.LoadComponent(new Uri(strTmplName, UriKind.RelativeOrAbsolute));doc.PagePadding = new Thickness(50);doc.DataContext = data;if (renderer != null){renderer.Render(doc, data);}return doc; }定義一個加載流文檔的方法
public void LoadXps(FlowDocument flowDoc) {//構造一個基于內存的xps documentMemoryStream ms = new MemoryStream();Package package = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);Uri DocumentUri = new Uri("pack://InMemoryDocument.xps");PackageStore.RemovePackage(DocumentUri);PackageStore.AddPackage(DocumentUri, package);XpsDocument xpsDocument = new XpsDocument(package, CompressionOption.Fast, DocumentUri.AbsoluteUri);//將flow document寫入基于內存的xps document中去XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);writer.Write(((IDocumentPaginatorSource)flowDoc).DocumentPaginator);//獲取這個基于內存的xps document的fixed documentdocViewer.Document = xpsDocument.GetFixedDocumentSequence();//關閉基于內存的xps documentxpsDocument.Close(); }這是打印預覽代碼,然后你可以從界面上DocumentView上的打印按鈕來打印
async Task ShowMyClass(MyClass myClass) {ClassReportData crd = await GetClassReportData(myClass);FlowDocument flowDoc = LoadDocumentAndRender("../Print/ClassReportDocument.xaml", crd, new ClassReportDocumentRenderer());Dispatcher.BeginInvoke(new LoadXpsMethod(LoadXps), DispatcherPriority.ApplicationIdle, flowDoc); }private async Task<ClassReportData > GetClassReportData(MyClass myClass) {ClassReportData erd = new ClassReportData ();await TaskEx.Delay(1000);return erd; }這是多選班級打印,直接跳到微軟的打印界面,我當時其實想的是弄個隊列,然后一個一個打印,后來檢查了PrintDialog類之后發現不用了,它內部實現其實就有PrintQueue,本身內部就有個隊列來維護先后順序的,那么我們直接調用PrintDocument方法就可以了
private async void printBtn_Click(object sender, RoutedEventArgs e) {PrintDialog pDialog = new PrintDialog();pDialog.PageRangeSelection = PageRangeSelection.AllPages;// Display the dialog. This returns true if the user presses the Print button.bool? print = pDialog.ShowDialog();if (print == true){List<MyClass> result = (from a in ViewModel.ClassListwhere a.IsSelectedselect a).ToList();foreach (var result in result){ClassReportData crd = await GetClassReportData(result);FlowDocument flowDoc = LoadDocumentAndRender("../Print/ClassReportDocument.xaml", crd, new ClassReportDocumentRenderer());Dispatcher.BeginInvoke(new Action(() =>{pDialog.PrintDocument(((IDocumentPaginatorSource)flowDoc).DocumentPaginator, "打印報表");}), DispatcherPriority.ApplicationIdle);}} }注意:上文必須使用BeginInvoke而且參數必須為DispatcherPriority.ApplicationIdle,否則binding的地方都是空白的,因為給Document的DataContext賦值的時候,Document的內容并不是馬上改變的。
總結
- 上一篇: 计算机打印预览在哪,excel打印在哪里
- 下一篇: .NET DataGridView 单元