WPF 自定义一个过滤器控件

先看看效果

绕了很多弯路,最终还是只能选择用ExpressionTree来实现。。。。

使用的框架是微软 MvvmToolkit,控件样式是Panuon

控件代码

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6 using System.Windows;
  7 using System.Windows.Controls;
  8 using System.Windows.Data;
  9 using System.Windows.Documents;
 10 using System.Windows.Input;
 11 using System.Windows.Media;
 12 using System.Windows.Media.Imaging;
 13 using System.Windows.Navigation;
 14 using System.Windows.Shapes;
 15 using Exp = System.Linq.Expressions.Expression;
 16 
 17 namespace PanuonLearn
 18 {
 19     /// <summary>
 20     /// 过滤类型
 21     /// </summary>
 22     public enum EFilterKinds
 23     {
 24         Number,
 25         String,
 26     }
 27 
 28     /// <summary>
 29     /// 数字操作符
 30     /// </summary>
 31     public enum ENumberOperator
 32     {
 33         Equal = 0,
 34 
 35         NotEqual = 1,
 36 
 37         GreaterThan = 2,
 38 
 39         LessThan = 4,
 40 
 41         GreaterThanOrEqual = 8,
 42 
 43         LessThanOrEqual = 16,
 44     }
 45 
 46     /// <summary>
 47     /// 字符串操作符
 48     /// </summary>
 49     public enum ETextOperator
 50     {
 51         Equal,
 52         NotEqual,
 53         Contains,
 54         NotContains,
 55         StartsWith,
 56         EndsWith,
 57     }
 58 
 59     [TemplatePart(Name = "PART_TitleTextBlock", Type = typeof(FilterBox))]
 60     [TemplatePart(Name = "PART_OperatorComboBox", Type = typeof(FilterBox))]
 61     [TemplatePart(Name = "PART_ConditionTextBox", Type = typeof(FilterBox))]
 62     public class FilterBox : Control
 63     {
 64         // Using a DependencyProperty as the backing store for Expression.  This enables animation, styling, binding, etc...
 65         public static readonly DependencyProperty ConditionExpressionProperty =
 66             DependencyProperty.Register("ConditionExpression", typeof(Exp), typeof(FilterBox), new FrameworkPropertyMetadata(default, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
 67 
 68         // Using a DependencyProperty as the backing store for IsSelected.  This enables animation, styling, binding, etc...
 69         public static readonly DependencyProperty IsSelectedProperty =
 70             DependencyProperty.Register("IsSelected", typeof(bool), typeof(FilterBox), new PropertyMetadata(default));
 71 
 72         // Using a DependencyProperty as the backing store for ObjectType.  This enables animation, styling, binding, etc...
 73         public static readonly DependencyProperty ObjectTypeProperty =
 74             DependencyProperty.Register("ObjectType", typeof(Type), typeof(FilterBox), new PropertyMetadata(default));
 75 
 76         // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
 77         public static readonly DependencyProperty TitleProperty =
 78             DependencyProperty.Register("Title", typeof(string), typeof(FilterBox), new PropertyMetadata(default));
 79 
 80         private ENumberOperator _numberOperator;
 81 
 82         /// <summary>
 83         /// 文本操作符
 84         /// </summary>
 85         private ETextOperator _textOperator;
 86 
 87         public Exp ConditionExpression
 88         {
 89             get { return (Exp)GetValue(ConditionExpressionProperty); }
 90             set { SetValue(ConditionExpressionProperty, value); }
 91         }
 92 
 93         /// <summary>
 94         /// 条件内容
 95         /// </summary>
 96         public string ConditionText { get; set; }
 97 
 98         public EFilterKinds FilterKind { get; set; }
 99 
100         /// <summary>
101         /// 是否被选择
102         /// </summary>
103         public bool IsSelected
104         {
105             get { return (bool)GetValue(IsSelectedProperty); }
106             set { SetValue(IsSelectedProperty, value); }
107         }
108 
109         /// <summary>
110         /// 要调用的方法名称
111         /// </summary>
112         public string MethodName { get; set; }
113 
114         /// <summary>
115         /// 对象类型
116         /// </summary>
117         public Type ObjectType
118         {
119             get { return (Type)GetValue(ObjectTypeProperty); }
120             set { SetValue(ObjectTypeProperty, value); }
121         }
122 
123         /// <summary>
124         /// 要使用的属性名称
125         /// </summary>
126         public string PropertyName { get; set; }
127 
128         /// <summary>
129         /// 显示名称
130         /// </summary>
131         public string Title
132         {
133             get { return (string)GetValue(TitleProperty); }
134             set { SetValue(TitleProperty, value); }
135         }
136 
137         static FilterBox()
138         {
139             DefaultStyleKeyProperty.OverrideMetadata(typeof(FilterBox), new FrameworkPropertyMetadata(typeof(FilterBox)));
140         }
141 
142         public override void OnApplyTemplate()
143         {
144             base.OnApplyTemplate();
145             var comboBox = GetTemplateChild("PART_OperatorComboBox") as ComboBox;
146             switch (FilterKind)
147             {
148                 case EFilterKinds.Number:
149                     comboBox.ItemsSource = Enum.GetValues(typeof(ENumberOperator));
150                     break;
151 
152                 case EFilterKinds.String:
153                     comboBox.ItemsSource = Enum.GetValues(typeof(ETextOperator));
154                     break;
155 
156                 default:
157                     break;
158             }
159             if (comboBox != null)
160             {
161                 comboBox.SelectionChanged += (s, e) =>
162                 {
163                     switch (FilterKind)
164                     {
165                         case EFilterKinds.Number:
166                             _numberOperator = (ENumberOperator)comboBox.SelectedValue;
167                             break;
168 
169                         case EFilterKinds.String:
170                             _textOperator = (ETextOperator)comboBox.SelectedValue;
171                             break;
172 
173                         default:
174                             break;
175                     }
176                     Compile();
177                 };
178             }
179             var textBox = GetTemplateChild("PART_ConditionTextBox") as TextBox;
180             if (textBox != null)
181             {
182                 textBox.TextChanged += (s, e) =>
183                 {
184                     ConditionText = textBox.Text;
185                     Compile();
186                 };
187             }
188         }
189 
190         /// <summary>
191         /// 编译条件
192         /// </summary>
193         private void Compile()
194         {
195             var paraExp = ExpressionObject.Parameter;//参数表达式,必须和View中的是同一个
196             Exp propertyExp = Exp.Property(paraExp, PropertyName);//属性表达式
197             if (ConditionText == null) return;
198             Exp constantExp = null;//常数表达式
199             switch (FilterKind)
200             {
201                 case EFilterKinds.Number:
202                     propertyExp = Exp.Convert(propertyExp, typeof(double));//转换表达式
203                     var num = Convert.ToDouble(ConditionText);
204                     constantExp = Exp.Constant(num);
205                     break;
206 
207                 case EFilterKinds.String:
208                     constantExp = Exp.Constant(ConditionText);
209                     break;
210 
211                 default:
212                     break;
213             }
214 
215             Exp condition = null;
216             if (FilterKind == EFilterKinds.Number)
217             {
218                 switch (_numberOperator)
219                 {
220                     case ENumberOperator.Equal:
221                         condition = Exp.Equal(propertyExp, constantExp);
222                         break;
223 
224                     case ENumberOperator.NotEqual:
225                         condition = Exp.NotEqual(propertyExp, constantExp);
226                         break;
227 
228                     case ENumberOperator.GreaterThan:
229                         condition = Exp.GreaterThan(propertyExp, constantExp);
230                         break;
231 
232                     case ENumberOperator.LessThan:
233                         condition = Exp.LessThan(propertyExp, constantExp);
234                         break;
235 
236                     case ENumberOperator.GreaterThanOrEqual:
237                         condition = Exp.GreaterThanOrEqual(propertyExp, constantExp);
238                         break;
239 
240                     case ENumberOperator.LessThanOrEqual:
241                         condition = Exp.LessThanOrEqual(propertyExp, constantExp);
242                         break;
243 
244                     default:
245                         break;
246                 }
247             }
248             else if (FilterKind == EFilterKinds.String)
249             {
250                 switch (_textOperator)
251                 {
252                     case ETextOperator.Equal:
253                         condition = Exp.Equal(propertyExp, constantExp);
254                         break;
255 
256                     case ETextOperator.NotEqual:
257                         condition = Exp.NotEqual(propertyExp, constantExp);
258                         break;
259 
260                     case ETextOperator.Contains:
261                         condition = Exp.Call(propertyExp, typeof(string).GetMethod("Contains"), constantExp);
262                         break;
263 
264                     case ETextOperator.NotContains:
265                         condition = Exp.Not(Exp.Call(propertyExp, typeof(string).GetMethod("Contains"), constantExp));
266                         break;
267 
268                     case ETextOperator.StartsWith:
269                         //有多个函数重载,必须在方法参数中指明调用的是哪一个
270                         condition = Exp.Call(propertyExp, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), constantExp);
271                         break;
272 
273                     case ETextOperator.EndsWith:
274                         condition = Exp.Call(propertyExp, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), constantExp);
275                         break;
276 
277                     default:
278                         break;
279                 }
280             }
281 
282             ConditionExpression = condition;
283         }
284     }
285 }

控件样式代码

 1 <ResourceDictionary
 2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4     xmlns:local="clr-namespace:PanuonLearn">
 5 
 6     <Style TargetType="{x:Type local:FilterBox}">
 7         <Setter Property="Template">
 8             <Setter.Value>
 9                 <ControlTemplate TargetType="{x:Type local:FilterBox}">
10                     <StackPanel Orientation="Horizontal">
11                         <CheckBox
12                             x:Name="IsSelectedCheckBox"
13                             Grid.Column="0"
14                             IsChecked="{TemplateBinding IsSelected}"/>
15                         <Grid IsEnabled="{Binding ElementName=IsSelectedCheckBox, Path=IsChecked}">
16                             <Grid.ColumnDefinitions>
17                                 <ColumnDefinition Width="Auto"/>
18                                 <ColumnDefinition Width="Auto" MinWidth="75"/>
19                                 <ColumnDefinition Width="Auto" MinWidth="120"/>
20                             </Grid.ColumnDefinitions>
21                             <TextBlock
22                                 x:Name="PART_TitleTextBlock"
23                                 Grid.Column="0"
24                                 Margin="5,0"
25                                 VerticalAlignment="Center"
26                                 Text="{TemplateBinding Title}"/>
27                             <ComboBox
28                                 x:Name="PART_OperatorComboBox"
29                                 Grid.Column="1"
30                                 Margin="5,0"/>
31                             <TextBox x:Name="PART_ConditionTextBox" Grid.Column="2"/>
32                         </Grid>
33                     </StackPanel>
34                 </ControlTemplate>
35             </Setter.Value>
36         </Setter>
37     </Style>
38 </ResourceDictionary>

ViewModel代码

  1 using Microsoft.Toolkit.Mvvm.ComponentModel;
  2 using Microsoft.Toolkit.Mvvm.Input;
  3 using Panuon.UI.Silver;
  4 using System;
  5 using System.Collections.Generic;
  6 using System.Collections.ObjectModel;
  7 using System.ComponentModel;
  8 using System.Linq;
  9 using System.Linq.Expressions;
 10 using System.Text;
 11 using System.Threading.Tasks;
 12 using System.Windows.Input;
 13 
 14 namespace PanuonLearn
 15 {
 16     /// <summary>
 17     /// 全局的参数变量,不这么做,无法正确生成
 18     /// </summary>
 19     public static class ExpressionObject
 20     {
 21         public static ParameterExpression Parameter;
 22 
 23         static ExpressionObject()
 24         {
 25             Parameter = Expression.Parameter(typeof(DataItem));
 26         }
 27     }
 28     public class DataItem
 29     {
 30         [DisplayName("数据")]
 31         public string Data { get; set; }
 32 
 33         [DisplayName("说明")]
 34         public string Description { get; set; }
 35 
 36         [DisplayName("索引")]
 37         public int Index { get; set; }
 38 
 39         [DisplayName("种类")]
 40         public EKind Kind { get; set; }
 41 
 42         [DisplayName("列表")]
 43         [ColumnCombo(ItemsSourceBindingPath = nameof(ListSource))]
 44         public List<string> ListSource { get; set; }
 45 
 46         [DisplayName("名称")]
 47         public string Name { get; set; }
 48 
 49         public DataItem()
 50         {
 51             ListSource = new List<string>();
 52             ListSource.Add("A");
 53             ListSource.Add("B");
 54             ListSource.Add("C");
 55             ListSource.Add("D");
 56             ListSource.Add("E");
 57         }
 58     }
 59     internal class FormViewModel : ObservableValidator
 60     {
 61         private Type _objectType;
 62 
 63         public Expression ConditionExp1 { get; set; }
 64         public Expression ConditionExp2 { get; set; }
 65 
 66         public ObservableCollection<DataItem> DataItems { get; set; }
 67 
 68         public ICommand FilterCmd { get; set; }
 69 
 70         public Type ObjectType
 71         {
 72             get => _objectType;
 73             set => SetProperty(ref _objectType, value);
 74         }
 75 
 76         public FormViewModel()
 77         {
 78             ObjectType = typeof(DataItem);
 79             FilterCmd = new RelayCommand(Filter);
 80             DataItems = new ObservableCollection<DataItem>();
 81             var item1 = new DataItem();
 82             item1.Name = "AA";
 83             item1.Index = 1;
 84             item1.Kind = EKind.Large;
 85             var item2 = new DataItem();
 86             item2.Name = "BB";
 87             item2.Index = 4;
 88             item2.Kind = EKind.Midden;
 89             var item3 = new DataItem();
 90             item3.Name = "BB";
 91             item3.Index = 7;
 92             item3.Kind = EKind.Small;
 93             DataItems.Add(item1);
 94             DataItems.Add(item2);
 95             DataItems.Add(item3);
 96         }
 97 
 98         private void Filter()
 99         {
100             var exp1 = ConditionExp1;//第一个条件
101             var exp2 = ConditionExp2;//第二个条件
102             var exp = Expression.AndAlso(exp1, exp2);//合并条件
103             var func = Expression.Lambda<Predicate<DataItem>>(exp, ExpressionObject.Parameter).Compile();
104             if (func == null) return;
105             var s = DataItems.Where(s1 => func.Invoke(s1)).FirstOrDefault();
106             if (s == null) return;
107             System.Windows.MessageBox.Show($"{s.Name}===={s.Index}");
108         }
109     }
110 }

界面代码

 1 <Window
 2     x:Class="PanuonLearn.FormView"
 3     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 5     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 6     xmlns:local="clr-namespace:PanuonLearn"
 7     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 8     xmlns:pu="clr-namespace:Panuon.UI.Silver;assembly=Panuon.UI.Silver"
 9     xmlns:purs="clr-namespace:Panuon.UI.Silver.Resources;assembly=Panuon.UI.Silver"
10     Title="FormView"
11     Width="800"
12     Height="450"
13     d:DataContext="{d:DesignInstance Type=local:FormViewModel}"
14     mc:Ignorable="d">
15     <Window.Resources>
16         <Style BasedOn="{StaticResource {x:Static purs:StyleKeys.TextBoxStyle}}" TargetType="TextBox">
17             <Setter Property="MinWidth" Value="200"/>
18             <Setter Property="Height" Value="35"/>
19             <Setter Property="pu:TextBoxHelper.CornerRadius" Value="1"/>
20         </Style>
21     </Window.Resources>
22     <Grid>
23         <StackPanel Orientation="Vertical">
24             <!--<GroupBox Header="修改用户信息">
25                 <GroupBox.HeaderTemplate>
26                     <DataTemplate>
27                         <TextBlock
28                             FontSize="16"
29                             FontWeight="Bold"
30                             Text="修改用户信息"/>
31                     </DataTemplate>
32                 </GroupBox.HeaderTemplate>
33                 <Grid>
34                     <Grid.RowDefinitions>
35                         <RowDefinition Height="Auto"/>
36                         <RowDefinition Height="Auto"/>
37                     </Grid.RowDefinitions>
38                     <UniformGrid Grid.Row="0" Columns="2">
39                         <pu:FormGroup GroupName="First" Header="客户名称:">
40                             <TextBox Height="35" MinWidth="200"/>
41                         </pu:FormGroup>
42                         <pu:FormGroup GroupName="First" Header="客户Id:">
43                             <TextBox pu:TextBoxHelper.Watermark="hhhh"/>
44                         </pu:FormGroup>
45                         <pu:FormGroup Header="客户类别:">
46                             <StackPanel Orientation="Horizontal">
47                                 <RadioButton Content="国内客户"/>
48                                 <RadioButton Content="国内客户"/>
49                             </StackPanel>
50                         </pu:FormGroup>
51                     </UniformGrid>
52                 </Grid>
53             </GroupBox>-->
54             <StackPanel Orientation="Vertical">
55                 <local:FilterBox
56                     Title="名称"
57                     Margin="10"
58                     ConditionExpression="{Binding ConditionExp2}"
59                     FilterKind="String"
60                     ObjectType="{Binding ObjectType}"
61                     PropertyName="Name"/>
62                 <local:FilterBox
63                     Title="索引"
64                     Margin="10"
65                     ConditionExpression="{Binding ConditionExp1}"
66                     FilterKind="Number"
67                     ObjectType="{Binding ObjectType}"
68                     PropertyName="Index"/>
69                 <Button
70                     Width="200"
71                     Height="35"
72                     HorizontalAlignment="Left"
73                     Command="{Binding FilterCmd}"/>
74             </StackPanel>
75 
76             <DataGrid ItemsSource="{Binding DataItems}"/>
77         </StackPanel>
78     </Grid>
79 </Window>

 

posted @ 2022-05-05 17:11  只吃肉不喝酒  阅读(159)  评论(0编辑  收藏  举报