因有一個業務需要在Winform界面中,以類似Excel表格界面中錄入相關的數據(畢竟很多時候,客戶想利用成熟的軟件體驗來輸入他們想要的東西),其中界面需要錄入基礎信息,列表信息,圖片信息等,綜合這些就是例如下面這樣的界面效果。本文主要針對如何利用FarPoint Spread表格控件實現類似Excel界面豐富數據的保存及顯示,以及在使用過程中的一些經驗心得,希望對大家在開發Winform的Excel數據錄入和顯示方面的開發有一定幫助。
從以上的界面分類可以看到,大致可以分為幾個類型的數據,一個是基礎字段數據,一個是有多行的列表數據,一個是圖片數據,還有就是備注信息的顯示錄入了。下面我來分類介紹這些功能的實現。
1、類似Excel的列表總體界面設計
首先,這個列表需要在Winform的界面中進行設計,拖入一個Farpoint控件到Winform界面上,設置好布局等屬性,然后在右鍵菜單上啟動Spread Designer就可以設計相關的Excel樣式表格的內容了。
注意,這里界面一般是在窗體中設計的,當然你的內容可以通過復制粘貼的方式,從Excel文檔拷貝過來,效果看起來一樣的,非常不錯。不過,雖然Farpoint Spread控件提供了一個另存為Xml文件的操作,並且可以通過API,Open一個XML文件,不過Open的XML文件后,好像內容不能進行修改的,而且類型CellType也是Null的,所以如果要在一個窗體上動態加載布局好像做不到,至少我沒有做到。不過對於開發來說,我們在設計時刻,設計好Excel樣式的列表界面,也未嘗不是一件好事。
2、下拉列表的綁定
在Excel列表中,我們很多時候,為了輸入的方便,需要通過下拉列表方式輸入內容,這樣可以提高速度和用戶體驗,但這些內容必須是通過數據庫內容進行綁定的,Farpoint Spread控件是如何做到綁定下拉列表的數據的呢。首先Farpoint Spread控件由很多輸入的內容,其中就包括有ComoBox類型,如下所示。
我們在指定下拉的類型后,Excel列表的顯示方式也跟着變化為下面樣式了。
以上打勾的就是我們下一步需要綁定列表數據的列表了,綁定列表的數據也不麻煩,就是需要明確Cell的序號,綁定給他數據源就可以了,不過說實話,經常要數着Cell的行列號是什么數字,有點不方便。
private void BindDict() { FarPoint.Win.Spread.Cell cell; //品名 cell = this.fpSpread1_Sheet1.Cells[1,9]; FarPoint.Win.Spread.CellType.ComboBoxCellType productType = new FarPoint.Win.Spread.CellType.ComboBoxCellType(); productType.BindDictItems("品名"); cell.CellType = productType; //客戶名 cell = this.fpSpread1_Sheet1.Cells[4, 8]; FarPoint.Win.Spread.CellType.ComboBoxCellType customerType = new FarPoint.Win.Spread.CellType.ComboBoxCellType(); customerType.BindDictItems("客戶名"); cell.CellType = customerType; //款號 cell = this.fpSpread1_Sheet1.Cells[1, 12]; FarPoint.Win.Spread.CellType.ComboBoxCellType styleType = new FarPoint.Win.Spread.CellType.ComboBoxCellType(); styleType.BindDictItems("款號"); cell.CellType = styleType; //面料 cell = this.fpSpread1_Sheet1.Cells[1, 15]; FarPoint.Win.Spread.CellType.ComboBoxCellType materialType = new FarPoint.Win.Spread.CellType.ComboBoxCellType(); materialType.BindDictItems("面料"); cell.CellType = materialType; }
其中代碼的BindDictItems我用了擴展方法,所以能通過對象直接調用,具體的函數代碼如下所示,就是調用字典業務類獲取數據,賦值給Items屬性即可,注意其中的Edittable最好選擇為true,否則它只是顯示里面列表的內容,類似DropdownList那樣。
/// <summary> /// 綁定下拉列表控件為指定的數據字典列表 /// </summary> /// <param name="combo">下拉列表控件</param> /// <param name="dictTypeName">數據字典類型名稱</param> public static void BindDictItems(this FarPoint.Win.Spread.CellType.ComboBoxCellType combo, string dictTypeName) { Dictionary<string, string> dict = BLLFactory<DictData>.Instance.GetDictByDictType(dictTypeName); List<string> listData = new List<string>(); foreach (string key in dict.Keys) { listData.Add(key); } combo.Items = listData.ToArray(); combo.Editable = true; }
3、如何構造界面自定義錄入
為了輸入方便,對於一些例如彈出框選擇內容,圖片編輯,備注內容(很長的時候)的編輯,這些一般來說,我們通過自定義界面來錄入比較好,比較Excel樣式的界面,錄入單元格很小,也有時候實現不了的。所以通過制定控件單元格的單擊事件,用來處理特殊錄入信息的操作。
this.fpSpread1.CellClick += new FarPoint.Win.Spread.CellClickEventHandler(fpSpread1_CellClick);
展開界面部分給大家看看,就是很把內容
void fpSpread1_CellClick(object sender, FarPoint.Win.Spread.CellClickEventArgs e) { FarPoint.Win.Spread.Cell cell = this.fpSpread1_Sheet1.Cells[e.Row, e.Column]; fpSpread1_Sheet1.SetActiveCell(e.Row, e.Column); if(e.Column == 14 && e.Row == 6) { #region 圖片操作 FrmImageEdit dlg = new FrmImageEdit(); if (!string.IsNullOrEmpty(ID)) { dlg.ID = ID; dlg.IsNew = false; } else { dlg.ID = NewID; dlg.IsNew = true; } dlg.OnDataSaved += new EventHandler(dlgPicture_OnDataSaved); dlg.ShowDialog(); #endregion } else if (e.Column == 1 && e.Row == 42) { #region 注意事項 object value = this.fpSpread1_Sheet1.Cells[e.Row, e.Column].Value; if (value != null) { FrmEditNote dlg = new FrmEditNote(); dlg.txtContent.Text = value.ToString(); if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { this.fpSpread1_Sheet1.Cells[e.Row, e.Column].Value = dlg.txtContent.Text; } } #endregion }
例如,對於下拉列表內容,需要進行彈出式選擇內容,如下界面所示。
對於圖片單元格,單擊就可以彈出下面的窗體,方便編輯或者查看。
對於備注內容,我們讓他彈出一個窗體,更好展現和編輯。
4、數據的顯示和保存
對於普通的主表數據字段的顯示很簡單,把內容賦值給對應的單元格Text屬性即可,如下所示。
/// <summary> /// 數據顯示的函數 /// </summary> public void DisplayData() { if (!string.IsNullOrEmpty(ID)) { #region 顯示信息 CraftHeaderInfo info = BLLFactory<CraftHeader>.Instance.FindByID(ID); if (info != null) { this.fpSpread1_Sheet1.Cells[1, 9].Text = info.ProductName; this.fpSpread1_Sheet1.Cells[1, 12].Text = info.StyleNo; this.fpSpread1_Sheet1.Cells[1, 15].Text = info.Material;
保存的時候,把對應的內容保存到實體類進行數據保存操作即可。
/// <summary> /// 編輯或者保存狀態下取值函數 /// </summary> /// <param name="info"></param> private void SetInfo(CraftHeaderInfo info) { info.ProductName = this.fpSpread1_Sheet1.Cells[1, 9].Text;//品名 info.StyleNo = this.fpSpread1_Sheet1.Cells[1, 12].Text;//款號 info.Material = this.fpSpread1_Sheet1.Cells[1, 15].Text;//面料
更多數據的時候,我們把內容保存分開,各個函數負責不同的部分即可,在主表保存后繼續保存其他部分的內容,例如紅色部分就是其他部分的保存操作。
private void btnSave_Click(object sender, EventArgs e) { if (!string.IsNullOrEmpty(ID)) { CraftHeaderInfo info = BLLFactory<CraftHeader>.Instance.FindByID(ID); if (info != null) { SetInfo(info); try { #region 更新數據 bool succeed = BLLFactory<CraftHeader>.Instance.Update(info, info.ID.ToString()); if (succeed) { SaveProcess(info.ID); SaveAccessories(info.ID); SaveIndicateSize(info.ID); SaveColorPair(info.ID); //可添加其他關聯操作 ProcessDataSaved(this.btnSave, new EventArgs()); MessageDxUtil.ShowTips("保存成功"); } #endregion } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } } else {
例如工藝過程是一個列表數據,保存的時候,需要指定行列的屬性進行操作,而且我們添加一個Seq的序列號,用來保存內容的順序,這樣加載的時候,我們就按照這個循序進行加載顯示,否則會出現問題。
private void SaveProcess(string headerId) { string condition = string.Format("Header_ID = '{0}' ", headerId); List<CraftProcessInfo> list = BLLFactory<CraftProcess>.Instance.Find(condition); //(e.Column == 1 && (e.Row >= 6 && e.Row < 30)) int i = 0; for (int row = 6; row < 30; row++) { CraftProcessInfo info = GetProcess(i++, list); info.Header_ID = headerId; int col = 0; info.HandNo = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Process = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Models = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.NeedleWork = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Flower = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.DownLine = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.PinCode = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.KnifeGate = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Note = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item1 = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item2 = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item3 = this.fpSpread1_Sheet1.Cells[row, col++].Text; BLLFactory<CraftProcess>.Instance.InsertUpdate(info, info.ID); } }
其中GetProcess函數,就是一個列表中查找對應順序的內容,如果有,那么我們更新這個對應順序的內容,如果沒有,那么我們認為它是新的數據,這樣就新增到數據庫中,所以最后用了InserUpdate就是這個道理。其中GetProcess函數邏輯代碼如下所示。
private CraftProcessInfo GetProcess(int index, List<CraftProcessInfo> list) { CraftProcessInfo info = new CraftProcessInfo(); if (list.Count > index) { info = list[index]; } info.Seq = index + 1;//重新調整順序號 return info; }
另外注意的時候,有些單元格是合並列的,所以一定要注意算好他的行列號哦。有些地方可能需要跳行。
private void SaveAccessories(string headerId) { string condition = string.Format("Header_ID = '{0}' ", headerId); List<AccessoriesInfo> list = BLLFactory<Accessories>.Instance.Find(condition); //(e.Column == 1 && (e.Row >= 31 && e.Row < 35)) int i = 0; for (int row = 31; row < 35; row++) { AccessoriesInfo info = GetAccessories(i++, list); info.Header_ID = headerId; int col = 1; info.Name = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Consumption = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Position = this.fpSpread1_Sheet1.Cells[row, col++].Text; col++;//空跳一列 info.Item1 = this.fpSpread1_Sheet1.Cells[row, col++].Text; col++;//空跳一列 col++;//空跳一列 info.Item2 = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item3 = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item4 = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item5 = this.fpSpread1_Sheet1.Cells[row, col++].Text; info.Item6 = this.fpSpread1_Sheet1.Cells[row, col++].Text; BLLFactory<Accessories>.Instance.InsertUpdate(info, info.ID); } }
5、Excel表格的數據打印及導出。
使用這個Farpoint Spread的空間,對於里面的內容進行打印或者導出Excel非常方便,代碼也不多,如下所示。
private void btnPrint_Click(object sender, EventArgs e) { PrintInfo pi = new PrintInfo(); pi.Header = "成衣工藝單"; pi.JobName = "成衣工藝單"; pi.Orientation = PrintOrientation.Auto; pi.PageOrder = PrintPageOrder.Auto; pi.ShowPrintDialog = true; pi.PrintNotes = PrintNotes.AtEnd; for (int i = 0; i < this.fpSpread1.Sheets.Count; i++) { pi.ShowPrintDialog = (i == 0); this.fpSpread1.Sheets[i].PrintInfo = pi; fpSpread1.PrintSheet(i); } } private void btnExport_Click(object sender, EventArgs e) { string file = FileDialogHelper.SaveExcel("成衣工藝單.xls"); if (!string.IsNullOrEmpty(file)) { try { bool success = this.fpSpread1.SaveExcel(file); if (success) { if (MessageDxUtil.ShowYesNoAndTips("導出成功,是否打開文件?") == System.Windows.Forms.DialogResult.Yes) { System.Diagnostics.Process.Start(file); } } } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } }
導出的效果和界面顯示的效果基本上一致的,而且圖片等特殊的格式,也是正常保留在Excel里面,總體感覺不錯。
最后附上這個系統的一些截圖作為補充了解。