最近一直忙於公司的事情,雖然一直在做一些相關的技術研究,但是很久沒能靜下心來好好寫寫博客文章了,想想也有半個月之多了,這半個月來,也一直致力於改善我的WInform開發框架,使得自己及客戶使用起來更加方便,更加友好,更加高效。本篇文章就是介紹最近框架改善的其中一個閃光點"通用高級查詢模塊",高級查詢模塊,在很多程序模塊中都很常見,也是給客戶擴展查詢的一個很好的補充,由於我一直希望我的Winform開發框架能夠精益求精,所以做了這個模塊,希望對今后我自己所有的項目以及框架本身,都能高效的使用。
1、通用高級查詢模塊的用途及介紹
既然稱之為通用查詢模塊,那么他就不能與具體的表字段有耦合關系,但是要實現具體的查詢,必須通過某種方式進行屬性傳遞,實現更直觀友好的字段查詢功能。高級查詢模塊,在很多完善的程序上都會提供,用於滿足用戶對特定的字段,添加特定的條件進行,因為一般情況下,由於版面的限制界面上查詢的內容比較有限,只是把一些很常見、重要的字段作為查詢輸入,如果表字段比較多,那么對有些特殊的字段就無所適從。
例如,我一個病人資料管理系統的界面如下所示
這個界面已經放置的比較多輸入控件進行查詢了,如果放置更多,超過3行就會導致感覺比較臃腫了。所以合理的應該是把不常用的放到高級查詢里面,這樣用戶需要更多條件的定制,可以在高級查詢中對每個字段都能進行搜索,常規的在主界面即可查詢。
我的高級查詢查詢模塊也是基於這個道理,因此,在主界面增加一個高級查詢按鈕入口,如上圖所示,單擊后,顯示一個所有字段的列表,如下界面。
這樣所有表的字段均可進行查詢了,但是我們注意到,這個界面並沒有輸入條件的地方,沒錯,這個只是顯示條件內容,浮在主界面的查詢列表上面,如果設置條件后,主界面的列表會根據高級查詢條件進行查詢,這樣也是符合客戶實際的期望的。
在介紹輸入條件的時候,我們注意到,查詢輸入,基本上可以分為幾類:其一是常規的文本類型,使用文本框替代即可;其二是下拉列表類型,用戶從列表下面選擇內容;其三是日期類型,需要用戶指定開始日期和結束日期;其四是數字類型,需要用戶指定起始和結束的數值。
剛才說到,上面的高級查詢界面,只是顯示,但用戶雙擊列表或者按下回車鍵,那么根據字段類型,彈出對應上面說到的四種輸入框,用於接收用戶的輸入。
1)常規的文本類型條件輸入界面:
下拉列表類型條件輸入界面:
日期類型條件輸入界面:
數字類型條件輸入界面:
輸入以上幾種條件后,高級查詢界面里面會顯示友好的條件內容,確保用戶能夠看懂輸入的條件,如下所示是輸入幾個不同類型的條件的顯示內容。
日期類型和數字類型,為了方便,都可以單獨輸入其中的一部分作為條件進行高級查詢。
為了方便,高級查詢模塊的內容,可以進行查詢后在主界面列表中顯示,也可以使用傳統的查詢按鈕進行查詢,使用傳統查詢按鈕后,高級查詢條件將失效,但是保留用戶的輸入,第二次打開界面后,會加載之前的輸入條件,這樣比較人性化一些,用戶可以隨時自由切換兩種查詢方式。
對於一些字段多的歷史資料表,這種高級查詢模式更加能夠發揮其作用,用戶可以在幾十個甚至上百個字段中選擇合適的條件,然后輸入進行查詢,這樣能夠使得客戶更加有認同感,例如下面是一個病人歷史檔案資料表的高級查詢界面,里面很多字段,使用這個擴展查詢就是最合適不過了。
2、通用高級查詢模塊的實現思路
1)獲取數據庫字段名稱和類型列表
首先前面說到,我是根據字段類型進行不同的控件顯示並輸入的,因此需要一個通用的函數獲取字段的名稱和類型,作為參考,由於這個功能比較通用,因此把它放在我的Winform開發框架的數據庫基類的抽象類里面,所有的數據庫訪問類均繼承不用重復寫代碼。
/// <summary> /// 獲取表的字段名稱和數據類型列表。 /// </summary> /// <returns></returns> public DataTable GetFieldTypeList() { DataTable dt = DataTableHelper.CreateTable("ColumnName,DataType"); DataTable schemaTable = GetReaderSchema(tableName); if (schemaTable != null) { foreach (DataRow dr in schemaTable.Rows) { string columnName = dr["ColumnName"].ToString().ToUpper(); string netType = dr["DataType"].ToString().ToLower(); DataRow row = dt.NewRow(); row["ColumnName"] = columnName; row["DataType"] = netType; dt.Rows.Add(row); } } return dt; } /// <summary> /// 獲取指定表的元數據,包括字段名稱、類型等等 /// </summary> /// <param name="tableName">數據庫表名</param> /// <returns></returns> private DataTable GetReaderSchema(string tableName) { DataTable schemaTable = null; string sql = string.Format("Select * FROM {0}", tableName); Database db = CreateDatabase(); DbCommand command = db.GetSqlStringCommand(sql); try { using (IDataReader reader = db.ExecuteReader(command)) { schemaTable = reader.GetSchemaTable(); } } catch (Exception ex) { LogTextHelper.Error(ex); } return schemaTable; }
2)設置高級查詢的字段顯示(有時候可能需要過濾部分字段)
3)指定下拉列表的數據內容
剛才說到,有一些下拉列表字段的輸入,那么下拉列表也就需要指定里面的列表內容了,高級查詢模塊,指定下拉列表的代碼如下所示。
dlg.AddColumnListItem("MidVideo", GetFieldListItem("MidVideo")); dlg.AddColumnListItem("InDiagnosis", GetFieldListItem("InDiagnosis")); dlg.AddColumnListItem("DirectorSurgeon", GetFieldListItem("DirectorSurgeon")); dlg.AddColumnListItem("TumorPart", GetFieldListItem("TumorPart")); dlg.AddColumnListItem("LeaveDiagnosis", GetFieldListItem("LeaveDiagnosis")); dlg.AddColumnListItem("IsFirstTime", GetFieldListItem("IsFirstTime")); dlg.AddColumnListItem("Professor", GetFieldListItem("Professor"));
對於一些固定的列表項目,我們也可以通過下面代碼進行綁定。
dlg.AddColumnListItem("Sex", new List<CListItem>() { new CListItem("男"), new CListItem("女") }); dlg.AddColumnListItem("HasReferral", new List<CListItem>() { new CListItem("是"), new CListItem("否") });
4)和普通查詢功能並存
為了使得傳統查詢按鈕,和高級查詢能夠並存,我們需要存儲一個高級查詢的查詢對象,但傳統查詢的時候,我們把高級查詢對象設置為空即可屏蔽高級查詢的條件了。
/// <summary> /// 高級查詢條件語句對象 /// </summary> private SearchCondition advanceCondition; /// <summary> /// 根據查詢條件構造查詢語句 /// </summary> private string GetConditionSql() { //如果存在高級查詢對象信息,則使用高級查詢條件,否則使用主表條件查詢 SearchCondition condition = advanceCondition; if (condition == null) { condition = new SearchCondition(); condition.AddCondition("BedNo", this.txtBedNo.Text.Trim(), SqlOperator.Like); condition.AddCondition("TumorPart", this.txtTumorPart.Text.Trim(), SqlOperator.Like); condition.AddCondition("MidVideo", this.txtMidVideo.Text.Trim(), SqlOperator.Like); condition.AddCondition("Name", this.txtName.Text.Trim(), SqlOperator.Like); condition.AddCondition("HospitalNo", this.txtHospitalNo.Text.Trim(), SqlOperator.Like); condition.AddCondition("IDNumber", this.txtIDNumber.Text.Trim(), SqlOperator.Like); condition.AddCondition("InDiagnosis", this.txtInDiagnosis.Text.Trim(), SqlOperator.Like); condition.AddCondition("LeaveDiagnosis", this.txtLeaveDiagnosis.Text.Trim(), SqlOperator.Like); condition.AddCondition("LeaveSpecimens", this.txtLeaveSpecimens.Text.Trim(), SqlOperator.Like); condition.AddCondition("Professor", this.txtProfessor.Text.Trim(), SqlOperator.Like); condition.AddCondition("Note", this.txtNote.Text.Trim(), SqlOperator.Like); condition.AddCondition("Pathology", this.txtPathology.Text.Trim(), SqlOperator.Like); condition.AddCondition("IsFirstTime", this.txtIsFirstTime.Text.Trim(), SqlOperator.Like); .................. } string where = condition.BuildConditionSql().Replace("Where", ""); return where; }
/// <summary> /// 查詢數據操作 /// </summary> private void btnSearch_Click(object sender, EventArgs e) { advanceCondition = null;//必須重置查詢條件,否則可能會使用高級查詢條件了 BindData(); }
5)根據類型轉換不同的窗體
為了輸入方便,我定義了幾個不同輸入內容的窗體,然后根據不同的字段類型,構造不同的窗體並進行顯示,這樣比較合理顯示並接收客戶的條件輸入。
private void gridControl1_MouseDoubleClick(object sender, MouseEventArgs e) { int[] rowSelected = this.gridView1.GetSelectedRows(); if (rowSelected.Length == 0) return; string fieldName = this.gridView1.GetFocusedRowCellValue("字段").ToString(); string fieldDisplay = this.gridView1.GetFocusedRowCellValue("字段名稱").ToString(); FieldType fieldType = (FieldType)this.gridView1.GetFocusedRowCellValue("字段類型"); string fieldValue = this.gridView1.GetFocusedRowCellValue("查詢條件值").ToString(); #region 根據類型轉換不同的窗體 FrmQueryBase dlg = null; if (fieldType == FieldType.Text) { dlg = new FrmQueryTextEdit(); } else if (fieldType == FieldType.Numeric) { dlg = new FrmQueryNumericEdit(); } else if (fieldType == FieldType.DateTime) { dlg = new FrmQueryDateEdit(); } else if (fieldType == FieldType.DropdownList) { dlg = new FrmQueryDropdown(); } #endregion
6)主窗體的事件響應
由於彈出的高級查詢窗體,里面的查詢以及清除操作,都是通過主窗體的事件進行處理,因此,彈出的高級查詢窗體,只需要調用相應的事件即可。
dlg.DataClear += new FrmQueryBase.DataClearEventHandler(dlg_DataClear); if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { //更新查詢界面顯示 UpdateFieldValue(fieldName, dlg.ReturnValue, dlg.ReturnDisplay); //更新父窗體的數據顯示 ProcessDataSearch(null, null); }
這樣我們就能把傳統查詢功能和高級查詢功能合並一起,發揮最大的作用,是我們的程序能夠盡量滿足客戶的需求,獲得更加好的反饋和支持了。