- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
在我们设计软件的很多地方,都看到需要对表格数据进行导入和导出的操作,主要是方便客户进行快速的数据处理和分享的功能,本篇随笔介绍基于WPF实现DataGrid数据的导入和导出操作.
在我们实现数据的导入导出功能之前,我们在主界面需要提供给客户相关的操作按钮,如下界面所示,在列表的顶端提供导入Excel、导出PDF、导出Excel.
由于这些操作功能基本上在各个页面模块,可能都会用到,因此尽可能的抽象到基类,以及提供通用的处理操作,实在有差异的,也可以通过一些属性或者事件方法的覆盖方式来实现即可.
因此我们在Xaml里面定义按钮的时候,基本上是调用视图模型的方法来通用化的处理,如下代码所示.
< Button Margin ="5" hc:IconElement.Geometry =" {StaticResource t_import} " Command =" { Binding ImportExcelCommand } " Content ="导入Excel" Style =" {StaticResource ButtonWarning} " /> < Button Margin ="5" hc:IconElement.Geometry =" {StaticResource SaveGeometry} " Command =" {Binding ViewModel.ExportPdfCommand } " CommandParameter ="用户信息列表" Content ="导出PDF" Style =" {StaticResource ButtonSuccess} " /> < Button Margin ="5" hc:IconElement.Geometry =" {StaticResource SaveGeometry} " Command =" { Binding ViewModel.ExportExcelCommand } " CommandParameter ="用户信息列表" Content ="导出Excel" Style =" {StaticResource ButtonSuccess} " />
而导入的处理操作函数ImportExcelComand的定义如下所示(注意这里声明了RelayCommand)代码会自动生成Command的后缀Command方法的.
/// <summary> /// 导出内容到Excel /// </summary> [ RelayCommand ] private void ImportExcel() { var page = App.GetService< ImportExcelData > (); page !.ViewModel.Items? .Clear(); page !.ViewModel.TemplateFile = $ " 系统用户信息-模板.xls " ; page !.OnDataSave -= ExcelData_OnDataSave; page !.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 里面即可,如下代码所示.
/// <summary> /// 触发导出Excel处理命令 /// </summary> [RelayCommand] protected virtual async Task ExportExcel( string title = " 列表数据 " ) { var table = await this .ConvertItems( this .Items); BaseExportExcel(table, title); }
而其中对于DataTable的处理Excel,提供一个通用的方法.
/// <summary> /// 可供重写的基类函数,导出Excel /// </summary> public virtual void BaseExportExcel(DataTable table, string title = " 列表数据 " ) { string file = FileDialogHelper.SaveExcel ( string .Format( " {0}.xls " , title)); if (! string .IsNullOrEmpty(file)) { try { string error = "" ; AsposeExcelTools.DataTableToExcel2 (table, file, out error); if (! string .IsNullOrEmpty(error)) { MessageDxUtil.ShowError( string .Format( " 导出Excel出现错误:{0} " , error)); } else { if (MessageDxUtil.ShowYesNoAndTips( " 导出成功,是否打开文件? " ) == System.Windows.MessageBoxResult.Yes) { Process.Start( " explorer.exe " , file); } } } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } }
其中 FileDialogHelper.SaveExcel 的代码如下所示.
/// <summary> /// 保存Excel对话框,并返回保存全路径 /// </summary> /// <returns></returns> public static string SaveExcel ( string filename, string initialDirectory) { return Save( " 保存Excel " , ExcelFilter, filename, initialDirectory); } /// <summary> /// 以指定的标题弹出保存文件对话框 /// </summary> /// <param name="title"> 对话框标题 </param> /// <param name="filter"> 后缀名过滤 </param> /// <param name="filename"> 默认文件名 </param> /// <param name="initialDirectory"> 初始化目录 </param> /// <returns></returns> public static string Save ( string title, string filter, string filename, string initialDirectory) { // 多语言支持 title = JsonLanguage.Default.GetString(title); var dialog = new SaveFileDialog (); dialog.Filter = filter; dialog.Title = title; dialog.FileName = filename; dialog.RestoreDirectory = true ; if (! string .IsNullOrEmpty(initialDirectory)) { dialog.InitialDirectory = initialDirectory; } if (dialog.ShowDialog() == DialogResult.OK) { return dialog.FileName; } return string .Empty; }
而其中 SaveFileDialog 是属于.net 中System.Windows.Forms里面的内容,WPF可以直接调用.
而DataTableToExcel2 方法,这是我们封装的一个使用Aspose.Cell的调用,主要用于快速处理DataTable到Excel的操作封装,我们也可可以利用其它操作Excel的封装,如NPOI等都可以实现.
代码如下所示.
/// <summary> /// 把DataTabel转换成Excel文件 /// </summary> /// <param name="datatable"> DataTable对象 </param> /// <param name="filepath"> 目标文件路径,Excel文件的全路径 </param> /// <param name="error"> 错误信息:返回错误信息,没有错误返回"" </param> /// <returns></returns> public static bool DataTableToExcel2(DataTable datatable, string filepath, out string error) { error = "" ; var wb = new Aspose.Cells.Workbook(); try { if (datatable == null ) { error = " DataTableToExcel:datatable 为空 " ; return false ; } // 为单元格添加样式 var style = wb.CreateStyle(); // 设置居中 style.HorizontalAlignment = Aspose.Cells.TextAlignmentType.Center; // 设置背景颜色 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]; string columnName = col.Caption ?? col.ColumnName; wb.Worksheets[ 0 ].Cells[rowIndex, i].PutValue(columnName); wb.Worksheets[ 0 ].Cells[rowIndex, i].SetStyle(style); } rowIndex ++ ; foreach (DataRow row in datatable.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.Worksheets[ 0 ].AutoFitColumn(k, 0 , 150 ); } wb.Worksheets[ 0 ].FreezePanes( 1 , 0 , 1 , datatable.Columns.Count); wb.Save(filepath); return true ; } catch (Exception e) { error = error + " DataTableToExcel: " + e.Message; return false ; } }
导出Excel的内容如下界面所示。另外导出文档的内容,我们可以用于导入的数据模板的.
我们可以根据需要设置要导出的列即可.
。
同样,数据导出到PDF的处理操作类似,也是通过视图基类的封装方法,实现快速的导出到PDF处理,如下是视图基类里面的实现方法.
/// <summary> /// 触发导出PDF处理命令 /// </summary> [RelayCommand] protected virtual async Task ExportPdf( string title = " 列表数据 " ) { var table = await this . ConvertItems ( this .Items); BaseExportPdf (table, title); } /// <summary> /// 可供重写的基类函数,导出PDF /// </summary> public virtual void BaseExportPdf (DataTable table, string title = " 列表数据 " ) { var pdfFile = FileDialogHelper.SavePdf (); if (! pdfFile.IsNullOrEmpty()) { bool isLandscape = true ; // 是否为横向打印,默认为true bool includeHeader = true ; // 是否每页包含表头信息 var headerAlignment = iText.Layout.Properties.HorizontalAlignment.CENTER; // 头部的对其方式,默认为居中 float headerFontSize = 9f; // 头部字体大小 float rowFontSize = 9f; // 行记录字体大小 float ? headerFixHeight = null ; // 头部的固定高度,否则为自适应 var success = TextSharpHelper.ExportTableToPdf (title, table, pdfFile, isLandscape, includeHeader, headerAlignment, headerFontSize, rowFontSize, headerFixHeight); // 提示信息 var message = success ? " 导出操作成功 " : " 导出操作失败 " ; if (success) { Growl.SuccessGlobal(message); Process.Start( " explorer.exe " , pdfFile); } else { Growl.ErrorGlobal(message); } } }
通过把List<T>的列表转换为常规的DataTable来处理,我们就可以利用之前我们随笔《 在Winform分页控件中集成导出PDF文档的功能 》介绍到的PDF导出函数来实现WPF数据导出到PDF的处理.
上面的 TextSharpHelper 就是对于itext7进行的封装,实现PDF的导出处理.
引入相关的Nugget类后,封装它的辅助类代码如下所示.
/// <summary> /// 基于iText7对PDF的导出处理 /// </summary> public static class TextSharpHelper { /// <summary> /// datatable转PDF方法 /// </summary> /// <param name="title"> 标题内容 </param> /// <param name="data"> dataTable数据 </param> /// <param name="pdfFile"> PDF文件保存的路径 </param> /// <param name="isLandscape"> 是否为横向打印,默认为true </param> /// <param name="includeHeader"> 是否每页包含表头信息 </param> /// <param name="headerAlignment"> 头部的对其方式,默认为居中对其 </param> /// <param name="headerFontSize"> 头部字体大小 </param> /// <param name="rowFontSize"> 行记录字体大小 </param> /// <param name="headerFixHeight"> 头部的固定高度,否则为自适应 </param> /// <returns></returns> public static bool ExportTableToPdf( string title, DataTable data, string pdfFile, bool isLandscape = true , bool includeHeader = true , iText.Layout.Properties.HorizontalAlignment headerAlignment = iText.Layout.Properties.HorizontalAlignment.CENTER, float headerFontSize = 9f, float rowFontSize = 9f, float ? headerFixHeight = null ) { var writer = new PdfWriter(pdfFile); PdfDocument pdf = new PdfDocument(writer); pdf.SetDefaultPageSize(isLandscape ? PageSize.A4.Rotate() : PageSize.A4); // A4横向 var doc = new Document(pdf); // 设置标题 if (! string .IsNullOrEmpty(title)) { var param = new Paragraph(title) .SetFontColor(iText.Kernel.Colors.ColorConstants.BLACK) .SetBold() // 粗体 .SetFontSize(headerFontSize + 5 ) .SetTextAlignment(TextAlignment.CENTER); // 居中 doc.Add(param); } var table = new Table(data.Columns.Count) .SetTextAlignment(TextAlignment.CENTER) .SetVerticalAlignment(VerticalAlignment.MIDDLE) .SetWidth( new UnitValue(UnitValue.PERCENT, 100 )); // 缩放比例 table.UseAllAvailableWidth(); // 添加表头 foreach (DataColumn dc in data.Columns) { var caption = ! string .IsNullOrEmpty(dc.Caption) ? dc.Caption : dc.ColumnName; var cell = new Cell().Add( new Paragraph(caption)) .SetBold() .SetVerticalAlignment(VerticalAlignment.MIDDLE) .SetHorizontalAlignment(headerAlignment) .SetPadding( 1 ) .SetFontSize(headerFontSize); if (headerFixHeight.HasValue) { cell.SetHeight( new UnitValue(UnitValue.POINT, headerFixHeight.Value)); } table.AddHeaderCell(cell); } // 插入数据 var colorWhite = Color.ConvertRgbToCmyk(iText.Kernel.Colors.WebColors.GetRGBColor( " White " )); // System.Drawing.Color.White; var colorEvent = iText.Kernel.Colors.WebColors.GetRGBColor( " LightCyan " ); // System.Drawing.Color.LightCyan; 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 cell = new Cell() .SetBackgroundColor(backgroudColor) .SetFontSize(rowFontSize) .SetVerticalAlignment(VerticalAlignment.MIDDLE) .Add( new Paragraph(text)); table.AddCell(cell); } } doc.Add(table); pdf.Close(); writer.Close(); return true ; } }
导出PDF的文档效果如下所示.
。
Excel数据的导入,可以降低批量处理数据的难度和繁琐的界面一个个录入,这种是一种常见的操作方式,我们主要提供固定的模板给客户下载录入数据,然后提交进行批量的导入即可.
导入的界面处理,我们这里涉及一个通用的导入界面(和WInform端的界面类似),这样我们每个不同的业务导入处理都可以重用,只需要设置一些不同的属性,以及一些事件的处理即可,如下是通用的界面效果.
我们这里主要针对性的介绍它的设计方式,前面我们介绍,在业务界面里面调用它的时候,如下代码所示.
/// <summary> /// 导出内容到Excel /// </summary> [RelayCommand] private void ImportExcel () { var page = App.GetService<ImportExcelData> (); page !. ViewModel.Items ? .Clear(); page !. ViewModel.TemplateFile = $ " 系统用户信息-模板.xls " ; page !. OnDataSave -= ExcelData_OnDataSave; page !.OnDataSave += ExcelData_OnDataSave; // 导航到指定页面 ViewModel.Navigate ( typeof ( ImportExcelData )); }
这个通用的窗体里面的视图模型,定义了一个模板的文件名称,以及一个通用的数据DataTable的集合,以及一个事件用于子类的导入转换的实现,它的视图模型类代码如下所示.
/// <summary> /// 批量导入Excel数据的视图模型基类 /// </summary> public partial class ImportExcelDataViewModel : BaseViewModel { [ ObservableProperty ] private string templateFile; [ ObservableProperty ] private string importFilePath; [ ObservableProperty ] private DataTable items;
我们为了给客户打开模板文件,方便用于录入Excel数据,因此我们在本地打开模板文件即可.
/// <summary> /// 打开模板文件 /// </summary> [RelayCommand] private void OpenFile() { if (! this .TemplateFile.IsNullOrEmpty()) { var realFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, this .TemplateFile); if (File.Exists(realFilePath)) { Process.Start( " explorer.exe " , realFilePath); } else { MessageDxUtil.ShowError($ " 没有找到该模板文件:{realFilePath} " ); } } }
在通用的导入页面的后台代码里面,我们需要实现一些如选择Excel后,显示数据到DataGrid的操作,以及批量保存数据的处理.
/// <summary> /// 选择Excel文件后,显示Excel里面的表格数据 /// </summary> [RelayCommand] private void BrowseExcel() { string file = FileDialogHelper.OpenExcel(); if (! string .IsNullOrEmpty(file)) { this .ViewModel.ImportFilePath = file; ViewData (); } }
/// <summary> /// 查看Excel文件并显示在界面上操作 /// </summary> private void ViewData () { if ( this .txtFilePath.Text == "" ) { MessageDxUtil.ShowTips( " 请选择指定的Excel文件 " ); return ; } try { var myDs = new DataSet(); string error = "" ; AsposeExcelTools . ExcelFileToDataSet ( this .txtFilePath.Text, out myDs, out error); this .ViewModel.Items = myDs.Tables[ 0 ]; } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } }
导入处理的操作代码如下所示.
/// <summary> /// 批量保存数据到数据库 /// </summary> /// <returns></returns> [RelayCommand] private async Task<CommonResult> SaveData() { if (ViewModel.Items == null || ViewModel.Items?.Rows?.Count == 0 ) return new CommonResult( false ); if (MessageDxUtil.ShowYesNoAndWarning( " 该操作将把数据导入到系统数据库中,您确定是否继续? " ) == System.Windows.MessageBoxResult.Yes) { var dt = this .ViewModel.Items; foreach (DataRow dr in dt.Rows) { try { await OnDataSave(dr); } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } return new CommonResult( true , " 操作成功 " ); } return new CommonResult( false ); }
注意,我们这里使用了事件的处理,把数据的转换逻辑留给子类去实现的.
/// <summary> /// 数据保存的事件 /// </summary> public event SaveDataHandler OnDataSave;
这样我们在用户信息的导入页面UserListPage.xaml.cs里面的代码就可以根据实际的情况进行实现事件了.
/// <summary> /// 导出内容到Excel /// </summary> [RelayCommand] private void ImportExcel() { var page = App.GetService<ImportExcelData> (); page !.ViewModel.Items? .Clear(); page !.ViewModel.TemplateFile = $ " 系统用户信息-模板.xls " ; page !.OnDataSave -= ExcelData_OnDataSave ; page !.OnDataSave += ExcelData_OnDataSave ; // 导航到指定页面 ViewModel.Navigate( typeof (ImportExcelData)); }
这个事件的实现,主要就是把个性化的用户信息(用户信息模板里面定义的字段),转换为DataTable的行信息即可,如下代码所示。具体根据模板设计的情况进行修改即可.
具体就不再一一赘述,主要就是基类逻辑和具体实现分离,实现不同的业务功能处理即可.
以上即是我们一个列表通用页面里面,往往需要用到的通用性的导入、导出操作的介绍,希望对读者在开发WPF应用功能上有所启发,有所参考,善莫大焉.
最后此篇关于循序渐进介绍基于CommunityToolkit.Mvvm和HandyControl的WPF应用端开发(4)--实现DataGrid数据的导入和导出操作的文章就讲到这里了,如果你想了解更多关于循序渐进介绍基于CommunityToolkit.Mvvm和HandyControl的WPF应用端开发(4)--实现DataGrid数据的导入和导出操作的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我已经对这个主题进行了一些研究,并且已经在少数应用程序中使用了 MVVM 模式。 我问这个问题是因为有时 MVVM 被称为设计,有时被称为架构模式。 在大多数情况下,MVVM 模式称为设计模式。但是就
我开始使用 MVVM Light 版本 4,但我无法理解: 为什么要使用 DataService 和 IDataService? 我应该为模型中的任何类创建数据服务吗? 最佳答案 首先 - 像往常一样
是否可以采用MVVM在一个平台(如 windows phone)中设计模式并以可移植到其他平台(如 android 和 iOS)的方式实现代码的数据绑定(bind)? 或者我最好问问MVVM设计模式在
使用 avalondock在 MVVM 环境中似乎相当具有挑战性。一旦我从 shellview 中分离 DocumentPane,我就会丢失相应的数据上下文并且我的 View 是空的。重新连接时,它会
我对避免背后代码中的代码的方法很感兴趣。 在我看来,有些情况下代码必须放在代码后面。 例如:我有一个未定义列数的网格。无法绑定(bind)列。所以最简单的方法是在后面的代码中生成列。 对于这种情况,我
我熟悉MVVM。实际上,我在SL4中进行了大部分学习。但是,由于最近的需求,我必须使用SL3。我试图将MVVM Light v3与SL3结合使用并利用命令。问题是在SL3中没有按钮的Command属性
UI逻辑在WindowsRT MVVM应用程序中应该在哪里?将其放到ViewModel上真的很“胖”,我想我们失去了MVVM模式的优势之一-在设计人员和程序员之间分配工作变得非常困难。但是,我创建了一
您好,我有 3 个关于 MVVM 模型的问题。 有没有办法绕过那个多余的PropertyChanged("PropName"); 将 POCO 对象包装到 WPF 的最佳方法是什么 INotifyPr
我正在使用 MVVM 模型做一个 Silverlight,我发现很难通过 MVVM 进行事件处理,尤其是事件处理程序在 View 中进行了大量更改,例如启用和禁用按钮、更新媒体元素功能和位置。我还是
我有一个测试应用程序来测试 windows phone 8.1 上的导航,我可以从主页到第二页进入第二页。 问题是,当我单击后退按钮时,我返回桌面屏幕并且应用程序进入后台,所以我必须按住后退按钮才能返
我正在尝试使用并选择好的MVVM Framework,并且其中有很多,因此选择确实很困难。 我想知道其中的2个-CinchV2(Sacha Barber)和MVVM Light Toolkit(Lau
我完全不熟悉Windows 8开发,现在遇到使用MVVM Light混合触摸和键盘导航的问题。 所以,我有个 View 模型的列表,在网格 View 和只要选择其中的一个,导航到选定的 View 模型
我最近下载了MVVMExtraLite,并且有一个名为 Mediator 的帮助程序。我听说过 Messenger (在MVVM Light中)。有什么区别吗? 最佳答案 他们使用相同的模式,即调解员
我正在尝试学习MVVM,并且在区分模型和 View 模型方面有些挣扎。 如果有人可以回答这两个问题,那么对我来说将大有帮助: 说我有一个Objects类,这是一个包含Object的多个Observab
我已经在网上进行了一些研究,并且得出了一些矛盾的答案。这是我的情况: 我有一个引用ClientViewModel的EditClient View ,还有一个还引用ClientViewModel的Add
我正在使用带有 ModelView-First 方法的 MVVM 模式。到目前为止,这工作正常。 现在我有一个用户控件( View ),它应该根据位于我的 ViewModel 中的属性显示各种内容。
我必须创建一个对话框,其中必须在运行时生成列,之前我使用的是 WPF 数据网格,因此在运行时生成列不是问题。现在我必须使用 View 模型,我需要为要在 View 中显示为列的任何字段具有属性。列数在
所以我目前正在使用 Xamarin.Forms 开发一个应用程序。 Xamarin Forms 使用 MVVM 模式,我觉得使用这种模式有点舒服,但我确实有一些问题。为了简单起见,我将使用一个单页应用
是否有在MVVM应用程序中使用Autofac的示例?我不确定在MVVM环境中如何控制生命周期和对象处置。 我知道我可以创建一个生命周期并从其下解决,但这确实更像是服务定位器模式而不是IoC模式。 最佳
我想我遗漏了一些简单的东西,但我找不到任何例子来说明如何做到这一点......另外,如果我使用的某些术语是错误的,请原谅我。 我只想使用绑定(bind)到 Kendo Observable 对象的 H
我是一名优秀的程序员,十分优秀!