WPF的打印预览
WPF的文档使用基本使用文档模版Flow Document,但是单独的Flow Document(流文档)是没法预览的,你必须把它放在一个容器中才可以,流文档的容器有FlowDocumentScrollViewer,FlowDocumentPageViewer,FlowDocumentReader,另外还有DocumentViewer,这个只支持固定流文档(只读)。本次博客就决定界面使用Docu.
本文基本借鉴了用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 document
MemoryStream 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 document
docViewer.Document = xpsDocument.GetFixedDocumentSequence();
//关闭基于内存的xps document
xpsDocument.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.ClassList
where a.IsSelected
select 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的内容并不是马上改变的。
更多推荐
所有评论(0)