在我们设计软件的很多地方,我们都看到需要导入导出表格数据,主要是为了方便客户快速的数据处理和共享功能方面,本文介绍了基于WPF的DataGrid数据的导入导出操作。
在实现数据导入导出功能之前,我们需要在主界面上为客户提供相关的操作按钮。如下界面所示,列表顶部提供导入Excel、导出PDF、导出Excel。
由于这些操作函数基本上每个页面模块都会用到,所以我们尽量将它们抽象到基类中,并提供通用的处理操作。如果存在差异,它们也可以被某些属性或事件方法覆盖。实现它的方法。
所以我们在Xaml中定义按钮时,基本上都是调用视图模型方法进行泛化处理,如下代码所示。
<按钮 边距="5" hc:IconElement.Geometry="{StaticResource t_import}" Command="{绑定 ImportExcelCommand}" 内容="导入Excel" 样式="{静态资源按钮警告}"/> <按钮 边距="5" hc:IconElement.Geometry="{静态资源保存几何}"Command="{绑定ViewModel.ExportPdfCommand}" CommandParameter="用户信息列表" 内容="导出PDF" 样式="{静态资源按钮成功}"/> <按钮 边距="5" hc:IconElement.Geometry="{静态资源保存几何}" Command="{绑定ViewModel.ExportExcelCommand}" CommandParameter="用户信息列表" 内容="导出Excel" 样式="{静态资源按钮成功}" />
导入的处理操作函数ImportExcelComand的定义如下(注意这里声明了RelayCommand),代码会自动生成Command后缀的Command方法。
////// 将内容导出到 Excel /// [继电器命令] 私有 void ImportExcel() { var页面 = App.GetService<ImportExcelData>(); 页!.ViewModel.Items?.Clear();page!.ViewModel.TemplateFile = $"系统用户信息-Template.xls"; 页!.OnDataSave -= ExcelData_OnDataSave; 页!.OnDataSave += ExcelData_OnDataSave; // 导航至指定页面 ViewModel.Navigate(typeof(ImportExcelData)); }
其中ImportExcelData 是我们定义的通用导入页面表单类。这里我们只需要实现一些属性设置(根据不同的子类进行调整,后期可以用代码生成工具生成),以及一些事件用于子类延迟实现,从而实现自定义的数据处理功能。
我们将在下面详细说明批量导入处理的细节。
导出数据到Excel在我们Winform端是很常见的,这里在WPF中也采用同样的处理方式。一般使用Excel的操作组件的封装类来实现,可以基于NPOI,也可以基于Aspose.Cell。您可以根据自己的需求实现简单的封装调用。
导出到Excel,首先需要弹出目录选择对话框选择目录,然后用它生成Excel文件,如下界面所示。
对于这个处理,由于WPF可以调用.net中的System.Windows.Forms,所以我们可以直接调用里面的对话框处理包。这个类来自我们Winform的UI公共类库部分。
在上一篇文章中,我们介绍了为了WPF开发的方便,我们设计了几个视图基类来减少代码处理。
对于不同的业务类,我们只需要根据实际情况生成对应的业务视图模型类即可。
我们可以把一般的导出操作放到这个视图基类BaseListViewModel中,如下代码所示。
//////触发导出Excel处理命令 /// [继电器命令] 受保护 虚拟 异步任务 ExportExcel( string) 标题 = "列表数据") { var表 = 等待 这个.ConvertItems(这个.物品); BaseExportExcel(表格,标题); }
而其中,Excel提供了处理DataTable的通用方法。
////// 用于重写的基类函数,导出到 Excel /// 公共 虚拟 void BaseExportExcel(DataTable 表,字符串标题 = "列表数据") { 字符串文件 = FileDialogHelper.SaveExcel(字符串.Format( ) "{0}.xls",标题 )); if(!字符串.IsNullOrEmpty(文件)) { 尝试 { 字符串错误 = ""; AsposeExcelTools.DataTableToExcel2(表,文件,出错误); if(!字符串.IsNullOrEmpty(错误)) { Messagedxutil.showerror(String.Format("导出excel错误:{0}",错误)); } 否则 { if(MessageDxUtil.ShowYesNoAndTips("导出成功,是否要打开文件?" ) == System.Windows.MessageBoxResult 。是的) { Process.Start("explorer.exe",文件); } } } catch(例外情况) { LogTextHelper.Error(前);MessageDxUtil.ShowError(例如.Message); } } }
FileDialogHelper.SaveExcel的代码如下。
////// 保存Excel对话框并返回保存完整路径 /// /// 公共 静态 字符串 SaveExcel (字符串文件名,字符串初始目录) { 返回保存("保存Excel",ExcelFilter,文件名,初始目录); } /// /// 弹出指定标题的保存文件对话框 /// /// 对话框标题 /// 后缀过滤 /// 默认文件名 /// 初始化目录 /// 公共 静态字符串保存 (字符串标题,字符串过滤器,字符串文件名,字符串初始目录) { //多语言支持 标题 = JsonLanguage.Default.GetString(title); var对话框=新SaveFileDialog();dialog.Filter = 过滤器; 对话框.标题=标题; dialog.FileName = 文件名; 对话框.RestoreDirectory = true; if (!字符串.IsNullOrEmpty(initialDirectory)) { dialog.InitialDirectory =initialDirectory; } if (dialog.ShowDialog() == DialogResult.OK) { 返回dialog.FileName; } 返回 串.空; }
其中SaveFileDialog是.net中System.Windows.Forms中的内容,可以直接被WPF调用。
DataTableToExcel2方法,是我们使用Aspose.Cell封装的调用,主要用于快速处理DataTable到Excel的操作封装。我们还可以使用其他Excel操作封装,比如NPOI等来实现这一点。
代码如下所示。
////// 将 DataTabel 转换为 Excel 文件 /// /// DataTable 对象 /// 目标文件路径,Excel文件的完整路径 /// 错误信息:返回错误信息,无错误返回“” /// 公共 静态boolDataTableToExcel2(DataTable 数据表,string 文件路径,out string 错误) { 错误= ""; var wb = new Aspose.Cells.Workbook(); 尝试 { if(数据表==null) { 错误= "DataTableToExcel:数据表为空"; 返回假; } //为单元格添加样式 var style = wb.CreateStyle(); // 设置中心 style.HorizontalAlignment = www.sychzs.cn; //设置背景颜色 style.ForegroundColor = System.Drawing.Color.FromArgb(153, 204, 0); style.Pattern =BackgroundType.Solid; style.Font.IsBold = true; int rowIndex = 0; for (int i = 0; i < datatable.Columns.Count; i++) { DataColumn col = datatable.Columns[i];stringcolumnName = col.Caption??col.ColumnName; wb.Worksheets[0].Cells[rowIndex, i].PutValue(columnName); wb.Worksheets[0].Cells[rowIndex, i].SetStyle(style); } rowIndex++; foreach(DataRow 行in 数据表.Rows) { for (int i = 0; i < datatable.Columns.Count; i++) { wb.Worksheets[0].Cells[rowIndex, i].PutValue(row[i].ToString()); } rowIndex++; } for (int k = 0; k < datatable.Columns.Count; k++) { wb.工作表[0].AutoFitColumn(k, 0, 150); } wb.Worksheets[0].FreezePanes(1, 0, 1, datatable.Columns.Count); wb.保存(文件路径); 返回真; } catch(例外 e) { 错误 = 错误 + " DataTableToExcel: " + e.Message; 返回假; } }
导出Excel的内容如下图所示。另外导出文档的内容,我们可以用于导入的数据模板的。
我们可以根据需要设置要导出的列。
同样,导出数据为PDF的处理操作类似。它还利用视图基类的封装方法来实现快速导出为PDF处理。下面是view基类中的实现方法。
////// 触发导出PDF处理命令 /// [继电器命令] 受保护 虚拟 异步任务 ExportPdf( string title = "列表数据") { var表 = 等待这个.ConvertItems (这个.物品); BaseExportPdf(表格,标题); } /// ///可重写的基类函数,导出PDF /// 公共 虚拟 void BaseExportPdf (DataTable表格,字符串标题 = "列出数据") { var pdfFile = FileDialogHelper.SavePdf(); if (!pdfFile.IsNullOrEmpty()) { 通过将List
转换为常规DataTable进行处理,我们可以使用我们文章《在Winform分页控件中集成导出PDF文档的功能》中介绍的PDF导出功能将WPF数据导出为PDF。 上面的TextSharpHelper 是对itext7的封装,实现PDF导出处理。
介绍完相关的Nugget类后,封装它的辅助类代码如下。
//////基于iText7的PDF导出处理 /// 公共 静态类 TextSharpHelper { /// /// 数据表转PDF方法 /// /// 标题内容 /// data表数据 /// PDF文件保存路径 /// 是否横向打印,默认为true /// 每页是否包含页眉信息 /// 头部对齐方式默认居中对齐 /// 标题字体大小 /// 行记录字体大小 /// 头部固定高度,否则自适应 /// 公共 静态 bool ExportTableToPdf(string) 标题,数据表数据,字符串pdf文件,bool isLandscape = true,bool includeHeader = true,iText.Layout.Properties.Hori zontalAlignment headerAlignment = iText.Layout.Properties.Horizowww.sychzs.cn, 浮动 headerFontSize = 9f, 浮动 rowFontSize = 9f, 浮动? headerFixHeight = 空) {var writer = new PdfWriter(pdfFile); PdfDocument pdf = new PdfDocument(作者); pdf.SetDefaultPageSize(isLandscape ? PageSize.A4.Rotate() : PageSize.A4); //A4横向 var doc = new文档(pdf);//设置标题 if(!字符串.IsNullOrEmpty(标题)) { var param = new 段落(标题) .SetFontColor(www.sychzs.cn) .SetBold() //粗体.SetFontSize(标题字体大小 + 5) .SetTextAlignment(www.sychzs.cn); //居中 doc.Add(param); } var表 = 新表(data.Columns.Count) .SetTextAlignment(www.sychzs.cn) .SetVerticalAlignment(VerticalAlignment.MIDDLE) .SetWidth(new UnitValue(UnitValue.PERCENT, 100));//缩放比例 table.UseAllAvailableWidth(); //添加表头 foreach(DataColumn dc in data.Columns) { var title = !string.IsNullOrEmpty(dc.Caption) ? dc.Caption : dc.ColumnName; var单元格 = new单元格().Add(new段落(标题)) .SetBold() .SetVerticalAlignment(VerticalAlignment.MIDDLE) .SetHorizontalAlignment(标题对齐) .SetPadding(1) .SetFontSize(headerFontSize); if(headerFixHeight.HasValue) { cell.SetHeight(newUnitValue(UnitValue.POINT, headerFixHeight.Value)); }表.AddHeaderCell(单元格); } //插入数据 var colorWhite = Color.ConvertRgbToCmyk(iText.Kernel.Colors.WebColors.GetRGBColor("White" ));//系统.绘图.颜色.白色; var colorEvent = iText.Kernel.Colors.WebColors.GetRGBColor("LightCyan"); // System.Drawing.Color。浅青色; var EventRowBackColor = Color.ConvertRgbToCmyk(colorEvent); for (int i = 0; i < data.Rows.Count; i++) { table.StartNewRow();//第一列开启新行 var backgroudColor = ((i % 2 == 0) ? colorWhite : EventRowBackColor); for (int j = 0; j < data.Columns.Count; j++) { var text = data.Rows[i][j].ToString(); var单元格=新单元格() .SetBackgroundColor(背景颜色) .SetFontSize(行字体大小) .SetVerticalAlignment(VerticalAlignment.MIDDLE).Add(new段落(文本)); 表.AddCell(单元格); } } doc.Add(表); pdf.关闭(); 作者.关闭(); 返回真; } } 导出PDF的文档效果如下。
4。导入Excel数据
Excel数据导入,可以减少批量处理数据的难度和一一录入的繁琐界面。这是常见的操作方法。我们主要提供固定模板供客户下载并录入数据,然后提交进行批量处理。只需导入即可。
导入接口处理,我们这里涉及到一个通用的导入接口(类似于WInform端的接口),这样我们每个不同的业务导入流程都可以复用,只需要设置一些不同的属性和处理一些事件。就是这样,下面是大致的界面效果。
这里主要介绍一下它的设计方法。前面我们介绍过,在业务接口中调用时,如下代码所示。
////// 将内容导出到 Excel /// [继电器命令] 私有void导入Excel() { var页面=App.GetService ();页!.ViewModel.Items?.Clear(); 页!.ViewModel.TemplateFile = $"系统用户信息模板.xls" ; 页!.OnDataSave -= ExcelData_OnDataSave; 页!.OnDataSave += ExcelData_OnDataSave; // 导航至指定页面 ViewModel.Navigate(typeof(ImportExcelData)); } 这种通用形式的视图模型定义了模板的文件名、通用数据DataTable的集合、以及实现子类导入转换的事件。其视图模型类代码如下所示。
////// 查看批量导入Excel数据的模型基类 /// 公共部分类ImportExcelDataViewModel:BaseViewModel { [ObservableProperty] 私有 字符串 templateFile; [ObservableProperty] 私有字符串导入文件路径; [ObservableProperty] 私有数据表项目; 为了方便客户打开模板文件,以便可以用来录入Excel数据,我们可以在本地打开模板文件。
////// 打开模板文件 /// [中继命令] 私有 void OpenFile() { if (!这个.TemplateFile.IsNullOrEmpty()) { var realFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, this.TemplateFile); if(文件.Exists(realFilePath)) { Process.Start("explorer.exe", realFilePath); } 否则 { MessageDxUtil.ShowError($"找不到模板文件:{realFilePath}"); } } } 在通用导入页面的后台代码中,我们需要实现选择Excel后将数据显示到DataGrid、批量保存数据等一些操作。
//////选择Excel文件后,显示Excel中的表格数据 /// [中继命令] 私有 void BrowseExcel() { 字符串 文件 = FileDialogHelper.OpenExcel(); if(!字符串.IsNullOrEmpty(文件)) { 这个.ViewModel.ImportFilePath = 文件;ViewData(); } } ////// 查看Excel文件并在界面上进行操作 /// 私有 void查看数据() { if (这个.txtFilePath.Text == "") { MessageDxUtil.ShowTips("请选择指定的Excel文件"); 返回; } 尝试 { var myDs = new DataSet(); 字符串错误 = ""; AsposeExcelTools.ExcelFileToDataSet(这个.txtFilePath.Text, 出 myDs,出错误); 这个.ViewModel.Items = myDs.Tables[0]; } catch(例外情况) { LogTextHelper.Error(前); MessageDxUtil.ShowError(例如.Message); } } 进口加工操作代码如下。
////// 批量保存数据到数据库 /// /// [继电器命令] 私有异步任务 SaveData() {if(ViewModel.Items == null || ViewModel.Items?.行?.Count == 0) 返回 新 CommonResult(假); ? )==系统。 Windows.MessageBoxResult.Yes) { var dt = 这个.ViewModel.Items; foreach(DataRow dr in dt.Rows) { 尝试 { 等待OnDataSave(dr); } catch(例外情况) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } return new CommonResult(true, "操作成功"); } return new CommonResult(false); } 注意,我们这里使用了事件的处理,把数据的转换逻辑留给子类去实现的。
////// 数据保存的事件 /// public event SaveDataHandler OnDataSave; 这样我们在用户信息的导入页面UserListPage.xaml.cs里面的代码就可以根据实际的情况进行实现事件了。
////// 导出内容到Excel /// [RelayCommand] private void ImportExcel() { var page = App.GetService (); page!.ViewModel.Items?.Clear(); page!.ViewModel.TemplateFile = $"系统用户信息-模板.xls"; page!.OnDataSave -= ExcelData_OnDataSave; page!.OnDataSave += ExcelData_OnDataSave; //导航到指定页面 ViewModel.Navigate(typeof(ImportExcelData)); } 这个事件的实现,主要就是把个性化的用户信息(用户信息模板里面定义的字段),转换为DataTable的行信息即可,如下代码所示。具体根据模板设计的情况进行修改即可。
具体就不再一一赘述,主要就是基类逻辑和具体实现分离,实现不同的业务功能处理即可。
以上即是我们一个列表通用页面里面,往往需要用到的通用性的导入、导出操作的介绍,希望对读者在开发WPF应用功能上有所启发,有所参考,善莫大焉。