做了很多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了,以上一家之言,歡迎拍磚或者共同探討。