让WPF中的DataGrid像Excel一样可以筛选

在默认情况下,WPF提供的DataGrid仅拥有数据展示等简单功能,如果要实现像Excel一样复杂的筛选过滤功能,则相对比较麻烦。本文以一个简单的小例子,简述如何通过WPF实话DataGrid的筛选功能,仅供学习分享使用,如有不足之处,还请指正。

涉及知识点

在本示例中,从数据绑定,到数据展示,涉及知识点如下所示:

  • DataGrid,要WPF提供的进行二维数据展示在列表控件,默认功能非常简单,但是可以通过数据模板或者控件模板进行扩展和美化,可伸缩性很强。
  • MVVM,是Model-View-ViewModel的简写,主要进行数据和UI进行前后端分离,在本示例中,主要用到的MVVM第三方库为CommunityToolkit.Mvvm,大大简化原生MVVM的实现方式。
  • 集合视图, 要对 DataGrid 中的数据进行分组、排序和筛选,可以将其绑定到支持这些函数的 CollectionView。 然后,可以在不影响基础源数据的情况下处理 CollectionView 中的数据。 集合视图中的更改反映在 DataGrid 用户界面 (UI) 中。
  • Popup控件,直接继承FrameworkElement,提供了一种在单独的窗口中显示内容的方法,该窗口相对于指定的元素或屏幕坐标,浮动在当前Popup应用程序窗口上,可用于悬浮窗口。

示例截图

本示例主要模仿Excel的筛选功能进行实现,右键标题栏打开浮动窗口,悬浮于标题栏下方,既可以通过文本框进行筛选,也可以通过筛选按钮弹出右键菜单,选择具体筛选方式,截图如下所示:

 

选择筛选方式,弹出窗口,如下所示:

 

 输入筛选条件,点击确定,或者取消筛选。如筛选学号里面包含2的,效果如下所示:

 

 注意:以上筛选都是客户端筛选,不会修改数据源,也不会重连数据库。

核心源码

在本示例中,核心源码主要包含以下几个部分:

前端视图【MainWindow.xaml】源码

主要实现了按学号,姓名,年龄三列进行筛选,既可以单列筛选,又可以组合筛选。且三列的筛选实现方式一致,仅是绑定列有差异。

  1 <Window x:Class="DemoDataGrid.MainWindow"
  2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6         xmlns:local="clr-namespace:DemoDataGrid"
  7         mc:Ignorable="d"
  8         Title="DataGrid筛选示例" Height="650" Width="800">
  9     <Window.Resources>
 10         <ResourceDictionary>
 11             <CollectionViewSource x:Key="ItemsSource" Source="{Binding Path=Students}"></CollectionViewSource>
 12             <CollectionViewSource x:Key="Names" Source="{Binding Path=Names}"></CollectionViewSource>
 13             <CollectionViewSource x:Key="Nos" Source="{Binding Path=Nos}"></CollectionViewSource>
 14             <CollectionViewSource x:Key="Ages" Source="{Binding Path=Ages}"></CollectionViewSource>
 15             <Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
 16                 <Setter Property="ScrollViewer.CanContentScroll" Value="True"></Setter>
 17                 <Setter Property="Template">
 18                     <Setter.Value>
 19                         <ControlTemplate TargetType="{x:Type ListBox}">
 20                             <ScrollViewer x:Name="ScrollViewer" CanContentScroll="True">
 21                                 <ItemsPresenter></ItemsPresenter>
 22                             </ScrollViewer>
 23                         </ControlTemplate>
 24                     </Setter.Value>
 25                 </Setter>
 26             </Style>
 27             <Geometry x:Key="Icon_Filter">
 28                 M608 864C588.8 864 576 851.2 576 832L576 448c0-6.4 6.4-19.2 12.8-25.6L787.2 256c6.4-6.4 6.4-19.2 0-19.2 0-6.4-6.4-12.8-19.2-12.8L256 224c-12.8 0-19.2 6.4-19.2 12.8 0 6.4-6.4 12.8 6.4 19.2l198.4 166.4C441.6 428.8 448 441.6 448 448l0 256c0 19.2-12.8 32-32 32S384 723.2 384 704L384 460.8 198.4 307.2c-25.6-25.6-32-64-19.2-96C185.6 179.2 217.6 160 256 160L768 160c32 0 64 19.2 76.8 51.2 12.8 32 6.4 70.4-19.2 89.6l-192 160L633.6 832C640 851.2 627.2 864 608 864z
 29             </Geometry>
 30             <ContextMenu x:Key="queryConditionMenu" MouseLeave="ContextMenu_MouseLeave" MenuItem.Click="ContextMenu_Click">
 31                 <MenuItem FontSize="12" Header="等于" Tag="Equal"></MenuItem>
 32                 <MenuItem FontSize="12" Header="不等于"  Tag="NotEqual"></MenuItem>
 33                 <MenuItem FontSize="12" Header="开头"  Tag="Begin"></MenuItem>
 34                 <MenuItem FontSize="12" Header="结尾"  Tag="End"></MenuItem>
 35                 <MenuItem FontSize="12" Header="包含"  Tag="In"></MenuItem>
 36                 <MenuItem FontSize="12" Header="不包含"  Tag="NotIn"></MenuItem>
 37             </ContextMenu>
 38         </ResourceDictionary>
 39         
 40     </Window.Resources>
 41     <Grid Margin="10">
 42         <Grid.RowDefinitions>
 43             <RowDefinition Height="20"></RowDefinition>
 44             <RowDefinition Height="*"></RowDefinition>
 45         </Grid.RowDefinitions>
 46         <DataGrid Grid.Row="1" x:Name="dgStudents" ItemsSource="{Binding Source={StaticResource ItemsSource} }" AutoGenerateColumns="False"
 47                               CanUserReorderColumns="True" CanUserDeleteRows="False" CanUserAddRows="False" HeadersVisibility="Column"
 48                               CanUserSortColumns="True"
 49                               VirtualizingPanel.VirtualizationMode="Recycling"
 50                               EnableColumnVirtualization="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True" GridLinesVisibility="All" RowHeight="25"
 51                               SelectionUnit="FullRow" SelectionMode="Single" IsReadOnly="True" FontSize="12" 
 52                               SelectedIndex="{Binding SelectTaskItemIndex}" SelectedItem="{Binding SelectTaskItem}"
 53                               CanUserResizeColumns="True">
 54             <DataGrid.Columns>
 55                 <DataGridTextColumn Binding="{Binding Id}" Header="Id" Width="*">
 56                     
 57                 </DataGridTextColumn>
 58                 <DataGridTextColumn Binding="{Binding No}" Width="*">
 59                     <DataGridTextColumn.Header>
 60                         <TextBlock Text="学号" FontWeight="Regular" MouseRightButtonDown="TextBlock_MouseRightButtonDown" Tag="No"></TextBlock>
 61                     </DataGridTextColumn.Header>
 62                 </DataGridTextColumn>
 63                 <DataGridTextColumn Binding="{Binding Name}" Width="*">
 64                     <DataGridTextColumn.Header>
 65                         <TextBlock Text="姓名" FontWeight="Regular" MouseRightButtonDown="TextBlock_MouseRightButtonDown" Tag="Name"></TextBlock>
 66                     </DataGridTextColumn.Header>
 67                 </DataGridTextColumn>
 68                 <DataGridTextColumn Binding="{Binding Age}" Width="*">
 69                     <DataGridTextColumn.Header>
 70                         <TextBlock Text="年龄" FontWeight="Regular" MouseRightButtonDown="TextBlock_MouseRightButtonDown" Tag="Age"></TextBlock>
 71                     </DataGridTextColumn.Header>
 72                 </DataGridTextColumn>
 73             </DataGrid.Columns>
 74         </DataGrid>
 75         <Popup x:Name="popupNo" Width="135" MaxHeight="500" Height="Auto" PopupAnimation="Slide" AllowsTransparency="False" MouseLeave="popup_MouseLeave">
 76             <Border BorderBrush="LightBlue" BorderThickness="1" Padding="5" Background="AliceBlue">
 77                 <Grid>
 78                     <Grid.ColumnDefinitions>
 79                         <ColumnDefinition></ColumnDefinition>
 80                         <ColumnDefinition></ColumnDefinition>
 81                     </Grid.ColumnDefinitions>
 82                     <Grid.RowDefinitions>
 83                         <RowDefinition></RowDefinition>
 84                         <RowDefinition></RowDefinition>
 85                         <RowDefinition></RowDefinition>
 86                     </Grid.RowDefinitions>
 87                     <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal">
 88                         <TextBox Height="25" x:Name="txtNo" MinWidth="60" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Tag="No" TextChanged="TextBox_TextChanged"></TextBox>
 89                         <Button x:Name="btnNoFilter"  Tag="No" ClickMode="Press" Click="ButtonFilter_Click" ContextMenu="{StaticResource queryConditionMenu}">
 90                             <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
 91                                 <Path Data="{StaticResource Icon_Filter}" Stroke="Gray" StrokeThickness="1" 
 92                        Height="12" Width="12" Stretch="Fill"></Path>
 93                                 <TextBlock Margin="2,0" Text="筛选" FontSize="12"></TextBlock>
 94                             </StackPanel>
 95                         </Button>
 96                     </StackPanel>
 97                     <ListBox x:Name="lbNos" ItemsSource="{Binding Source={StaticResource Nos}}" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizing="True" Style="{StaticResource ListBoxStyle}">
 98                         <ListBox.ItemTemplate>
 99                             <DataTemplate>
100                                 <CheckBox Content="{Binding FilterText}" IsChecked="{Binding IsChecked}"></CheckBox>
101                             </DataTemplate>
102                         </ListBox.ItemTemplate>
103                     </ListBox>
104 
105                     <Button Tag="No" Content="取消" Width="60" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="0" Click="btnCancel_Click"></Button>
106                     <Button Content="确定" Width="60" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Click="btnOk_Click"></Button>
107                 </Grid>
108             </Border>
109         </Popup>
110         <Popup x:Name="popupName" Width="135" MaxHeight="500" Height="Auto" PopupAnimation="Slide" AllowsTransparency="False" MouseLeave="popup_MouseLeave">
111             <Border BorderBrush="LightBlue" BorderThickness="1" Padding="5" Background="AliceBlue">
112                 <Grid>
113                     <Grid.ColumnDefinitions>
114                         <ColumnDefinition></ColumnDefinition>
115                         <ColumnDefinition></ColumnDefinition>
116                     </Grid.ColumnDefinitions>
117                     <Grid.RowDefinitions>
118                         <RowDefinition></RowDefinition>
119                         <RowDefinition></RowDefinition>
120                         <RowDefinition></RowDefinition>
121                     </Grid.RowDefinitions>
122                     <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal">
123                         <TextBox Height="25" x:Name="txtName" MinWidth="60" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Tag="Name" TextChanged="TextBox_TextChanged"></TextBox>
124                         <Button x:Name="btnNameFilter"  Tag="Name" Click="ButtonFilter_Click" ContextMenu="{StaticResource queryConditionMenu}">
125                             <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
126                                 <Path Data="{StaticResource Icon_Filter}" Stroke="Gray" StrokeThickness="1" 
127                        Height="12" Width="12" Stretch="Fill"></Path>
128                                 <TextBlock Margin="2,0" Text="筛选" FontSize="12"></TextBlock>
129                             </StackPanel>
130                         </Button>
131                     </StackPanel>
132                     <ListBox x:Name="lbNames" ItemsSource="{Binding Source={StaticResource Names}}" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizing="True" Style="{StaticResource ListBoxStyle}">
133                         <ListBox.ItemTemplate>
134                             <DataTemplate>
135                                 <CheckBox Content="{Binding FilterText}" IsChecked="{Binding IsChecked}"></CheckBox>
136                             </DataTemplate>
137                         </ListBox.ItemTemplate>
138                     </ListBox>
139 
140                     <Button Tag="No" Content="取消" Width="60" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="0" Click="btnCancel_Click"></Button>
141                     <Button Content="确定" Width="60" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Click="btnOk_Click"></Button>
142                 </Grid>
143             </Border>
144         </Popup>
145         <Popup x:Name="popupAge" Width="135" MaxHeight="500" Height="Auto" PopupAnimation="Slide" AllowsTransparency="False" MouseLeave="popup_MouseLeave">
146             <Border BorderBrush="LightBlue" BorderThickness="1" Padding="5" Background="AliceBlue">
147                 <Grid>
148                     <Grid.ColumnDefinitions>
149                         <ColumnDefinition></ColumnDefinition>
150                         <ColumnDefinition></ColumnDefinition>
151                     </Grid.ColumnDefinitions>
152                     <Grid.RowDefinitions>
153                         <RowDefinition></RowDefinition>
154                         <RowDefinition></RowDefinition>
155                         <RowDefinition></RowDefinition>
156                     </Grid.RowDefinitions>
157                     <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal">
158                         <TextBox Height="25" x:Name="txtAge" MinWidth="60" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Tag="Age" TextChanged="TextBox_TextChanged"></TextBox>
159                         <Button x:Name="btnAgeFilter"  Tag="Age" Click="ButtonFilter_Click" ContextMenu="{StaticResource queryConditionMenu}">
160                             <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
161                                 <Path Data="{StaticResource Icon_Filter}" Stroke="Gray" StrokeThickness="1" 
162                        Height="12" Width="12" Stretch="Fill"></Path>
163                                 <TextBlock Margin="2,0" Text="筛选" FontSize="12"></TextBlock>
164                             </StackPanel>
165                         </Button>
166                     </StackPanel>
167                     <ListBox x:Name="lbAges" ItemsSource="{Binding Source={StaticResource Ages}}" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizing="True" Style="{StaticResource ListBoxStyle}">
168                         <ListBox.ItemTemplate>
169                             <DataTemplate>
170                                 <CheckBox Content="{Binding FilterText}" IsChecked="{Binding IsChecked}"></CheckBox>
171                             </DataTemplate>
172                         </ListBox.ItemTemplate>
173                     </ListBox>
174 
175                     <Button Tag="No" Content="取消" Width="60" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="0" Click="btnCancel_Click"></Button>
176                     <Button Content="确定" Width="60" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Click="btnOk_Click"></Button>
177                 </Grid>
178             </Border>
179         </Popup>
180 
181         <Popup x:Name="popupNoMenu" Width="300" MaxHeight="500" Height="200" PopupAnimation="Slide" AllowsTransparency="False" Tag="">
182             <Border BorderThickness="1" BorderBrush="Beige" Padding="15" Background="AliceBlue">
183                 <Grid>
184                     <Grid.ColumnDefinitions>
185                         <ColumnDefinition></ColumnDefinition>
186                         <ColumnDefinition></ColumnDefinition>
187                     </Grid.ColumnDefinitions>
188                     <Grid.RowDefinitions>
189                         <RowDefinition Height="Auto"></RowDefinition>
190                         <RowDefinition Height="Auto"></RowDefinition>
191                         <RowDefinition Height="Auto"></RowDefinition>
192                         <RowDefinition></RowDefinition>
193                     </Grid.RowDefinitions>
194                     <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
195                         <TextBlock Text="学号" VerticalAlignment="Center"></TextBlock>
196                         <ComboBox x:Name="combNoMenu1" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center">
197                             <ComboBoxItem Content="等于" ></ComboBoxItem>
198                             <ComboBoxItem Content="不等于"></ComboBoxItem>
199                             <ComboBoxItem Content="开头"></ComboBoxItem>
200                             <ComboBoxItem Content="结尾"></ComboBoxItem>
201                             <ComboBoxItem Content="包含"></ComboBoxItem>
202                             <ComboBoxItem Content="不包含"></ComboBoxItem>
203                         </ComboBox>
204                         <TextBox x:Name="txtNoMenu1" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox>
205                     </StackPanel>
206                     <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0"  Grid.ColumnSpan="2">
207                         <RadioButton x:Name="rbNoAnd" Content="与" IsChecked="True" Margin="4" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton>
208                         <RadioButton x:Name="rbNoOr" Content="或" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton>
209                     </StackPanel>
210                     <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0"  Grid.ColumnSpan="2">
211                         <TextBlock Text="学号" VerticalAlignment="Center"></TextBlock>
212                         <ComboBox x:Name="combNoMenu2" Height="28" Margin="4" Width="100" VerticalContentAlignment="Center">
213                             <ComboBoxItem Content="等于" ></ComboBoxItem>
214                             <ComboBoxItem Content="不等于"></ComboBoxItem>
215                             <ComboBoxItem Content="开头"></ComboBoxItem>
216                             <ComboBoxItem Content="结尾"></ComboBoxItem>
217                             <ComboBoxItem Content="包含"></ComboBoxItem>
218                             <ComboBoxItem Content="不包含"></ComboBoxItem>
219                         </ComboBox>
220                         <TextBox x:Name="txtNoMenu2" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox>
221                     </StackPanel>
222                     <Button Tag="No" Content="取消" Width="100" Height="28" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="0" Click="btnCancelFilter_Click"></Button>
223                     <Button Tag="No" Content="确定" Width="100" Height="28" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1" Click="btnOkFilter_Click"></Button>
224                 </Grid>
225             </Border>
226         </Popup>
227         <Popup x:Name="popupNameMenu" Width="300" MaxHeight="500" Height="200" PopupAnimation="Slide" AllowsTransparency="False" Tag="">
228             <Border BorderThickness="1" BorderBrush="Beige" Padding="15" Background="AliceBlue">
229                 <Grid>
230                     <Grid.RowDefinitions>
231                         <RowDefinition Height="Auto"></RowDefinition>
232                         <RowDefinition Height="Auto"></RowDefinition>
233                         <RowDefinition Height="Auto"></RowDefinition>
234                         <RowDefinition Height="Auto"></RowDefinition>
235                     </Grid.RowDefinitions>
236                     <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0">
237                         <TextBlock Text="姓名" VerticalAlignment="Center"></TextBlock>
238                         <ComboBox x:Name="combNameMenu1" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center">
239                             <ComboBoxItem Content="等于" ></ComboBoxItem>
240                             <ComboBoxItem Content="不等于"></ComboBoxItem>
241                             <ComboBoxItem Content="开头"></ComboBoxItem>
242                             <ComboBoxItem Content="结尾"></ComboBoxItem>
243                             <ComboBoxItem Content="包含"></ComboBoxItem>
244                             <ComboBoxItem Content="不包含"></ComboBoxItem>
245                         </ComboBox>
246                         <TextBox x:Name="txtNameMenu1" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox>
247                     </StackPanel>
248                     <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0">
249                         <RadioButton x:Name="rbNameAnd" Content="与" IsChecked="True" Margin="4" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton>
250                         <RadioButton x:Name="rbNameOr" Content="或" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton>
251                     </StackPanel>
252                     <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0">
253                         <TextBlock Text="姓名" VerticalAlignment="Center"></TextBlock>
254                         <ComboBox x:Name="combNameMenu2" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center">
255                             <ComboBoxItem Content="等于" ></ComboBoxItem>
256                             <ComboBoxItem Content="不等于"></ComboBoxItem>
257                             <ComboBoxItem Content="开头"></ComboBoxItem>
258                             <ComboBoxItem Content="结尾"></ComboBoxItem>
259                             <ComboBoxItem Content="包含"></ComboBoxItem>
260                             <ComboBoxItem Content="不包含"></ComboBoxItem>
261                         </ComboBox>
262                         <TextBox x:Name="txtNameMenu2" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox>
263                     </StackPanel>
264                     <Button Tag="Name" Content="取消" Width="100" Height="28" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="0" Click="btnCancelFilter_Click"></Button>
265                     <Button Tag="Name" Content="确定" Width="100" Height="28" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1" Click="btnOkFilter_Click"></Button>
266                 </Grid>
267             </Border>
268         </Popup>
269         <Popup x:Name="popupAgeMenu" Width="300" MaxHeight="500" Height="200" PopupAnimation="Slide" AllowsTransparency="False" Tag="">
270             <Border BorderThickness="1" BorderBrush="Beige" Padding="15" Background="AliceBlue">
271                 <Grid>
272                     <Grid.RowDefinitions>
273                         <RowDefinition Height="Auto"></RowDefinition>
274                         <RowDefinition Height="Auto"></RowDefinition>
275                         <RowDefinition Height="Auto"></RowDefinition>
276                         <RowDefinition Height="Auto"></RowDefinition>
277                     </Grid.RowDefinitions>
278                     <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0">
279                         <TextBlock Text="年龄" VerticalAlignment="Center"></TextBlock>
280                         <ComboBox x:Name="combAgeMenu1" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center">
281                             <ComboBoxItem Content="等于" ></ComboBoxItem>
282                             <ComboBoxItem Content="不等于"></ComboBoxItem>
283                             <ComboBoxItem Content="开头"></ComboBoxItem>
284                             <ComboBoxItem Content="结尾"></ComboBoxItem>
285                             <ComboBoxItem Content="包含"></ComboBoxItem>
286                             <ComboBoxItem Content="不包含"></ComboBoxItem>
287                         </ComboBox>
288                         <TextBox x:Name="txtAgeMenu1" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox>
289                     </StackPanel>
290                     <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0">
291                         <RadioButton x:Name="rbAgeAnd" Content="与"  IsChecked="True" Margin="4" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton>
292                         <RadioButton x:Name="rbAgeOr" Content="或" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton>
293                     </StackPanel>
294                     <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0">
295                         <TextBlock Text="年龄" VerticalAlignment="Center"></TextBlock>
296                         <ComboBox x:Name="combAgeMenu2" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center">
297                             <ComboBoxItem Content="等于" ></ComboBoxItem>
298                             <ComboBoxItem Content="不等于"></ComboBoxItem>
299                             <ComboBoxItem Content="开头"></ComboBoxItem>
300                             <ComboBoxItem Content="结尾"></ComboBoxItem>
301                             <ComboBoxItem Content="包含"></ComboBoxItem>
302                             <ComboBoxItem Content="不包含"></ComboBoxItem>
303                         </ComboBox>
304                         <TextBox x:Name="txtAgeMenu2" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox>
305                     </StackPanel>
306                     <Button Tag="Age" Content="取消" Width="100" Height="28" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="0" Click="btnCancelFilter_Click"></Button>
307                     <Button Tag="Age" Content="确定" Width="100" Height="28" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1" Click="btnOkFilter_Click"></Button>
308                 </Grid>
309             </Border>
310         </Popup>
311     </Grid>
312 </Window>

业务逻辑【MainWindowViewModel】

业务逻辑处理主要复责数据初始化等业务相关内容,和UI无关,如下所示:

 1 using CommunityToolkit.Mvvm.ComponentModel;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 
 8 namespace DemoDataGrid
 9 {
10     public class MainWindowViewModel:ObservableObject
11     {
12         #region 属性及构造函数
13 
14         private List<Student> students;
15 
16         public List<Student> Students
17         {
18             get { return students; }
19             set { SetProperty(ref students, value); }
20         }
21 
22         private List<FilterInfo> names;
23 
24         public List<FilterInfo> Names
25         {
26             get { return names; }
27             set { SetProperty(ref names, value); }
28         }
29 
30         private List<FilterInfo> nos;
31 
32         public List<FilterInfo> Nos
33         {
34             get { return nos; }
35             set {SetProperty(ref nos , value); }
36         }
37 
38         private List<FilterInfo> ages;
39 
40         public List<FilterInfo> Ages
41         {
42             get { return ages; }
43             set {SetProperty(ref ages , value); }
44         }
45 
46 
47 
48         public MainWindowViewModel()
49         {
50             this.Students= new List<Student>();
51             for (int i = 0; i < 20; i++) {
52                 this.Students.Add(new Student()
53                 {
54                     Id = i,
55                     Name = $"张{i}牛",
56                     Age = (i % 10) + 10,
57                     No = i.ToString().PadLeft(4, '0'),
58                 });
59             }
60             this.Nos= new List<FilterInfo>();
61             this.Names= new List<FilterInfo>();
62             this.Ages= new List<FilterInfo>();
63             this.Students.ForEach(s => {
64                 this.Nos.Add(new FilterInfo() { FilterText=s.No,IsChecked=false });
65                 this.Names.Add(new FilterInfo() { FilterText = s.Name, IsChecked = false });
66                 this.Ages.Add(new FilterInfo() { FilterText = s.Age.ToString(), IsChecked = false });
67             });
68             this.Ages=this.Ages.Distinct().ToList();//去重
69         }
70 
71         #endregion
72 
73 
74     }
75 }

筛选功能实现【MainWindow.xaml.cs】

本示例为了简化实现,筛选功能处理主要在cs后端实现,如下所示:

  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 
 16 namespace DemoDataGrid
 17 {
 18     /// <summary>
 19     /// Interaction logic for MainWindow.xaml
 20     /// </summary>
 21     public partial class MainWindow : Window
 22     {
 23         private MainWindowViewModel viewModel;
 24 
 25         public MainWindow()
 26         {
 27             InitializeComponent();
 28             viewModel = new MainWindowViewModel();
 29             this.DataContext = viewModel;
 30         }
 31 
 32 
 33         #region 筛选
 34 
 35         private void TextBlock_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
 36         {
 37             if (sender != null && sender is TextBlock)
 38             {
 39                 var textBlock = sender as TextBlock;
 40                 var tag = textBlock.Tag.ToString();
 41                 var pop = this.FindName($"popup{tag}");
 42                 if (pop != null)
 43                 {
 44                     var popup = pop as System.Windows.Controls.Primitives.Popup;
 45                     if (popup != null)
 46                     {
 47                         popup.IsOpen = true;
 48                         popup.PlacementTarget = textBlock;
 49                         popup.Placement = System.Windows.Controls.Primitives.PlacementMode.RelativePoint;
 50                         popup.VerticalOffset = 10;
 51                         popup.HorizontalOffset = 10;
 52                     }
 53                 }
 54             }
 55         }
 56 
 57         private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
 58         {
 59             TextBox textBox = e.OriginalSource as TextBox;
 60             var tag = textBox.Tag;//条件
 61             var text = textBox.Text;
 62             if (tag != null)
 63             {
 64                 if (tag.ToString() == "No")
 65                 {
 66                     Filter(this.lbNos.ItemsSource, this.txtNo.Text);
 67                 }
 68                 if (tag.ToString() == "Name")
 69                 {
 70                     Filter(this.lbNames.ItemsSource, this.txtName.Text);
 71                 }
 72                 if (tag.ToString() == "Age")
 73                 {
 74                     Filter(this.lbAges.ItemsSource, this.txtAge.Text);
 75                 }
 76             }
 77 
 78         }
 79 
 80         private void Filter(object source, string filter)
 81         {
 82             var cv = CollectionViewSource.GetDefaultView(source);
 83             if (cv != null && cv.CanFilter)
 84             {
 85                 cv.Filter = new Predicate<object>((obj) => {
 86                     bool flag = true;
 87                     var t = obj as FilterInfo;
 88                     if (t != null)
 89                     {
 90                         flag = t.FilterText.Contains(filter);
 91                     }
 92                     return flag;
 93                 });
 94             }
 95         }
 96 
 97         private void popup_MouseLeave(object sender, MouseEventArgs e)
 98         {
 99             var popup = e.OriginalSource as System.Windows.Controls.Primitives.Popup;
100             var showContext = (this.FindResource("queryConditionMenu") as ContextMenu)?.IsOpen;
101             if (popup != null && showContext==false)
102             {
103                 popup.IsOpen = false;
104             }
105         }
106 
107         private void btnCancel_Click(object sender, RoutedEventArgs e)
108         {
109             var btn = e.OriginalSource as Button;
110             if (btn != null)
111             {
112                 var tag = btn.Tag;
113                 if (tag.ToString() == "No")
114                 {
115                     ClearFilter(this.txtNo, this.viewModel.Nos);
116                 }
117                 if (tag.ToString() == "Name")
118                 {
119                     ClearFilter(this.txtName, this.viewModel.Names);
120 
121                 }
122                 if (tag.ToString() == "Age")
123                 {
124                     ClearFilter(this.txtAge, this.viewModel.Ages);
125                 }
126                 FilterTask();//清除以后,重新刷新
127             }
128         }
129 
130         private void ClearFilter(TextBox textBox, List<FilterInfo> collection)
131         {
132             textBox.Clear();
133             foreach (var f in collection)
134             {
135                 f.IsChecked = false;
136             }
137         }
138 
139         private void btnOk_Click(object sender, RoutedEventArgs e)
140         {
141             //
142             FilterTask();
143         }
144 
145 
146         private void FilterTask()
147         {
148             var cv = CollectionViewSource.GetDefaultView(this.dgStudents.ItemsSource);
149             if (cv != null && cv.CanFilter)
150             {
151                 cv.Filter = new Predicate<object>((obj) =>
152                 {
153                     bool flag = true;
154                     var t = obj as Student;
155                     if (t != null)
156                     {
157                         var nos = this.viewModel.Nos.Where(r => r.IsChecked == true).ToList();
158                         var names = this.viewModel.Names.Where(r => r.IsChecked == true).ToList();
159                         var ages = this.viewModel.Ages.Where(r => r.IsChecked == true).ToList();
160                         if (nos.Count() > 0)
161                         {
162                             flag = flag && nos.Select(r => r.FilterText).Contains(t.No);
163                         }
164                         if (names.Count() > 0)
165                         {
166                             flag = flag && names.Select(r => r.FilterText).Contains(t.Name);
167                         }
168                         if (ages.Count() > 0)
169                         {
170                             flag = flag && ages.Select(r => r.FilterText).Contains(t.Age.ToString());
171                         }
172                     }
173                     return flag;
174                 });
175             }
176         }
177 
178         #endregion
179 
180         private List<string> condition = new List<string>() { "Equal", "NotEqual", "Begin", "End", "In", "NotIn" };
181 
182         private void ButtonFilter_Click(object sender, RoutedEventArgs e)
183         {
184             var btn = e.OriginalSource as Button;
185             if (btn != null)
186             {
187                 var tag = btn.Tag;
188                 var popup = this.FindName($"popup{tag}") as System.Windows.Controls.Primitives.Popup;
189                 if (popup != null)
190                 {
191                     popup.IsOpen = true;
192                 }
193                 if (btn.ContextMenu.IsOpen)
194                 {
195                     btn.ContextMenu.IsOpen = false;
196                 }
197                 else
198                 {
199                     btn.ContextMenu.Tag = tag;
200                     btn.ContextMenu.Width = 100;
201                     btn.ContextMenu.Height = 150;
202                     btn.ContextMenu.IsOpen = true;
203                     btn.ContextMenu.PlacementTarget = btn;
204                     btn.ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
205                 }
206             }
207         }
208 
209         private void ContextMenu_MouseLeave(object sender, MouseEventArgs e)
210         {
211             var menu = e.OriginalSource as ContextMenu;
212             if (menu != null)
213             {
214                 menu.IsOpen = false;
215             }
216         }
217 
218         private void ContextMenu_Click(object sender, RoutedEventArgs e)
219         {
220             var contextMenu = sender as ContextMenu;
221             if (contextMenu == null)
222             {
223                 return;
224             }
225             var menuItem = e.OriginalSource as MenuItem;
226             if (menuItem == null)
227             {
228                 return;
229             }
230             var tag1 = contextMenu.Tag.ToString();//点击的哪一个按钮
231             var tag2 = menuItem.Tag.ToString();//点击的是哪一个菜单
232             var pop = this.FindName($"popup{tag1}Menu");
233             var comb = this.FindName($"comb{tag1}Menu1");
234             HideParentPopup(tag1);//隐藏父Popup
235             if (comb != null)
236             {
237                 var combMenu = comb as ComboBox;
238                 combMenu.SelectedIndex = condition.IndexOf(tag2);
239             }
240             if (pop != null)
241             {
242                 var popup = pop as System.Windows.Controls.Primitives.Popup;
243                 popup.IsOpen = true;
244                 popup.PlacementTarget = dgStudents;
245                 popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Center;
246             }
247         }
248 
249         private void btnCancelFilter_Click(object sender, RoutedEventArgs e)
250         {
251             if (sender == null)
252             {
253                 return;
254             }
255             var btn = sender as System.Windows.Controls.Button;
256             if (btn != null)
257             {
258                 var tag = btn.Tag.ToString();
259                 HidePopupMenu(tag);//隐藏Popup控件
260                 if (tag == "No")
261                 {
262                     ClearMenuFilter(this.txtNoMenu1, this.txtNoMenu2);
263                 }
264                 if (tag == "Name")
265                 {
266                     ClearMenuFilter(this.txtNameMenu1, this.txtNameMenu2);
267                 }
268                 if (tag == "Age")
269                 {
270                     ClearMenuFilter(this.txtAgeMenu1, this.txtAgeMenu2);
271                 }
272                 FilterMenuTask();
273             }
274         }
275 
276 
277         private void btnOkFilter_Click(object sender, RoutedEventArgs e)
278         {
279             if (sender == null)
280             {
281                 return;
282             }
283             var btn = sender as System.Windows.Controls.Button;
284             if (btn != null)
285             {
286                 var tag = btn.Tag.ToString();
287                 HidePopupMenu(tag);
288                 FilterMenuTask();
289             }
290         }
291 
292         /// <summary>
293         /// 隐藏父Popup
294         /// </summary>
295         /// <param name="tag"></param>
296         private void HideParentPopup(string tag)
297         {
298             //点击右键菜单时,隐藏父Popup控件
299             if (tag == "No")
300             {
301                 this.popupNo.IsOpen = false;
302             }
303             if (tag == "Name")
304             {
305                 this.popupName.IsOpen = false;
306             }
307             if (tag == "Age")
308             {
309                 this.popupAge.IsOpen = false;
310             }
311         }
312 
313         /// <summary>
314         /// 隐藏菜单弹出的Popup控件
315         /// </summary>
316         /// <param name="tag"></param>
317         private void HidePopupMenu(string tag)
318         {
319             var pop = this.FindName($"popup{tag}Menu");
320             if (pop != null)
321             {
322                 var popup = pop as System.Windows.Controls.Primitives.Popup;
323                 popup.IsOpen = false;
324             }
325         }
326 
327         /// <summary>
328         /// 清除菜单中的文本过滤条件
329         /// </summary>
330         /// <param name="txt1"></param>
331         /// <param name="txt2"></param>
332         private void ClearMenuFilter(TextBox txt1, TextBox txt2)
333         {
334             txt1?.Clear();
335             txt2?.Clear();
336         }
337 
338         /// <summary>
339         /// 
340         /// </summary>
341         private void FilterMenuTask()
342         {
343             var cv = CollectionViewSource.GetDefaultView(this.dgStudents.ItemsSource);
344             if (cv != null && cv.CanFilter)
345             {
346                 cv.Filter = new Predicate<object>((obj) =>
347                 {
348                     bool flag = true;
349                     var t = obj as Student;
350                     if (t != null)
351                     {
352                         string noText1 = this.txtNoMenu1.Text.Trim();
353                         string noText2 = this.txtNoMenu2.Text.Trim();
354                         int noConditionType1 = this.combNoMenu1.SelectedIndex;
355                         int noConditionType2 = this.combNoMenu2.SelectedIndex;
356                         string nameText1 = this.txtNameMenu1.Text.Trim();
357                         string nameText2 = this.txtNameMenu2.Text.Trim();
358                         int nameConditionType1 = this.combNameMenu1.SelectedIndex;
359                         int nameConditionType2 = this.combNameMenu2.SelectedIndex;
360                         string ageText1 = this.txtAgeMenu1.Text.Trim();
361                         string ageText2 = this.txtAgeMenu2.Text.Trim();
362                         int ageConditionType1 = this.combAgeMenu1.SelectedIndex;
363                         int ageConditionType2 = this.combAgeMenu2.SelectedIndex;
364                         bool? isNoAnd = this.rbNoAnd.IsChecked;
365                         bool? isNoOr = this.rbNoOr.IsChecked;
366                         bool? isNameAnd = this.rbNameAnd.IsChecked;
367                         bool? isNameOr = this.rbNameOr.IsChecked;
368                         bool? isAgeAnd = this.rbAgeAnd.IsChecked;
369                         bool? isAgeOr = this.rbAgeOr.IsChecked;
370                         bool flagNo = true;
371                         bool flagName = true;
372                         bool flagAge = true;
373                         flagNo = CheckConditions(noConditionType1, noConditionType2, t.No, noText1, noText2, isNoAnd, isNoOr);
374                         flagName = CheckConditions(nameConditionType1, nameConditionType2, t.Name, nameText1, nameText2, isNameAnd, isNameOr);
375                         flagAge = CheckConditions(ageConditionType1, ageConditionType2, t.Age.ToString(), ageText1, ageText2, isAgeAnd, isAgeOr);
376                         flag = flag && flagNo && flagName && flagAge;
377                     }
378                     return flag;
379                 });
380             }
381         }
382 
383         private bool CheckConditions(int conditionIndex1, int conditionIndex2, string source, string condition1, string condition2, bool? isAnd, bool? isOr)
384         {
385             bool flag = true;
386             bool flag1 = true;
387             bool flag2 = true;
388             if (!string.IsNullOrEmpty(condition1) && !string.IsNullOrWhiteSpace(condition1) && conditionIndex1 != -1)
389             {
390                 flag1 = CheckCondition(conditionIndex1, source, condition1);
391             }
392             if (!string.IsNullOrEmpty(condition2) && !string.IsNullOrWhiteSpace(condition2) && conditionIndex2 != -1)
393             {
394                 flag2 = CheckCondition(conditionIndex2, source, condition2);
395             }
396             if (isAnd == true)
397             {
398                 flag = flag1 && flag2;
399             }
400             if (isOr == true)
401             {
402                 flag = flag1 || flag2;
403             }
404             return flag;
405         }
406 
407         private bool CheckCondition(int condtionIndex, string source, string condition)
408         {
409             bool flag = true;
410             if (condtionIndex == 0)
411             {
412                 flag = flag && source == condition;
413             }
414             if (condtionIndex == 1)
415             {
416                 flag = flag && source != condition;
417             }
418             if (condtionIndex == 2)
419             {
420                 flag = flag && source.StartsWith(condition);
421             }
422             if (condtionIndex == 3)
423             {
424                 flag = flag && source.EndsWith(condition);
425             }
426             if (condtionIndex == 4)
427             {
428                 flag = flag && source.Contains(condition);
429             }
430             if (condtionIndex == 5)
431             {
432                 flag = flag && !source.Contains(condition);
433             }
434             return flag;
435         }
436     }
437 }

学号,姓名,年龄三列过滤列表绑定内容模型一致,为FilterInfo,如下所示:

using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoDataGrid
{
    public class FilterInfo : ObservableObject
    {
        private string filterText;

        public string FilterText
        {
            get { return filterText; }
            set { SetProperty(ref filterText, value); }
        }

        private bool isChecked;

        public bool IsChecked
        {
            get { return isChecked; }
            set { SetProperty(ref isChecked, value); }
        }
    }
}

不足与思考

上述筛选实现方式,并非唯一实现,也并非最优实现,同样存在许多可以优化的地方。

在本示例中,存在许多冗余代码,如视图页面,对三列的弹出窗口,内容虽然相对统一,只是列名和绑定内容不同而已,却堆积了三大段代码,是否可以从控件模块或者数据模板的角度,进行简化呢?

筛选功能实现上,同样存在许多冗余代码,是否可以进行简化呢?以上是我们需要思考的地方,希望可以集思广益,共同学习,一起进步。

posted @ 2023-02-28 16:41  老码识途呀  阅读(845)  评论(0编辑  收藏  举报