C#实现通用数据过滤窗体

最近一直在做WINFORM项目,所以经常有些新的想法或尝试与大家分享,之前与大家分享了通用窗体遮罩层、通用可附加数据绑定的DataGridView、窗体渐显,今天来分享一个大家在其它软件中常见的功能:数据过滤查询。

先看一下我实现的的整体效果:

 

C#实现通用数据过滤窗体

过滤之后:

C#实现通用数据过滤窗体

说一下实现上述功能的思路:

首先说一下界面的设计》

1.创建一个窗体(在此称作:过滤窗体FrmFilter),然后在窗体上部放一个DataGridView控件、下面放一个Panel,然后Panel中放两个按钮,至于如何更好的布局或是否需要适应窗体变化,这些都比较简单,在此就不介绍了;

2.设计DataGridView控件,分别加入4列(字段名、运算符、值、值2),其中字段名、运算符列需支持下拉,值、值2列需支持输入

界面设计很简单,现在说一下代码的实现,完整代如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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();
        }
    }
}

以下是系统自动生成的窗体设计代码:

构造函数中的的 FilterOperators.Operators表示的是运算符的数据源,我是定义的一个struct,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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<stringstring> Operators = new KeyValueList<stringstring>()
    {
        {Equal,"等于"},{NotEqual,"不等于"},
        {LessThan,"小于"},{GreaterThan,"大于"},
        {LessThanOrEqual,"小于或等于"},{GreaterThanOrEqual,"大于或等于"},
        {Contains,"包含"},{StartsWith,"开头包含"},{EndsWith,"结尾包含"},
        {Between,"区间"}
    };
}

FilterTable属性是用来获取过滤条件的表格,表格的数据是通过绑定DataGridView控件来获得的,如果不知道为什么通过数据源绑定到一个空表格就能获取数据,请查找相关资料,这里不作说明,当然也可以通过评论与我交流。

以上都是关于过滤窗体的实现,下面我要讲解最关键字部份,也是最重要的部份,就是:如何将获得的过滤条件DataTable转换成查询语句,这里的查询语句包括SQL或表达式树,由于是通过的过滤窗体,所以关于生成查询条件语句我放在了调用完该窗体后来实现,当然如果大家只用一种方式(实体类或表),可以直接集成在窗体类中,直接返回生成的查询语句即可。

因为我项目中用的是实体类来查询,所以我采用动态生成Lambda表达式树,然后借助PredicateBuilder类(这是别人开发的类,详见我之前的博文)进行拼接,最后生成用于查询的表达式树,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/// <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(nullnew 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是我扩展的支持任意类型的转换,该扩展方法又引用了其它的自定义扩展方法,具体方法定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/// <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
}

  

以下是具体的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//获取用于过滤的字段(这里直接采用数据列表的相应列,大家可以生成相应的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);
        }

由于代码较多,且使用了一些其它的编写好的类,若有不明白的地方,可以在此文下评论,我会帮忙解答,希望能帮到大家,不足之处也欢迎大家指证,谢谢!


本文转自 梦在旅途 博客园博客,原文链接:http://www.cnblogs.com/zuowj/p/4459956.html  ,如需转载请自行联系原作者