使用 WPF 和 MySQL 搭建小型人资管理系统——打印功能
本系列的第四篇~~~
主要会介绍一下打印是怎么实现的,以及表格的排版怎么做
开始!
最终效果
这是员工详情页的界面,当点击右上方点击绿色的打印按钮后,就会弹出打印窗口。
员工详情页.png
打印窗口最终效果如下,可以根据录入的员工的信息生成表格。
打印窗口.png
代码结构
在查找资料的时候,看到了一篇前辈的文章,建议大家看看,我就是基于他的示例项目进行修改和整合从而实现的功能。
下面按照先后顺序进行说明。首先看一下打印按钮的点击事件是怎么写的:
private void Print_button_Click(object sender, RoutedEventArgs e)
{
//在这里我们将FlowDocument.xaml这个页面传进去,之后通过打印预览窗口的构造函数填充打印内容,如果有数据要插入应该在此传数据结构进去
Printing_preview previewWnd = new Printing_preview("FlowDocument.xaml",Print_preview_info_Class.staff_info());
previewWnd.Owner = (Window)this.Parent;
previewWnd.ShowInTaskbar = false;//设置预览窗体在最小化时不要出现在任务栏中
previewWnd.ShowDialog();//显示打印预览窗体
}
发现这里首先 new 了一个 Printing_preview 的对象,接下来显示它就可以了。
那么我们转到 Printing_preview 的定义看看,发现 VS 跳转到了 Printing_preview.xaml 页面,代码很少,window 标签中只有下面的代码:
<Grid>
<DocumentViewer Name="docViewer"></DocumentViewer>
</Grid>
其实只定义了一个文档查看器。所以界面只有下图这样的一个空壳:
Printing_preview.xaml 的界面.png那么我们要找的代码应该是定义类的 .cs 文件里了。继续查看 Printing_preview.xaml.cs,代码如下:
//此处省略了若干 using 语句
namespace DatabaseProject
{
/// <summary>
/// Printing_preview.xaml 的交互逻辑
/// </summary>
public partial class Printing_preview : Window
{
private delegate void LoadXpsMethod();//委托事件,相当于函数指针
//private readonly FlowDocument m_doc;//流文档
public static FlowDocument m_doc;
public Printing_preview(string strTmplName, object data)//从上面得到待打印的文档
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
m_doc = (FlowDocument)Application.LoadComponent(new Uri(strTmplName, UriKind.RelativeOrAbsolute));//从xaml文件中加载流文档对象
//m_doc.PagePadding = new Thickness(50);//设置页面与页面之间的边距宽度
m_doc.DataContext = data;
Dispatcher.BeginInvoke(new LoadXpsMethod(LoadXps), DispatcherPriority.ApplicationIdle);//“延后”调用,不然刚刚更改的数据不会马上更新,也就是说打印或者预览不到更新后的数据
}
public void LoadXps()
{
//构造一个基于内存的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);//在这里需要添加对.NET 4.0 的一些应用,比较蛋疼
writer.Write(((IDocumentPaginatorSource)m_doc).DocumentPaginator);
//获取这个基于内存的xps document的fixed documen
docViewer.Document = xpsDocument.GetFixedDocumentSequence();
//关闭基于内存的xps document
xpsDocument.Close();
}
}
}
我们重点关注其中的构造方法,有两个参数,第一个是要显示在 DocumentViewer 文档查看器中的文件,也就是流文档(Flow Document)的模版,单独的流文档无法被预览,必须把它放在一个容器中才可以查看,这里我们选择的容器就是 DocumentViewer,它只支持固定流文档,相当于只读。第二个参数是数据。
可以这么理解,传入的第一个参数决定了表格的格式、属性和外观,传入的第二个参数决定了填入表格的内容。
前两行是让窗口屏幕居中显示,第三行载入一个FlowDocument 模板,第四行把数据传给模板的 DataContext,第五行用来设置延后调用,这里我还没有完全理解相关的概念和 LoadXps() 方法,感兴趣的同学可以去前辈的文章进行更深入的了解。
还记得吗,打印按钮的响应事件的第一行是Printing_preview previewWnd = new Printing_preview("FlowDocument.xaml",Print_preview_info_Class.staff_info());
因此我们传入的流文件模板就是 FlowDocument.xaml,数据的设置则调用了 Print_preview_info_Class.staff_info() 这个函数。看来关键内容就在这两个地方了!
FlowDocument.xaml
前面已经说过,这个文件是一个流文档的模板。那么什么是流文档(Flow Document)呢?
Flow Document 是 WPF 3.0 中提供的一个用于显示的新功能,它给了开发人员另一种选择去显示内容。Flow Document 通过类似 HTML 文档的格式定义文本流,但其功能更强大,并可提供明显更先进的布局选项。它内置了很多的元素,例如,Figure, Paragraph, Section, Floater, Table, InlineUIContainer 等可以通过不同的布局和元素控制其显示方式。并且,它支持对图像的支持,使其可以像在 HTML 中一样随意控制。再加上其默认支持的导航,显示模式,搜索,让其内容展现方式有了进一步的提高。
需要说明一下,我使用的这个流文档模板的名字也是 FlowDocument,这纯粹是为了方便 : )
那么接下来,怎么在流文档中设置表格的各种属性呢?其实并不难,网上也有很多的教程。下面给出一个简化的 FlowDocument.xaml 供参考:
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
ColumnWidth="400"
FontSize="14"
FontFamily="宋体"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
PagePadding="40,20,40,20"
TextOptions.TextFormattingMode="Display">
<!--分别定义 Table 与 TableCell 的 style-->
<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>
<Paragraph FontSize="16"
BorderThickness="0"
TextAlignment="Center">人员信息调研评估表</Paragraph>
<Table Name="flow_table" Style="{StaticResource BorderedTable}">
<!--把表格整体划分为 10 列-->
<Table.Columns>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
<TableColumn Width="*"></TableColumn>
</Table.Columns>
<!--接下来只需要分别定义每一行-->
<TableRowGroup Name="rowsDetails">
<!--以“担任职务”所在的第七行的定义作为例子-->
<TableRow>
<TableCell Style="{StaticResource BorderedCell}"
ColumnSpan="2">
<Paragraph>担任职务</Paragraph>
</TableCell>
<TableCell Style="{StaticResource BorderedCell}"
ColumnSpan="2">
<Paragraph>
<Run Text="{Binding danrenzhiwu, Mode=OneWay}"></Run>
</Paragraph>
</TableCell>
<TableCell Style="{StaticResource BorderedCell}"
ColumnSpan="1">
<Paragraph>任职年限</Paragraph>
</TableCell>
<TableCell Style="{StaticResource BorderedCell}"
ColumnSpan="2">
<Paragraph>
<Run Text="{Binding renzhinianxian, Mode=OneWay}"></Run>
</Paragraph>
</TableCell>
<TableCell Style="{StaticResource BorderedCell}"
ColumnSpan="1">
<Paragraph>任职表现</Paragraph>
</TableCell>
<TableCell Style="{StaticResource BorderedCell}"
ColumnSpan="2">
<Paragraph>
<Run Text="{Binding renzhibiaoxian, Mode=OneWay}"></Run>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</FlowDocument>
注意一个 TableRow 中有若干个 TableCell,它们的 ColumnSpan 之和应该与开头定义的列数一致。另外还需要注意绑定数据的写法,Binding 后的变量名实际上都是 Print_preview_info_Class 类的变量。除此之外,还可在 TableCell 标签中设置 TextAlignment(对齐)、Foreground(颜色)等属性。
上面展示的是“担任职务”所在的第七行的定义,最终的效果如下图:
示例效果.pngPrint_preview_info_Class.staff_info()
这是第二个关键的地方,staff_info() 是 Print_preview_info_Class 类的方法,而 Print_preview_info_Class 类的定义在 Print_preview_info_Class.cs 中,这里给出其简化后的代码:
//此处省略若干 using 语句
namespace DatabaseProject
{
class Print_preview_info_Class
{
public string name { get; set; }
public string sex { get; set; }
public string age { get; set; }
public static Print_preview_info_Class staff_info()
{
Print_preview_info_Class print_info = new Print_preview_info_Class();
DataTable dtc1 = Detail_info_Page.ds1.Tables[0];
if (dtc1.Rows.Count == 1)
{
print_info.name = dtc1.Rows[0][1].ToString();
print_info.sex = dtc1.Rows[0][4].ToString();
print_info.age = dtc1.Rows[0][7].ToString();
}
return print_info;
}
}
}
这里以姓名、性别和年龄共三个变量举例,在 staff_info() 中,首先初始化一个 Print_preview_info_Class 变量 print_info,接下来对 print_info 的三个成员变量进行赋值,最后返回 print_info 对象。在赋值时,首先从员工详情页对应的类中取出包含姓名、性别和年龄等信息的 DataTable ,然后逐项赋值即可。如果查看员工详情页的表格,会发现姓名、性别和年龄对应的位置正好是第二、第五和第八项。
因为在之前的流文件模板中绑定了很多变量,它们对应着员工详情页中的很多 DataTable,因此一定要注意找到正确的 DataTable,以及变量在其中的位置。
在流文件模板里,绑定了 name 变量的代码如下:
<TableCell Style="{StaticResource BorderedCell}">
<Paragraph>
<Run Text="{Binding name, Mode=OneWay}"></Run>
</Paragraph>
</TableCell>
因此最终在表格中,姓名项对应的格子会填上赋给 name 的值,即员工详情页的第一个 DataTable 的第二项。
说到这里,可能有同学会有疑问,如何保证这里生成的表格信息就是对应的员工信息,毕竟有那么多员工。实际上我们之前已经说过,Detail_info_Page 页面,即员工详情页,是主窗口的静态变量。这就意味着它是唯一的。它就像一个抽屉,当我在人事管理页点击查看张三的员工详情页时,它就装上张三的东西;当我点击查看李四的员工详情页时,它就装上李四的东西。在上一篇文章中可以看到关于 Detail_info_Page 的更详细的阐述。
总结
说了这么多,最后简单做个总结。首先我们需要一个流文档模板,和一个与表格信息有关的类,它的成员变量就是流文档模板中绑定的变量。这个类还有一个方法,它返回一个该类的对象,并设置好对象的成员变量。
接下来,我们需要新建一个打印窗口,首先在 VS 中新建一个窗口,在对应的 xaml 文件中只包含一个 DocumentViewer,而在对应的 cs 文件中需要的代码在上面已给出。
现在条件已经齐全了,我们只需要编写按钮的点击事件,就可以在点击打印按钮后看到弹出的窗口中的流文档模板和已被赋值的绑定的变量了!
最后祝大家元宵快乐~~~