C#實現通用數據過濾窗體


最近一直在做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);
        }

由於代碼較多,且使用了一些其它的編寫好的類,若有不明白的地方,可以在此文下評論,我會幫忙解答,希望能幫到大家,不足之處也歡迎大家指證,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM