WPF使用XAML实现报表的一种思路(支持外部加载)

在WPF里做报告这一块以前用过多种实现方式,有用过RDLC、XPS等,看过一些开源的报表控件,跟RDLC的原理都差不多。

最近刚好又要实现报表的功能,就想使用一种新的方法:直接使用XAML实现,直接将界面呈现的内容打印出来。

 

打印WPF元素

在我前面的文章中,介绍过如何使用PrintDialog打印WPF元素,

https://www.cnblogs.com/zhaotianff/p/7340554.html

借助这种方法,我们可以直接打印整个WPF界面呈现的内容。

 

简易的报告模板

WPF提供了XamlReader/XamlWriter类来操作Xaml,可以实现XAML加载和保存。

动态加载的Xaml也是支持绑定的,我们可以借助这种模式来实现简易的报告模板功能。

 

首先我们创建一个报表内容,如下所示

1  <Grid x:Name="grid1">
2      <Image Source="{Binding Image}" Stretch="Uniform" Margin="0,0,0,20"></Image>
3      <Label Content="{Binding Text}" HorizontalAlignment="Center" VerticalAlignment="Bottom" FontFamily="Arial"></Label>
4  </Grid>

 

然后我们使用XamlWriter类,将Xaml保存出来

1  var xaml = XamlWriter.Save(this.grid1);
2  System.IO.File.WriteAllText("Template.xaml", xaml);

 

保存出来的内容如下

 

可以看到内容这里都是为null,我们可以改成绑定,如下所示:

1 <Grid Name="grid1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
2     <Image Source="{Binding Image}" Stretch="Uniform" Margin="0,0,0,20" />
3     <Label Content="{Binding Text}" FontFamily="Arial" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
4 </Grid>

 

定义一个ViewModel用于绑定

这里创建了几个简单的属性用于界面显示

 1 public class ReportViewModel : INotifyPropertyChanged
 2 {
 3     private string image;
 4 
 5     public string Image
 6     {
 7         get => this.image;
 8         set
 9         {
10             this.image = value;
11             RaiseChanged("Image");
12         }
13     }
14 
15     private string text;
16     public string Text
17     {
18         get => this.text;
19         set
20         {
21             this.text = value;
22             RaiseChanged("Text");
23         }
24     }
25 
26     private int width;
27     public int Width
28     {
29         get => this.width;
30         set
31         {
32             this.width = value;
33             RaiseChanged("Width");
34         }
35     }
36 
37     private int height;
38     public int Height
39     {
40         get => this.height;
41         set
42         {
43             this.height = value;
44             RaiseChanged("Height");
45         }
46     }
47 
48     public event PropertyChangedEventHandler? PropertyChanged;
49 
50     private void RaiseChanged(string propertyName)
51     {
52         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
53     }
54 }

 

新建一个界面,用于加载这个报告模板显示

 1  <Grid Name="grid">
 2      <Grid.RowDefinitions>
 3          <RowDefinition/>
 4          <RowDefinition Height="30"/>
 5      </Grid.RowDefinitions>
 6 
 7      <Button Content="加载报告内容" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center" Margin="-120,0,0,0" Width="88" Height="28" Click="Button_Click"></Button>
 8      <Button Content="打印" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center" Margin="120,0,0,0" Width="88" Height="28" Click="Button_Click_1"></Button>
 9 
10  </Grid>

 

加载报告内容按钮事件

 1  private void Button_Click(object sender, RoutedEventArgs e)
 2  {
 3      var xaml = File.ReadAllText(Environment.CurrentDirectory + "\\Template.xaml");
 4      using (StringReader stringReader = new StringReader(xaml))
 5      {
 6          XmlReader xmlReader = XmlReader.Create(stringReader);
 7          dynamicGrid = (Grid)XamlReader.Load(xmlReader); //dynamicGrid为全局Grid变量
 8          this.grid.Children.Add(dynamicGrid);
 9          ReportViewModel reportModel = new ReportViewModel();
10          reportModel.Image = "https://myfreetime.cn/usr/uploads/2024/4/%E6%B8%85%E5%B9%B3%E8%B0%83%C2%B7%E5%90%8D%E8%8A%B1%E5%80%BE%E5%9B%BD%E4%B8%A4%E7%9B%B8%E6%AC%A2/jk.jpg";
11          reportModel.Text = "HelloWorld";
12          reportModel.Width = 300;
13          reportModel.Height = 600;
14          dynamicGrid.DataContext = reportModel;
15      }           
16  }

 

 

在界面点击加载后,可以看到内容被显示出来了

 

打印按钮事件

 1 private void Button_Click_1(object sender, RoutedEventArgs e)
 2 {
 3     PrintDialog pd = new PrintDialog();
 4     Window window = Window.GetWindow(dynamicGrid);
 5     Point point = dynamicGrid.TransformToAncestor(window).Transform(new Point(0, 0));//获取当前控件 的坐标
 6     dynamicGrid.Measure(new Size(dynamicGrid.ActualWidth, dynamicGrid.ActualHeight));
 7     dynamicGrid.Arrange(new Rect(new Size(dynamicGrid.ActualWidth, dynamicGrid.ActualHeight)));
 8     pd.PrintVisual(dynamicGrid, "");
 9     dynamicGrid.Arrange(new Rect(point.X, point.Y, dynamicGrid.ActualWidth, dynamicGrid.ActualHeight));//设置为原来的位置
10 }

 

修改本地的XAML模板文件,就可以实现动态变更模板,例如,我增加WidthHeight字段显示

 

 

到这一步,我们就实现了一个简易的报告模板功能,一些小型的报表需求应该还是能满足的。最重要的是可以直接使用XAML,开发起来比较方便。

 

 

多页报告

多页报告时主要用到FixedDocument对象,首先我们创建一个FixedDocument对象,并设置参数

1  FixedDocument document = new FixedDocument();
2  //设置纸张大小
3  document.DocumentPaginator.PageSize = new Size(600, 800);

 

FixedDocument增加一页

1   //增加一页
2   FixedPage page1 = new FixedPage();
3   page1.Width = document.DocumentPaginator.PageSize.Width;
4   page1.Height = document.DocumentPaginator.PageSize.Height;

 

我们这里增加一个Image控件放到第一页

1 //增加一个图像显示到第一页
2 Image image = new Image();
3 image.Width = document.DocumentPaginator.PageSize.Width;
4 image.Height = document.DocumentPaginator.PageSize.Height;
5 image.Stretch = Stretch.Uniform;
6 image.Source = new BitmapImage(new Uri("https://myfreetime.cn/usr/uploads/2024/4/%E6%B8%85%E5%B9%B3%E8%B0%83%C2%B7%E5%90%8D%E8%8A%B1%E5%80%BE%E5%9B%BD%E4%B8%A4%E7%9B%B8%E6%AC%A2/jk.jpg"));
7 page1.Children.Add(image);

 

然后将页添加到FixedDocument中

1  //添加页到Document中
2  PageContent page1Content = new PageContent();
3  ((IAddChild)page1Content).AddChild(page1);
4  document.Pages.Add(page1Content);

 

用上面一样的方法添加第二页内容

 1  FixedPage page2 = new FixedPage();
 2  page2.Width = document.DocumentPaginator.PageSize.Width;
 3  page2.Height = document.DocumentPaginator.PageSize.Height;
 4  TextBlock page2Text = new TextBlock();
 5  page2Text.Text = "HelloWorld";
 6  page2Text.Width = document.DocumentPaginator.PageSize.Width;
 7  page2Text.Height = document.DocumentPaginator.PageSize.Height;
 8  page2Text.FontSize = 40;
 9  page2Text.HorizontalAlignment = HorizontalAlignment.Center;
10  page2Text.VerticalAlignment = VerticalAlignment.Center;
11  page2.Children.Add(page2Text);
12  PageContent page2Content = new PageContent();
13  ((IAddChild)page2Content).AddChild(page2);
14  document.Pages.Add(page2Content);

 

此时我们已经拥有一个多页的文档了,借用PrintDialogPrintDocument功能,就可以打印多页文档。

1 PrintDialog printDialog = new PrintDialog();
2 printDialog.PrintDocument(document.DocumentPaginator, "");

 

打印输出效果如下:

 

实时预览多页报告

使用DocumentViewer控件,就可以预览FixedDocument内容。

XAML

1   <DocumentViewer x:Name="viewer"></DocumentViewer>

C#

1  this.viewer.Document = document;

 

显示效果

 

从模板加载多页报告

我们将前面的模板XAML文件进行升级,使其具备多页的功能,如下:

 1 <Pages>
 2     <FixedPage xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 3       <Grid >
 4         <Image Source="{Binding Image}" Stretch="Uniform" Margin="0,0,0,20" />
 5         <Label Content="{Binding Text}" FontFamily="Arial" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
 6         <Label Content="{Binding Width}" FontFamily="Arial" HorizontalAlignment="Left" VerticalAlignment="Bottom" />
 7         <Label Content="{Binding Height}" FontFamily="Arial" HorizontalAlignment="Left" Margin="60,0,0,0" VerticalAlignment="Bottom" />
 8     </Grid>
 9     </FixedPage>
10     <FixedPage xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
11         <Grid >
12         <Border CornerRadius="10">
13             <Image Source="{Binding Image}" Stretch="Uniform" Margin="0,0,0,20" />
14         </Border>
15     </Grid>
16     </FixedPage>
17 </Pages>

 

因为XamlReader是不能识别Pages标签的,所以我们需要进行简单的处理

1 //加载XAML
2 var xaml = System.IO.File.ReadAllText(Environment.CurrentDirectory + "\\Template.xaml");
3 StringReader sr = new StringReader(xaml);
4 XDocument doc = XDocument.Load(sr);
5 var pages = doc.Root.Elements();

 

pages就是FixedPage的集合,此时我们再用XamlReader进行读取就可以识别了。

 

完整的动态读取代码如下:

 1 //加载XAML
 2 var xaml = System.IO.File.ReadAllText(Environment.CurrentDirectory + "\\Template.xaml");
 3 StringReader sr = new StringReader(xaml);
 4 XDocument doc = XDocument.Load(sr);
 5 var pages = doc.Root.Elements();
 6 
 7 FixedDocument document = new FixedDocument();
 8 //设置纸张大小
 9 document.DocumentPaginator.PageSize = new Size(600, 800);
10 
11 //动态读取第一页
12 StringReader xamlReader = new StringReader(pages.ElementAt(0).ToString());
13 XmlReader xmlReader = XmlReader.Create(xamlReader);
14 FixedPage page1 = (FixedPage)XamlReader.Load(xmlReader);
15 var page1Container = page1.Children[0] as Grid;
16 page1Container.Width = document.DocumentPaginator.PageSize.Width;
17 page1Container.Height = document.DocumentPaginator.PageSize.Height;
18 ReportViewModel reportModel = new ReportViewModel();
19 reportModel.Image = "https://myfreetime.cn/usr/uploads/2024/4/%E6%B8%85%E5%B9%B3%E8%B0%83%C2%B7%E5%90%8D%E8%8A%B1%E5%80%BE%E5%9B%BD%E4%B8%A4%E7%9B%B8%E6%AC%A2/jk.jpg";
20 reportModel.Text = "HelloWorld";
21 reportModel.Width = 300;
22 reportModel.Height = 600;
23 //设置ViewModel
24 page1.DataContext = reportModel;
25 
26 //添加页到Document中
27 PageContent page1Content = new PageContent();
28 ((IAddChild)page1Content).AddChild(page1);
29 document.Pages.Add(page1Content);
30 
31 //动态读取第二页
32 StringReader xamlReader2 = new StringReader(pages.ElementAt(1).ToString());
33 XmlReader xmlReader2 = XmlReader.Create(xamlReader2);
34 FixedPage page2 = (FixedPage)XamlReader.Load(xmlReader2);
35 var page2Container = page2.Children[0] as Grid;
36 page2Container.Width = document.DocumentPaginator.PageSize.Width;
37 page2Container.Height = document.DocumentPaginator.PageSize.Height;
38 ReportViewModel reportModel2 = new ReportViewModel();
39 reportModel2.Image = "https://myfreetime.cn/usr/uploads/2024/4/%E6%B8%85%E5%B9%B3%E8%B0%83%C2%B7%E5%90%8D%E8%8A%B1%E5%80%BE%E5%9B%BD%E4%B8%A4%E7%9B%B8%E6%AC%A2/jk2.jpg";
40 reportModel2.Text = "HelloWorld";
41 //设置ViewModel
42 page2.DataContext = reportModel2;
43 
44 //添加页到Document中
45 PageContent page2Content = new PageContent();
46 ((IAddChild)page2Content).AddChild(page2);
47 document.Pages.Add(page2Content);
48 
49 viewer.Document = document;
50 sr.Dispose();
51 xamlReader.Dispose();
52 xamlReader2.Dispose();

 

读取完成后,显示在DocumentViewer控件中,效果如下:

 

到这里基础的打印功能应该都是可以实现了,在给客户使用时,可以通过XAML设计新的报告模板进行分发。

 

如果需要将报告打印为PDF,可以参考我前面的文章:

https://www.cnblogs.com/zhaotianff/p/17247683.html

 

示例代码

示例代码 

 

参考资料:

https://www.nbdtech.com/Blog/archive/2009/03/19/wpf-printing-part-1-printing-visuals.aspx

https://www.nbdtech.com/Blog/archive/2009/04/20/wpf-printing-part-2-the-fixed-document.aspx

https://www.nbdtech.com/Blog/archive/2009/05/19/wpf-printing-part-3-ndash-sizes.aspx

https://www.nbdtech.com/Blog/archive/2009/07/09/wpf-printing-part-4-ndash-print-preview.aspx

 

posted @ 2024-06-18 17:22  zhaotianff  阅读(151)  评论(0编辑  收藏  举报