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

简介:

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

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

 

过滤之后:

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

首先说一下界面的设计》

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< string string > Operators =  new  KeyValueList< string string >()
     {
         {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( 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是我扩展的支持任意类型的转换,该扩展方法又引用了其它的自定义扩展方法,具体方法定义如下:

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  ,如需转载请自行联系原作者

相关文章
|
2月前
|
C# 数据库
c# dev Form1 gridview1使用Form2 gridview1的数据
c# dev Form1 gridview1使用Form2 gridview1的数据
|
2月前
|
开发框架 .NET C#
C#数据去重的这几种方式,你知道几种?
C#数据去重的这几种方式,你知道几种?
|
2月前
|
Java 数据库 C#
C#winforms实现windows窗体人脸识别
C#winforms实现windows窗体人脸识别
|
2月前
|
编译器 数据处理 C#
C#中的异步流:使用IAsyncEnumerable<T>和await foreach实现异步数据迭代
【1月更文挑战第10天】本文介绍了C#中异步流的概念,并通过使用IAsyncEnumerable<T>接口和await foreach语句,详细阐述了如何异步地迭代数据流。异步流为处理大量数据或需要流式处理数据的场景提供了一种高效且非阻塞性的方法,使得开发者能够更优雅地处理并发和数据流问题。
|
2月前
|
SQL 开发框架 .NET
EntityFramework数据持久化复习资料3、C#拓展方法与yield关键字使用
EntityFramework数据持久化复习资料3、C#拓展方法与yield关键字使用
35 0
|
11天前
|
开发框架 数据可视化 C#
|
2月前
|
存储 数据管理 开发工具
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存(CustomData)功能(C#)
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存(CustomData)功能(C#)
42 0
|
2月前
|
SQL 存储 开发框架
C# DataSet结合FlyTreeView显示树状模型数据
C# DataSet结合FlyTreeView显示树状模型数据
|
2月前
|
XML 存储 开发框架
c#教你网站数据轻松解析抓取,HtmlAgilityPack解析的奇妙之处
c#教你网站数据轻松解析抓取,HtmlAgilityPack解析的奇妙之处
52 0