最近一直在做WINFORM項目,所以經常有些新的想法或嘗試與大家分享,之前與大家分享了通用窗體遮罩層、通用可附加數據綁定的DataGridView、窗體漸顯,今天來分享一個大家在其它軟件中常見的功能:數據過濾查詢。
先看一下我實現的的整體效果:
過濾之后:
說一下實現上述功能的思路:
首先說一下界面的設計》
1.創建一個窗體(在此稱作:過濾窗體FrmFilter),然后在窗體上部放一個DataGridView控件、下面放一個Panel,然后Panel中放兩個按鈕,至於如何更好的布局或是否需要適應窗體變化,這些都比較簡單,在此就不介紹了;
2.設計DataGridView控件,分別加入4列(字段名、運算符、值、值2),其中字段名、運算符列需支持下拉,值、值2列需支持輸入
界面設計很簡單,現在說一下代碼的實現,完整代如下:
using System; using System.Data; using System.Windows.Forms; using TEMS.Service; namespace TEMS.Forms { public partial class FrmFilter : FormBase { private DataTable filterTable = null; /// <summary> /// 獲取過濾條件的表格(zuowenjun.cn) /// </summary> public DataTable FilterTable { get { return filterTable; } } public FrmFilter(object source, string text, string value) { InitializeComponent(); dataGridFilter.AutoGenerateColumns = false; var col0 = dataGridFilter.Columns[0] as DataGridViewComboBoxColumn; col0.DataSource = source; col0.DisplayMember = text; col0.ValueMember = value; var col1 = dataGridFilter.Columns[1] as DataGridViewComboBoxColumn; col1.DataSource = FilterOperators.Operators; col1.DisplayMember = "Value"; col1.ValueMember = "Key"; InitFilterDataTable(); } private void InitFilterDataTable() { filterTable = new DataTable(); foreach (DataGridViewColumn col in dataGridFilter.Columns) { filterTable.Columns.Add(col.DataPropertyName,typeof(string)); } dataGridFilter.DataSource = filterTable; } private void btnOk_Click(object sender, EventArgs e) { this.Close(); } private void btnReset_Click(object sender, EventArgs e) { InitFilterDataTable(); this.Close(); } } }
以下是系統自動生成的窗體設計代碼:
namespace TEMS.Forms { partial class FrmFilter { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// Clean up any resources being used. /// </summary> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.panel1 = new System.Windows.Forms.Panel(); this.btnReset = new System.Windows.Forms.Button(); this.btnOk = new System.Windows.Forms.Button(); this.dataGridFilter = new System.Windows.Forms.DataGridView(); this.name = new System.Windows.Forms.DataGridViewComboBoxColumn(); this.operators = new System.Windows.Forms.DataGridViewComboBoxColumn(); this.value = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.value2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.tableLayoutPanel1.SuspendLayout(); this.panel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.dataGridFilter)).BeginInit(); this.SuspendLayout(); // // tableLayoutPanel1 // this.tableLayoutPanel1.ColumnCount = 1; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel1.Controls.Add(this.panel1, 0, 1); this.tableLayoutPanel1.Controls.Add(this.dataGridFilter, 0, 0); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 2; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 90F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); this.tableLayoutPanel1.Size = new System.Drawing.Size(598, 436); this.tableLayoutPanel1.TabIndex = 0; // // panel1 // this.panel1.Controls.Add(this.btnReset); this.panel1.Controls.Add(this.btnOk); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; this.panel1.Location = new System.Drawing.Point(3, 395); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(592, 38); this.panel1.TabIndex = 0; // // btnReset // this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.btnReset.Location = new System.Drawing.Point(508, 6); this.btnReset.Name = "btnReset"; this.btnReset.Size = new System.Drawing.Size(75, 23); this.btnReset.TabIndex = 0; this.btnReset.Text = "重 置"; this.btnReset.UseVisualStyleBackColor = true; this.btnReset.Click += new System.EventHandler(this.btnReset_Click); // // btnOk // this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.btnOk.Location = new System.Drawing.Point(427, 6); this.btnOk.Name = "btnOk"; this.btnOk.Size = new System.Drawing.Size(75, 23); this.btnOk.TabIndex = 0; this.btnOk.Text = "確 定"; this.btnOk.UseVisualStyleBackColor = true; this.btnOk.Click += new System.EventHandler(this.btnOk_Click); // // dataGridFilter // this.dataGridFilter.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dataGridFilter.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.name, this.operators, this.value, this.value2}); this.dataGridFilter.Dock = System.Windows.Forms.DockStyle.Fill; this.dataGridFilter.Location = new System.Drawing.Point(3, 3); this.dataGridFilter.Name = "dataGridFilter"; this.dataGridFilter.RowTemplate.Height = 23; this.dataGridFilter.Size = new System.Drawing.Size(592, 386); this.dataGridFilter.TabIndex = 1; // // name // this.name.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.name.DataPropertyName = "name"; this.name.FillWeight = 80F; this.name.HeaderText = "字段名"; this.name.Name = "name"; // // operators // this.operators.DataPropertyName = "operators"; this.operators.HeaderText = "運算符"; this.operators.Name = "operators"; // // value // this.value.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.value.DataPropertyName = "value"; this.value.HeaderText = "值"; this.value.Name = "value"; // // value2 // this.value2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.value2.DataPropertyName = "value2"; this.value2.HeaderText = "值2"; this.value2.Name = "value2"; // // FrmFilter // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(598, 436); this.Controls.Add(this.tableLayoutPanel1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.Name = "FrmFilter"; this.Opacity = 1D; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "篩選查詢"; this.tableLayoutPanel1.ResumeLayout(false); this.panel1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.dataGridFilter)).EndInit(); this.ResumeLayout(false); } #endregion private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button btnReset; private System.Windows.Forms.Button btnOk; private System.Windows.Forms.DataGridView dataGridFilter; private System.Windows.Forms.DataGridViewComboBoxColumn name; private System.Windows.Forms.DataGridViewComboBoxColumn operators; private System.Windows.Forms.DataGridViewTextBoxColumn value; private System.Windows.Forms.DataGridViewTextBoxColumn value2; } }
構造函數中的的 FilterOperators.Operators表示的是運算符的數據源,我是定義的一個struct,如下:
public struct FilterOperators { public const string Equal = "Equal"; public const string NotEqual = "NotEqual"; public const string LessThan = "LessThan"; public const string GreaterThan = "GreaterThan"; public const string LessThanOrEqual = "LessThanOrEqual"; public const string GreaterThanOrEqual = "GreaterThanOrEqual"; public const string Contains = "Contains"; public const string StartsWith = "StartsWith"; public const string EndsWith = "EndsWith"; public const string Between = "Between"; public static KeyValueList<string, string> Operators = new KeyValueList<string, string>() { {Equal,"等於"},{NotEqual,"不等於"}, {LessThan,"小於"},{GreaterThan,"大於"}, {LessThanOrEqual,"小於或等於"},{GreaterThanOrEqual,"大於或等於"}, {Contains,"包含"},{StartsWith,"開頭包含"},{EndsWith,"結尾包含"}, {Between,"區間"} }; }
FilterTable屬性是用來獲取過濾條件的表格,表格的數據是通過綁定DataGridView控件來獲得的,如果不知道為什么通過數據源綁定到一個空表格就能獲取數據,請查找相關資料,這里不作說明,當然也可以通過評論與我交流。
以上都是關於過濾窗體的實現,下面我要講解最關鍵字部份,也是最重要的部份,就是:如何將獲得的過濾條件DataTable轉換成查詢語句,這里的查詢語句包括SQL或表達式樹,由於是通過的過濾窗體,所以關於生成查詢條件語句我放在了調用完該窗體后來實現,當然如果大家只用一種方式(實體類或表),可以直接集成在窗體類中,直接返回生成的查詢語句即可。
因為我項目中用的是實體類來查詢,所以我采用動態生成Lambda表達式樹,然后借助PredicateBuilder類(這是別人開發的類,詳見我之前的博文)進行拼接,最后生成用於查詢的表達式樹,實現代碼如下:
/// <summary> /// 獲取查詢表達式樹 (zuowenjun.cn) /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="fieldName"></param> /// <param name="operatorName"></param> /// <param name="value"></param> /// <param name="value2"></param> /// <returns></returns> public static Expression<Func<TEntity, bool>> GetQueryExpression<TEntity>(string fieldName, string operatorName, string value, string value2) where TEntity : class { PropertyInfo fieldInfo = typeof(TEntity).GetProperty(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); Type pType = fieldInfo.PropertyType; if (string.IsNullOrEmpty(operatorName)) { throw new ArgumentException("運算符不能為空!", "operatorName"); } dynamic convertedValue; if (!value.TryChangeType(pType, out convertedValue)) { throw new ArgumentException(string.Format("【{0}】的查詢值類型不正確,必須為{1}類型!", General.GetDisplayName(fieldInfo), pType.FullName), "value"); } ParameterExpression expParameter = Expression.Parameter(typeof(TEntity), "f"); MemberExpression expl = Expression.Property(expParameter, fieldInfo); ConstantExpression expr = Expression.Constant(convertedValue, pType); Expression expBody = null; Type expType = typeof(Expression); var expMethod = expType.GetMethod(operatorName, new[] { expType, expType }); if (expMethod != null) { expBody = (Expression)expMethod.Invoke(null, new object[] { expl, expr }); } else if (FilterOperators.Between == operatorName) { dynamic convertedValue2; if (!value2.TryChangeType(pType, out convertedValue2)) { throw new ArgumentException(string.Format("【{0}】的查詢值2類型不正確,必須為{1}類型!", General.GetDisplayName(fieldInfo), pType.FullName), "value"); } ConstantExpression expr2 = Expression.Constant(convertedValue2, pType); expBody = Expression.GreaterThanOrEqual(expl, expr); expBody = Expression.AndAlso(expBody, Expression.LessThanOrEqual(expl, expr2)); } else if (new[] { FilterOperators.Contains, FilterOperators.StartsWith, FilterOperators.EndsWith }.Contains(operatorName)) { expBody = Expression.Call(expl, typeof(string).GetMethod(operatorName, new Type[] { typeof(string) }), expr); } else { throw new ArgumentException("無效的運算符!", "operatorName"); } Expression<Func<TEntity, bool>> lamExp = Expression.Lambda<Func<TEntity, bool>>(expBody, expParameter); return lamExp; }
其中TryChangeType是我擴展的支持任意類型的轉換,該擴展方法又引用了其它的自定義擴展方法,具體方法定義如下:
/// <summary> /// 判斷是否可為轉換為指定的類型 /// </summary> /// <param name="str"></param> /// <param name="type"></param> /// <returns></returns> public static bool TryChangeType(this object str, Type type,out dynamic returnValue) { try { if (type.IsNullableType()) { if (str == null || str.ToString().Length == 0) { returnValue = null; } else { type = type.GetGenericArguments()[0]; returnValue = Convert.ChangeType(str, type); } } else { returnValue = Convert.ChangeType(str, type); } return true; } catch { returnValue = type.DefaultValue(); return false; } } /// <summary> /// 判斷是否為可空類型 /// </summary> /// <param name="type"></param> /// <returns></returns> public static bool IsNullableType(this Type type) { return (type.IsGenericType && type.GetGenericTypeDefinition().Equals (typeof(Nullable<>))); } /// <summary> /// 默認值 /// </summary> /// <param name="targetType"></param> /// <returns></returns> public static dynamic DefaultValue(this Type targetType) { return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; }
以下是具體的調用:
//獲取用於過濾的字段(這里直接采用數據列表的相應列,大家可以生成相應的List數據源) (zuowenjun.cn) private List<DataGridViewColumn> GetQueryGridColumnInfos() { List<DataGridViewColumn> cols = new List<DataGridViewColumn>(); for (int i = 0; i < dataGridBase.Columns.Count - 3; i++) { cols.Add(dataGridBase.Columns[i]); } return cols; } //工具欄點擊過濾查詢按鈕事件 (zuowenjun.cn) private void ToolStrip_OnFilter(object sender, EventArgs e) { if (filterForm == null) { filterForm = new FrmFilter(GetQueryGridColumnInfos(), "HeaderText", "DataPropertyName"); } filterForm.ShowDialog(Common.MainForm); whereExpr = PredicateBuilder.True<Category>(); var p = whereExpr.Parameters[0]; foreach (DataRow row in filterForm.FilterTable.Rows) { string fieldName = row[0].ToString(); string opt = row[1].ToString(); string value = row[2].ToString(); string value2 = row[3].ToString(); var fieldExpr = Common.GetQueryExpression<Category>(fieldName, opt, value, value2);//動態生成查詢表達式樹 whereExpr = whereExpr.And(fieldExpr);//連接表達式樹 } FirstLoadList();//加載數據並顯示 } ///加載數據 (zuowenjun.cn) private void FirstLoadList() { int recordCount = 0; base.PageInfo = new PageInfo(); base.PageInfo.PageSize = 10; base.PageInfo.CurrentPageNo = 1; var resultList = QueryBusiness<Category>.GetListByPage(whereExpr, t => t, t => t.ID, 1, base.PageInfo.PageSize, base.PageInfo.CurrentPageNo, out recordCount); base.PageInfo.RecordCount = recordCount; base.AppendDataToGrid(resultList,false); }
由於代碼較多,且使用了一些其它的編寫好的類,若有不明白的地方,可以在此文下評論,我會幫忙解答,希望能幫到大家,不足之處也歡迎大家指證,謝謝!