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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
<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的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<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代码中添加数据行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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 @   竹天笑  阅读(4043)  评论(6编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示