搭建Wpf框架(3) —— Wpf实现打印报表

Wpf流文档自带打印功能,那么使用MVVM轻松可以实现打印功能。

1.新建一个窗体,放置流文档的父容器

<Window x:Class="AIStudio.Wpf.BasePage.Views.PrintPreviewWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AIStudio.Wpf.BasePage.Views"
        mc:Ignorable="d"
         Title="打印预览窗口" Height="736" Width="882">
    <Grid>
        <DocumentViewer Name="docViewer"></DocumentViewer>
    </Grid>
</Window>

2.DocumentViewer加载FlowDocument的方法

 public static void LoadXps(FlowDocument m_doc, DocumentViewer docViewer)
        {
            //构造一个基于内存的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)m_doc).DocumentPaginator);

            //获取这个基于内存的xps document的fixed document
            docViewer.Document = xpsDocument.GetFixedDocumentSequence();

            //关闭基于内存的xps document
            xpsDocument.Close();
        }

3.绘制自己要打印的页面MVVM模式。

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              ColumnWidth="400" FontSize="14" FontFamily="宋体"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
              TextOptions.TextFormattingMode="Display">
    <Paragraph TextAlignment="Center" FontSize="16" FontWeight="Bold">
        <Run Text="{Binding DefFormName}"></Run>        
    </Paragraph>
    <Paragraph>
        <Run Text="因"></Run>
        <Run Text="{Binding Text}" TextDecorations="Underline"></Run>
        <Run Text=",本人想"></Run>
        <Run Text="{Binding SubType}"></Run>
        <Run Text="{Binding Flag}" TextDecorations="Underline"></Run>
        <Run Text="{Binding Unit}"></Run>
        <Run Text=",望领导批准!"></Run>
    </Paragraph>
    <Paragraph TextAlignment="Right">
        <Run Text="谢谢!"></Run>
    </Paragraph>
    <Paragraph TextAlignment="Right">
        <Run Text="申请人:"></Run>
        <Run Text="{Binding ApplicantUserId}" TextDecorations="Underline"></Run>
    </Paragraph>
</FlowDocument>

4.如果需要打印表格,那么xaml如下:

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:local="clr-namespace:AIStudio.Wpf.Base_Manage.Views"
              ColumnWidth="400" FontSize="14" FontFamily="宋体"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
              TextOptions.TextFormattingMode="Display">
    <FlowDocument.Resources>
        <Style TargetType="Table" x:Key="BorderedTable">
            <Setter Property="CellSpacing" Value="0"></Setter>
            <Setter Property="BorderThickness" Value="1"></Setter>
            <Setter Property="BorderBrush" Value="#000"></Setter>
        </Style>
        <Style TargetType="TableCell" x:Key="BorderedCell">
            <Setter Property="BorderThickness" Value="0.5"></Setter>
            <Setter Property="BorderBrush" Value="#000"></Setter>
            <Setter Property="Padding" Value="3"></Setter>
        </Style>
    </FlowDocument.Resources>   

    <Table Style="{StaticResource BorderedTable}">
        <Table.Columns>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
        </Table.Columns>
        <TableRowGroup Name="rowsDetails">
            <TableRow FontWeight="Bold" >
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>用户名</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>姓名</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>性别</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>出生日期</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>所属部门</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>所属角色</Paragraph>
                </TableCell>           
            </TableRow>
        </TableRowGroup>
    </Table>
</FlowDocument>

 这个时候还需要在cs代码中添加数据行

  class Base_UserDocumentRenderer : IDocumentRenderer
    {
        public void Render(FlowDocument doc, object data)
        {
            if (data is ObservableCollection<Base_UserDTO> items)
            {
                TableRowGroup groupDetails = doc.FindName("rowsDetails") as TableRowGroup;

                Style styleCell = doc.Resources["BorderedCell"] as Style;
                foreach (var item in items)
                {
                    TableRow row = new TableRow();

                    TableCell cell = new TableCell(new Paragraph(new Run(item.UserName)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.RealName)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.SexText)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.Birthday?.ToString("yyyy-MM-dd"))));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.DepartmentName)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.RoleNames)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    groupDetails.Rows.Add(row);
                }
            }
        }
    }

最后整理一下:

1.PrintHelper

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Xps;
using System.Windows.Xps.Packaging;

namespace AIStudio.Core.Helpers
{
    public class PrintHelper
    {
        public delegate void LoadManyXpsMethod(List<FlowDocument> m_doclist, DocumentViewer docViewer);
        public delegate void LoadXpsMethod(FlowDocument m_doclist, DocumentViewer docViewer);

        public static void LoadXps(FlowDocument m_doc, DocumentViewer docViewer)
        {
            //构造一个基于内存的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)m_doc).DocumentPaginator);

            //获取这个基于内存的xps document的fixed document
            docViewer.Document = xpsDocument.GetFixedDocumentSequence();

            //关闭基于内存的xps document
            xpsDocument.Close();
        }

        public static void LoadManyXps(List<FlowDocument> m_doclist, DocumentViewer docViewer)
        {
            //------------------定义新文档的结构
            FixedDocumentSequence newFds = new FixedDocumentSequence();//创建一个新的文档

            for (int i = 0; i < m_doclist.Count; i++)
            {
                //构造一个基于内存的xps document
                MemoryStream ms = new MemoryStream();
                Package package = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
                Uri DocumentUri = new Uri("pack://InMemoryDocument" + i.ToString() + ".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)m_doclist[i]).DocumentPaginator);

                DocumentReference newDocRef = AddPage(xpsDocument);//加入第一个文件
                newFds.References.Add(newDocRef);

                //关闭基于内存的xps document
                xpsDocument.Close();
            }

            string newFile = "xpsShow.xps";
            File.Delete(newFile);
            //xps写入新文件
            XpsDocument NewXpsDocument = new XpsDocument("xpsShow.xps", System.IO.FileAccess.ReadWrite);
            XpsDocumentWriter xpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(NewXpsDocument);
            xpsDocumentWriter.Write(newFds);

            //获取这个基于内存的xps document的fixed document
            docViewer.Document = NewXpsDocument.GetFixedDocumentSequence();  
            NewXpsDocument.Close();
        }

        public static DocumentReference AddPage(XpsDocument xpsDocument)
        {
            DocumentReference newDocRef = new DocumentReference();
            FixedDocument newFd = new FixedDocument();

            FixedDocumentSequence docSeq = xpsDocument.GetFixedDocumentSequence();

            foreach (DocumentReference docRef in docSeq.References)
            {
                FixedDocument fd = docRef.GetDocument(false);

                foreach (PageContent oldPC in fd.Pages)
                {
                    Uri uSource = oldPC.Source;//读取源地址
                    Uri uBase = (oldPC as IUriContext).BaseUri;//读取目标页面地址

                    PageContent newPageContent = new PageContent();
                    newPageContent.GetPageRoot(false);
                    newPageContent.Source = uSource;
                    (newPageContent as IUriContext).BaseUri = uBase;
                    newFd.Pages.Add(newPageContent);//将新文档追加到新的documentRefences中
                }
            }
            newDocRef.SetDocument(newFd);
            xpsDocument.Close();
            return newDocRef;
        }

        public static DocumentReference AddPage(string fileName)
        {
            DocumentReference newDocRef = new DocumentReference();
            FixedDocument newFd = new FixedDocument();

            XpsDocument xpsDocument = new XpsDocument(fileName, FileAccess.Read);
            FixedDocumentSequence docSeq = xpsDocument.GetFixedDocumentSequence();

            foreach (DocumentReference docRef in docSeq.References)
            {
                FixedDocument fd = docRef.GetDocument(false);

                foreach (PageContent oldPC in fd.Pages)
                {
                    Uri uSource = oldPC.Source;//读取源地址
                    Uri uBase = (oldPC as IUriContext).BaseUri;//读取目标页面地址

                    PageContent newPageContent = new PageContent();
                    newPageContent.GetPageRoot(false);
                    newPageContent.Source = uSource;
                    (newPageContent as IUriContext).BaseUri = uBase;
                    newFd.Pages.Add(newPageContent);//将新文档追加到新的documentRefences中
                }
            }
            newDocRef.SetDocument(newFd);
            xpsDocument.Close();
            return newDocRef;
        }
    }
}
View Code

2.PrintPreviewWindow.xaml

<Window x:Class="AIStudio.Wpf.BasePage.Views.PrintPreviewWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AIStudio.Wpf.BasePage.Views"
        mc:Ignorable="d"
         Title="打印预览窗口" Height="736" Width="882">
    <Grid>
        <DocumentViewer Name="docViewer"></DocumentViewer>
    </Grid>
</Window>
View Code

3.PrintPreviewWindow.xaml.cs

using AIStudio.Core.Helpers;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace AIStudio.Wpf.BasePage.Views
{
    /// <summary>
    /// PrintPreviewWindow.xaml 的交互逻辑
    /// </summary>
    public partial class PrintPreviewWindow : Window
    {
        public PrintPreviewWindow()
        {
            InitializeComponent();
        }

        public static 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);
            }

            DocumentPaginator paginator = ((IDocumentPaginatorSource)doc).DocumentPaginator;
            paginator.PageSize = new Size(595, 842);
            //doc.PagePadding = new Thickness(50, 50, 50, 50);
            doc.ColumnWidth = double.PositiveInfinity;
            return doc;
        }
        public PrintPreviewWindow(string strTmplName, Object data, IDocumentRenderer renderer = null) : this()
        {
            if (data is System.Collections.IList resultList)
            {
                var m_doclist = new List<FlowDocument>();
                foreach (var result in resultList)
                {
                    m_doclist.Add(LoadDocumentAndRender(strTmplName, result, renderer));
                }
                Dispatcher.BeginInvoke(new PrintHelper.LoadManyXpsMethod(PrintHelper.LoadManyXps), DispatcherPriority.ApplicationIdle, m_doclist, docViewer);
            }
            else
            {
                var m_doc = LoadDocumentAndRender(strTmplName, data, renderer);
                Dispatcher.BeginInvoke(new PrintHelper.LoadXpsMethod(PrintHelper.LoadXps), DispatcherPriority.ApplicationIdle, m_doc, docViewer);
            }

        }

        public PrintPreviewWindow(string strTmplName, System.Collections.IList data, IDocumentRenderer renderer = null) : this()
        {
            var m_doc = LoadDocumentAndRender(strTmplName, data, renderer);
            Dispatcher.BeginInvoke(new PrintHelper.LoadXpsMethod(PrintHelper.LoadXps), DispatcherPriority.ApplicationIdle, m_doc, docViewer);
        }
    }
}
View Code

4.Base_UserDocumentRenderer.cs

using AIStudio.Core.Helpers;
using AIStudio.Wpf.Business.DTOModels;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Documents;

namespace AIStudio.Wpf.Base_Manage.ViewModels
{
    class Base_UserDocumentRenderer : IDocumentRenderer
    {
        public void Render(FlowDocument doc, object data)
        {
            if (data is ObservableCollection<Base_UserDTO> items)
            {
                TableRowGroup groupDetails = doc.FindName("rowsDetails") as TableRowGroup;

                Style styleCell = doc.Resources["BorderedCell"] as Style;
                foreach (var item in items)
                {
                    TableRow row = new TableRow();

                    TableCell cell = new TableCell(new Paragraph(new Run(item.UserName)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.RealName)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.SexText)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.Birthday?.ToString("yyyy-MM-dd"))));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.DepartmentName)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    cell = new TableCell(new Paragraph(new Run(item.RoleNames)));
                    cell.Style = styleCell;
                    row.Cells.Add(cell);

                    groupDetails.Rows.Add(row);
                }
            }
        }
    }
}
View Code

5.Base_UserFlowDocument.xaml

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:local="clr-namespace:AIStudio.Wpf.Base_Manage.Views"
              ColumnWidth="400" FontSize="14" FontFamily="宋体"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
              TextOptions.TextFormattingMode="Display">
    <FlowDocument.Resources>
        <Style TargetType="Table" x:Key="BorderedTable">
            <Setter Property="CellSpacing" Value="0"></Setter>
            <Setter Property="BorderThickness" Value="1"></Setter>
            <Setter Property="BorderBrush" Value="#000"></Setter>
        </Style>
        <Style TargetType="TableCell" x:Key="BorderedCell">
            <Setter Property="BorderThickness" Value="0.5"></Setter>
            <Setter Property="BorderBrush" Value="#000"></Setter>
            <Setter Property="Padding" Value="3"></Setter>
        </Style>
    </FlowDocument.Resources>   

    <Table Style="{StaticResource BorderedTable}">
        <Table.Columns>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
            <TableColumn Width="*"></TableColumn>
        </Table.Columns>
        <TableRowGroup Name="rowsDetails">
            <TableRow FontWeight="Bold" >
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>用户名</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>姓名</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>性别</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>出生日期</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>所属部门</Paragraph>
                </TableCell>
                <TableCell Style="{StaticResource BorderedCell}">
                    <Paragraph>所属角色</Paragraph>
                </TableCell>           
            </TableRow>
        </TableRowGroup>
    </Table>
</FlowDocument>
View Code

6.使用的地方

 PrintPreviewWindow previewWnd = new PrintPreviewWindow("/AIStudio.Wpf.Base_Manage;component/Views/Base_UserFlowDocument.xaml", Data, new Base_UserDocumentRenderer());
            previewWnd.ShowDialog();

 

最后打印以更新到源码中,欢迎大家下载。

posted @ 2021-03-20 19:45  竹天笑  阅读(3801)  评论(6编辑  收藏  举报