1. 程式人生 > 其它 >WPF 自定義一個過濾器控制元件

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>