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>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
2021-05-05 C# 类的成员的值类型和引用类型