做了很多Winform的项目,对于数据导入,一直也有自己的理解,由于一般的业务系统,经常性的数据导入时很正常的业务需求,因为毕竟使用Excel来操作数据也很方便,或者由于系统之间的数据交换需要,我们需要提供一个入口给客户导入所需要的数据。但是导入数据的时候,不同的业务数据对应不同的Excel文件,很难做到统一,但如果是每个业务模型,都创建一个不同的导入界面来操作Excel数据,又会觉得可能某种程度上重复劳动,增加开发及维护成本。
那么有无一种介于两者之间的方法,来实现效率的最优化,并且能够统一利用好一个导入的界面呢,在开发领域,只要能想到的,一般也能做到,由于工作的需要,在我的Winform开发框架中引入了一个通用的数据导入模块,来实现这个既是统一,又是变化的业务需求,首先我们来看看能大致的模块功能介绍图,如下所示。
然后我们再来看看实际的导入模块操作界面,如下图所示
在最底的状态栏里面,但我们保存数据的时候,会调用后台线程进行数据保存,并显示数据导入的进度状态,由于是采用后台线程处理,不会阻塞当前的界面,在多文档的Winform开发框架界面中,可以切换到其他业务界面进行其他处理,不影响整体界面操作。
既然是导入界面统一,它肯定封装了一些常规操作,同时提供一些属性或者接口给外部调用对象进行操作,这样才能实现有机的统一,我们来看看具体的实现代码是如何的。
1)定义事件处理
public delegate bool SaveDataHandler(DataRow dr); public event SaveDataHandler OnDataSave; public event EventHandler OnRefreshData;
首先我们定义一个数据保存(单行)的处理事件,然后也定义一个数据保存后,刷新主体列表的数据刷新事件,这两个都是提供给调用者实现的逻辑。
我们在这个通用的导入数据窗体,需要的就是利用后台线程调用整个逻辑进行处理数据的导入及后续的刷新操作,如下所示。
private BackgroundWorker worker = null; public delegate bool SaveDataHandler(DataRow dr); public event SaveDataHandler OnDataSave; public event EventHandler OnRefreshData; public FrmImportExcelData() { InitializeComponent(); this.gridView1.OptionsBehavior.AutoPopulateColumns = true; worker = new BackgroundWorker(); worker.WorkerReportsProgress = true; worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); } void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; } void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.progressBar1.Visible = false; this.progressBar1.Value = 0; if (OnRefreshData != null) { OnRefreshData(null, null); } string tips = e.Result as string; if (!string.IsNullOrEmpty(tips)) { MessageDxUtil.ShowTips(tips); if (tips == "操作成功") { this.gridControl1.DataSource = null; } } }
2)设置显示不同的模板文件
由于导入数据操作要应用于不同的业务数据,那么他们的模板肯定也不同,因此需要提供一个接口给外部,实现模板文件的修改及打开操作。
/// <summary> /// 设置导入模板标题,及文件路径 /// </summary> /// <param name="title"></param> /// <param name="filePath"></param> public void SetTemplate(string title, string filePath) { this.lnkExcel.Text = title; this.lnkExcel.Tag = filePath; } private void lnkExcel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { try { string templateFile = this.lnkExcel.Tag.ToString(); Process.Start(templateFile); } catch (Exception) { MessageDxUtil.ShowWarning("文件打开失败"); } }
3)显示Excel数据
我们在数据导入的时候,最好提供一个数据的显示界面给客户,方便对导入数据的核对,这样可以提高体验效果以及对数据的核对操作,减少出错的几率。具体的实现代码如下所示。数据显示的操作,可以通过操作Excel数据库的方式进行读取,然后显示数据。(其中有些接口API来自我的共用类库,需要可以到我的随笔中了解相关的类库使用。
private void btnBrowse_Click(object sender, EventArgs e) { string file = FileDialogHelper.OpenExcel(); if (!string.IsNullOrEmpty(file)) { this.txtFilePath.Text = file; ViewData(); } } private void ViewData() { if (this.txtFilePath.Text == "") { MessageDxUtil.ShowTips("请选择指定的Excel文件"); return; } try { string connectString = string.Format(connectionStringFormat, this.txtFilePath.Text); string firstSheet = ExcelHelper.GetExcelFirstTableName(connectString); myDs.Tables.Clear(); myDs.Clear(); this.gridControl1.DataSource = null; OleDbConnection cnnxls = new OleDbConnection(connectString); OleDbDataAdapter myDa = new OleDbDataAdapter(string.Format("select * from [{0}]", firstSheet), cnnxls); myDa.Fill(myDs, "【导入表】"); this.gridControl1.DataSource = myDs.Tables[0]; this.gridView1.PopulateColumns(); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
4)调用者给出保存数据的逻辑
由于是通用的数据导入操作,因此公用的导入界面,只能抛出相应的事件给外部进行数据保存的逻辑处理,数据导入页面只需要负责总体逻辑,具体的保存逻辑交给调用者实现,这样各司其职,共同把事情做好。下面是调用者(药品信息显示窗体中),对数据导入的操作逻辑实现。我们可以看到,它需要指定模板文件、数据刷新操作、数据保存操作,其他的交给通用数据导入界面进行处理即可。
private string moduleName = "药品目录"; private void btnImport_Click(object sender, EventArgs e) { string templateFile = string.Format("{0}-模板.xls", moduleName); FrmImportExcelData dlg = new FrmImportExcelData(); dlg.SetTemplate(templateFile, System.IO.Path.Combine(Application.StartupPath, templateFile)); dlg.OnDataSave += new FrmImportExcelData.SaveDataHandler(ExcelData_OnDataSave); dlg.OnRefreshData += new EventHandler(ExcelData_OnRefreshData); dlg.ShowDialog(); } void ExcelData_OnRefreshData(object sender, EventArgs e) { BindData(); } bool ExcelData_OnDataSave(DataRow dr) { bool success = false; DrugDetailInfo info = new DrugDetailInfo(); info.DrugNo = dr["药品编码"].ToString(); info.DrugName = dr["药品名称"].ToString(); info.Manufacture = dr["制造商"].ToString(); info.Formulations = dr["剂型"].ToString(); info.Specification = dr["规格"].ToString(); info.Unit = dr["药品单位"].ToString(); info.Note = dr["备注信息"].ToString(); info.StockQuantity = ConvertHelper.ToInt32(dr["库存量"].ToString(), 0); info.EditTime = DateTime.Now; info.Editor = Portal.gc.LoginInfo.Name; info.Dept_ID = Portal.gc.LoginInfo.Dept_ID; success = BLLFactory<DrugDetail>.Instance.Insert(info); return success; }
到这里,通用数据导入的操作基本上就结束了,我的处理方式是否和你的想法吻合呢,或者有更好的实现方式?
不过大家的总体思想,肯定是殊途同归,抽象封装统一的部分,并提供个性化的逻辑给外部进行处理,这样就可以实现综合的统一,提高整体的使用效率,较少今后维护的成本。
在这里顺便说一下,数据导出的操作,因为既然有导入,应该也有导出,所以我们也需要实现。它的操作代码不是很复杂,只需要把数据按照导入模板约定的字段名称导出即可,记得要和导入模板一致。
private void btnExport_Click(object sender, EventArgs e) { string file = FileDialogHelper.SaveExcel(string.Format("{0}.xls", moduleName)); if (!string.IsNullOrEmpty(file)) { List<DrugDetailInfo> list = BLLFactory<DrugDetail>.Instance.GetAll(); DataTable dtNew = DataTableHelper.CreateTable("序号|int,药品编码,药品名称,制造商,剂型,规格,药品单位,备注信息,库存量"); DataRow dr; for (int i = 0; i < list.Count; i++) { dr = dtNew.NewRow(); dr["序号"] = i + 1; dr["药品编码"] = list[i].DrugNo; dr["药品名称"] = list[i].DrugName; dr["制造商"] = list[i].Manufacture; dr["剂型"] = list[i].Formulations; dr["规格"] = list[i].Specification; dr["药品单位"] = list[i].Unit; dr["备注信息"] = list[i].Note; dr["库存量"] = list[i].StockQuantity; dtNew.Rows.Add(dr); } try { string error = ""; AsposeExcelTools.DataTableToExcel2(dtNew, file, out error); if (!string.IsNullOrEmpty(error)) { MessageDxUtil.ShowError(string.Format("导出Excel出现错误:{0}", error)); } else { if (MessageDxUtil.ShowYesNoAndTips("导出成功,是否打开文件?") == System.Windows.Forms.DialogResult.Yes) { System.Diagnostics.Process.Start(file); } } } catch (Exception ex) { LogHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } }
以上就是我的通用数据导入导出操作,其实利用代码生成工具Database2Sharp,选定表后,自动生成的WInform界面中,就已经自动生成以上导入、导出Excel的功能代码了,已经极大简化了重复输入代码的可能性了,只需要把界面调整一下就基本上OK了,以上一家之言,欢迎拍砖或者共同探讨。