分享

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

 老码识途 2023-10-10 发布于广东

在默认情况下,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】源码

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

<Window x:Class="DemoDataGrid.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"        xmlns:mc="http://schemas./markup-compatibility/2006"        xmlns:local="clr-namespace:DemoDataGrid"        mc:Ignorable="d"        Title="DataGrid筛选示例" Height="650" Width="800">    <Window.Resources>        <ResourceDictionary>            <CollectionViewSource x:Key="ItemsSource" Source="{Binding Path=Students}"></CollectionViewSource>            <CollectionViewSource x:Key="Names" Source="{Binding Path=Names}"></CollectionViewSource>            <CollectionViewSource x:Key="Nos" Source="{Binding Path=Nos}"></CollectionViewSource>            <CollectionViewSource x:Key="Ages" Source="{Binding Path=Ages}"></CollectionViewSource>            <Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">                <Setter Property="ScrollViewer.CanContentScroll" Value="True"></Setter>                <Setter Property="Template">                    <Setter.Value>                        <ControlTemplate TargetType="{x:Type ListBox}">                            <ScrollViewer x:Name="ScrollViewer" CanContentScroll="True">                                <ItemsPresenter></ItemsPresenter>                            </ScrollViewer>                        </ControlTemplate>                    </Setter.Value>                </Setter></Style>            <Geometry x:Key="Icon_Filter">                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            </Geometry>            <ContextMenu x:Key="queryConditionMenu" MouseLeave="ContextMenu_MouseLeave" MenuItem.Click="ContextMenu_Click">                <MenuItem FontSize="12" Header="等于" Tag="Equal"></MenuItem>                <MenuItem FontSize="12" Header="不等于"  Tag="NotEqual"></MenuItem>                <MenuItem FontSize="12" Header="开头"  Tag="Begin"></MenuItem>                <MenuItem FontSize="12" Header="结尾"  Tag="End"></MenuItem>                <MenuItem FontSize="12" Header="包含"  Tag="In"></MenuItem>                <MenuItem FontSize="12" Header="不包含"  Tag="NotIn"></MenuItem>            </ContextMenu>        </ResourceDictionary>
</Window.Resources> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="20"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <DataGrid Grid.Row="1" x:Name="dgStudents" ItemsSource="{Binding Source={StaticResource ItemsSource} }" AutoGenerateColumns="False" CanUserReorderColumns="True" CanUserDeleteRows="False" CanUserAddRows="False" HeadersVisibility="Column" CanUserSortColumns="True" VirtualizingPanel.VirtualizationMode="Recycling" EnableColumnVirtualization="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True" GridLinesVisibility="All" RowHeight="25" SelectionUnit="FullRow" SelectionMode="Single" IsReadOnly="True" FontSize="12" SelectedIndex="{Binding SelectTaskItemIndex}" SelectedItem="{Binding SelectTaskItem}" CanUserResizeColumns="True"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Id}" Header="Id" Width="*">
</DataGridTextColumn> <DataGridTextColumn Binding="{Binding No}" Width="*"> <DataGridTextColumn.Header> <TextBlock Text="学号" FontWeight="Regular" MouseRightButtonDown="TextBlock_MouseRightButtonDown" Tag="No"></TextBlock> </DataGridTextColumn.Header> </DataGridTextColumn> <DataGridTextColumn Binding="{Binding Name}" Width="*"> <DataGridTextColumn.Header> <TextBlock Text="姓名" FontWeight="Regular" MouseRightButtonDown="TextBlock_MouseRightButtonDown" Tag="Name"></TextBlock> </DataGridTextColumn.Header> </DataGridTextColumn> <DataGridTextColumn Binding="{Binding Age}" Width="*"> <DataGridTextColumn.Header> <TextBlock Text="年龄" FontWeight="Regular" MouseRightButtonDown="TextBlock_MouseRightButtonDown" Tag="Age"></TextBlock> </DataGridTextColumn.Header> </DataGridTextColumn> </DataGrid.Columns> </DataGrid> <Popup x:Name="popupNo" Width="135" MaxHeight="500" Height="Auto" PopupAnimation="Slide" AllowsTransparency="False" MouseLeave="popup_MouseLeave"> <Border BorderBrush="LightBlue" BorderThickness="1" Padding="5" Background="AliceBlue"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal"> <TextBox Height="25" x:Name="txtNo" MinWidth="60" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Tag="No" TextChanged="TextBox_TextChanged"></TextBox> <Button x:Name="btnNoFilter" Tag="No" ClickMode="Press" Click="ButtonFilter_Click" ContextMenu="{StaticResource queryConditionMenu}"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <Path Data="{StaticResource Icon_Filter}" Stroke="Gray" StrokeThickness="1" Height="12" Width="12" Stretch="Fill"></Path> <TextBlock Margin="2,0" Text="筛选" FontSize="12"></TextBlock> </StackPanel> </Button> </StackPanel> <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}"> <ListBox.ItemTemplate> <DataTemplate> <CheckBox Content="{Binding FilterText}" IsChecked="{Binding IsChecked}"></CheckBox> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
<Button Tag="No" Content="取消" Width="60" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="0" Click="btnCancel_Click"></Button> <Button Content="确定" Width="60" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Click="btnOk_Click"></Button> </Grid> </Border> </Popup> <Popup x:Name="popupName" Width="135" MaxHeight="500" Height="Auto" PopupAnimation="Slide" AllowsTransparency="False" MouseLeave="popup_MouseLeave"> <Border BorderBrush="LightBlue" BorderThickness="1" Padding="5" Background="AliceBlue"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal"> <TextBox Height="25" x:Name="txtName" MinWidth="60" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Tag="Name" TextChanged="TextBox_TextChanged"></TextBox> <Button x:Name="btnNameFilter" Tag="Name" Click="ButtonFilter_Click" ContextMenu="{StaticResource queryConditionMenu}"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <Path Data="{StaticResource Icon_Filter}" Stroke="Gray" StrokeThickness="1" Height="12" Width="12" Stretch="Fill"></Path> <TextBlock Margin="2,0" Text="筛选" FontSize="12"></TextBlock> </StackPanel> </Button> </StackPanel> <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}"> <ListBox.ItemTemplate> <DataTemplate> <CheckBox Content="{Binding FilterText}" IsChecked="{Binding IsChecked}"></CheckBox> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
<Button Tag="No" Content="取消" Width="60" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="0" Click="btnCancel_Click"></Button> <Button Content="确定" Width="60" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Click="btnOk_Click"></Button> </Grid> </Border> </Popup> <Popup x:Name="popupAge" Width="135" MaxHeight="500" Height="Auto" PopupAnimation="Slide" AllowsTransparency="False" MouseLeave="popup_MouseLeave"> <Border BorderBrush="LightBlue" BorderThickness="1" Padding="5" Background="AliceBlue"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal"> <TextBox Height="25" x:Name="txtAge" MinWidth="60" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Tag="Age" TextChanged="TextBox_TextChanged"></TextBox> <Button x:Name="btnAgeFilter" Tag="Age" Click="ButtonFilter_Click" ContextMenu="{StaticResource queryConditionMenu}"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <Path Data="{StaticResource Icon_Filter}" Stroke="Gray" StrokeThickness="1" Height="12" Width="12" Stretch="Fill"></Path> <TextBlock Margin="2,0" Text="筛选" FontSize="12"></TextBlock> </StackPanel> </Button> </StackPanel> <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}"> <ListBox.ItemTemplate> <DataTemplate> <CheckBox Content="{Binding FilterText}" IsChecked="{Binding IsChecked}"></CheckBox> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
<Button Tag="No" Content="取消" Width="60" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="0" Click="btnCancel_Click"></Button> <Button Content="确定" Width="60" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="1" Click="btnOk_Click"></Button> </Grid> </Border> </Popup>
<Popup x:Name="popupNoMenu" Width="300" MaxHeight="500" Height="200" PopupAnimation="Slide" AllowsTransparency="False" Tag=""> <Border BorderThickness="1" BorderBrush="Beige" Padding="15" Background="AliceBlue"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"> <TextBlock Text="学号" VerticalAlignment="Center"></TextBlock> <ComboBox x:Name="combNoMenu1" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center"> <ComboBoxItem Content="等于" ></ComboBoxItem> <ComboBoxItem Content="不等于"></ComboBoxItem> <ComboBoxItem Content="开头"></ComboBoxItem> <ComboBoxItem Content="结尾"></ComboBoxItem> <ComboBoxItem Content="包含"></ComboBoxItem> <ComboBoxItem Content="不包含"></ComboBoxItem> </ComboBox> <TextBox x:Name="txtNoMenu1" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"> <RadioButton x:Name="rbNoAnd" Content="与" IsChecked="True" Margin="4" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton> <RadioButton x:Name="rbNoOr" Content="或" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"> <TextBlock Text="学号" VerticalAlignment="Center"></TextBlock> <ComboBox x:Name="combNoMenu2" Height="28" Margin="4" Width="100" VerticalContentAlignment="Center"> <ComboBoxItem Content="等于" ></ComboBoxItem> <ComboBoxItem Content="不等于"></ComboBoxItem> <ComboBoxItem Content="开头"></ComboBoxItem> <ComboBoxItem Content="结尾"></ComboBoxItem> <ComboBoxItem Content="包含"></ComboBoxItem> <ComboBoxItem Content="不包含"></ComboBoxItem> </ComboBox> <TextBox x:Name="txtNoMenu2" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox> </StackPanel> <Button Tag="No" Content="取消" Width="100" Height="28" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="0" Click="btnCancelFilter_Click"></Button> <Button Tag="No" Content="确定" Width="100" Height="28" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1" Click="btnOkFilter_Click"></Button> </Grid> </Border> </Popup> <Popup x:Name="popupNameMenu" Width="300" MaxHeight="500" Height="200" PopupAnimation="Slide" AllowsTransparency="False" Tag=""> <Border BorderThickness="1" BorderBrush="Beige" Padding="15" Background="AliceBlue"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0"> <TextBlock Text="姓名" VerticalAlignment="Center"></TextBlock> <ComboBox x:Name="combNameMenu1" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center"> <ComboBoxItem Content="等于" ></ComboBoxItem> <ComboBoxItem Content="不等于"></ComboBoxItem> <ComboBoxItem Content="开头"></ComboBoxItem> <ComboBoxItem Content="结尾"></ComboBoxItem> <ComboBoxItem Content="包含"></ComboBoxItem> <ComboBoxItem Content="不包含"></ComboBoxItem> </ComboBox> <TextBox x:Name="txtNameMenu1" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0"> <RadioButton x:Name="rbNameAnd" Content="与" IsChecked="True" Margin="4" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton> <RadioButton x:Name="rbNameOr" Content="或" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0"> <TextBlock Text="姓名" VerticalAlignment="Center"></TextBlock> <ComboBox x:Name="combNameMenu2" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center"> <ComboBoxItem Content="等于" ></ComboBoxItem> <ComboBoxItem Content="不等于"></ComboBoxItem> <ComboBoxItem Content="开头"></ComboBoxItem> <ComboBoxItem Content="结尾"></ComboBoxItem> <ComboBoxItem Content="包含"></ComboBoxItem> <ComboBoxItem Content="不包含"></ComboBoxItem> </ComboBox> <TextBox x:Name="txtNameMenu2" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox> </StackPanel> <Button Tag="Name" Content="取消" Width="100" Height="28" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="0" Click="btnCancelFilter_Click"></Button> <Button Tag="Name" Content="确定" Width="100" Height="28" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1" Click="btnOkFilter_Click"></Button> </Grid> </Border> </Popup> <Popup x:Name="popupAgeMenu" Width="300" MaxHeight="500" Height="200" PopupAnimation="Slide" AllowsTransparency="False" Tag=""> <Border BorderThickness="1" BorderBrush="Beige" Padding="15" Background="AliceBlue"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0"> <TextBlock Text="年龄" VerticalAlignment="Center"></TextBlock> <ComboBox x:Name="combAgeMenu1" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center"> <ComboBoxItem Content="等于" ></ComboBoxItem> <ComboBoxItem Content="不等于"></ComboBoxItem> <ComboBoxItem Content="开头"></ComboBoxItem> <ComboBoxItem Content="结尾"></ComboBoxItem> <ComboBoxItem Content="包含"></ComboBoxItem> <ComboBoxItem Content="不包含"></ComboBoxItem> </ComboBox> <TextBox x:Name="txtAgeMenu1" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0"> <RadioButton x:Name="rbAgeAnd" Content="与" IsChecked="True" Margin="4" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton> <RadioButton x:Name="rbAgeOr" Content="或" Background="AliceBlue" VerticalAlignment="Center" VerticalContentAlignment="Center"></RadioButton> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0"> <TextBlock Text="年龄" VerticalAlignment="Center"></TextBlock> <ComboBox x:Name="combAgeMenu2" Height="28" Width="100" Margin="4" VerticalContentAlignment="Center"> <ComboBoxItem Content="等于" ></ComboBoxItem> <ComboBoxItem Content="不等于"></ComboBoxItem> <ComboBoxItem Content="开头"></ComboBoxItem> <ComboBoxItem Content="结尾"></ComboBoxItem> <ComboBoxItem Content="包含"></ComboBoxItem> <ComboBoxItem Content="不包含"></ComboBoxItem> </ComboBox> <TextBox x:Name="txtAgeMenu2" Height="28" Width="100" VerticalContentAlignment="Center"></TextBox> </StackPanel> <Button Tag="Age" Content="取消" Width="100" Height="28" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="0" Click="btnCancelFilter_Click"></Button> <Button Tag="Age" Content="确定" Width="100" Height="28" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1" Click="btnOkFilter_Click"></Button> </Grid> </Border> </Popup> </Grid></Window>

业务逻辑【MainWindowViewModel】

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

using CommunityToolkit.Mvvm.ComponentModel;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;
namespace DemoDataGrid{ public class MainWindowViewModel:ObservableObject { #region 属性及构造函数
private List<Student> students;
public List<Student> Students { get { return students; } set { SetProperty(ref students, value); } }
private List<FilterInfo> names;
public List<FilterInfo> Names { get { return names; } set { SetProperty(ref names, value); } }
private List<FilterInfo> nos;
public List<FilterInfo> Nos { get { return nos; } set {SetProperty(ref nos , value); } }
private List<FilterInfo> ages;
public List<FilterInfo> Ages { get { return ages; } set {SetProperty(ref ages , value); } }


public MainWindowViewModel() { this.Students= new List<Student>(); for (int i = 0; i < 20; i++) { this.Students.Add(new Student() { Id = i, Name = $"张{i}牛", Age = (i % 10) + 10, No = i.ToString().PadLeft(4, '0'), }); } this.Nos= new List<FilterInfo>(); this.Names= new List<FilterInfo>(); this.Ages= new List<FilterInfo>(); this.Students.ForEach(s => { this.Nos.Add(new FilterInfo() { FilterText=s.No,IsChecked=false }); this.Names.Add(new FilterInfo() { FilterText = s.Name, IsChecked = false }); this.Ages.Add(new FilterInfo() { FilterText = s.Age.ToString(), IsChecked = false }); }); this.Ages=this.Ages.Distinct().ToList();//去重 }
#endregion

}}

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

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

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;
namespace DemoDataGrid{ /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private MainWindowViewModel viewModel;
public MainWindow() { InitializeComponent(); viewModel = new MainWindowViewModel(); this.DataContext = viewModel; }

#region 筛选
private void TextBlock_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { if (sender != null && sender is TextBlock) { var textBlock = sender as TextBlock; var tag = textBlock.Tag.ToString(); var pop = this.FindName($"popup{tag}"); if (pop != null) { var popup = pop as System.Windows.Controls.Primitives.Popup; if (popup != null) { popup.IsOpen = true; popup.PlacementTarget = textBlock; popup.Placement = System.Windows.Controls.Primitives.PlacementMode.RelativePoint; popup.VerticalOffset = 10; popup.HorizontalOffset = 10; } } } }
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { TextBox textBox = e.OriginalSource as TextBox; var tag = textBox.Tag;//条件 var text = textBox.Text; if (tag != null) { if (tag.ToString() == "No") { Filter(this.lbNos.ItemsSource, this.txtNo.Text); } if (tag.ToString() == "Name") { Filter(this.lbNames.ItemsSource, this.txtName.Text); } if (tag.ToString() == "Age") { Filter(this.lbAges.ItemsSource, this.txtAge.Text); } }
}
private void Filter(object source, string filter) { var cv = CollectionViewSource.GetDefaultView(source); if (cv != null && cv.CanFilter) { cv.Filter = new Predicate<object>((obj) => { bool flag = true; var t = obj as FilterInfo; if (t != null) { flag = t.FilterText.Contains(filter); } return flag; }); } }
private void popup_MouseLeave(object sender, MouseEventArgs e) { var popup = e.OriginalSource as System.Windows.Controls.Primitives.Popup; var showContext = (this.FindResource("queryConditionMenu") as ContextMenu)?.IsOpen; if (popup != null && showContext==false) { popup.IsOpen = false; } }
private void btnCancel_Click(object sender, RoutedEventArgs e) { var btn = e.OriginalSource as Button; if (btn != null) { var tag = btn.Tag; if (tag.ToString() == "No") { ClearFilter(this.txtNo, this.viewModel.Nos); } if (tag.ToString() == "Name") { ClearFilter(this.txtName, this.viewModel.Names);
} if (tag.ToString() == "Age") { ClearFilter(this.txtAge, this.viewModel.Ages); } FilterTask();//清除以后,重新刷新 } }
private void ClearFilter(TextBox textBox, List<FilterInfo> collection) { textBox.Clear(); foreach (var f in collection) { f.IsChecked = false; } }
private void btnOk_Click(object sender, RoutedEventArgs e) { // FilterTask(); }

private void FilterTask() { var cv = CollectionViewSource.GetDefaultView(this.dgStudents.ItemsSource); if (cv != null && cv.CanFilter) { cv.Filter = new Predicate<object>((obj) => { bool flag = true; var t = obj as Student; if (t != null) { var nos = this.viewModel.Nos.Where(r => r.IsChecked == true).ToList(); var names = this.viewModel.Names.Where(r => r.IsChecked == true).ToList(); var ages = this.viewModel.Ages.Where(r => r.IsChecked == true).ToList(); if (nos.Count() > 0) { flag = flag && nos.Select(r => r.FilterText).Contains(t.No); } if (names.Count() > 0) { flag = flag && names.Select(r => r.FilterText).Contains(t.Name); } if (ages.Count() > 0) { flag = flag && ages.Select(r => r.FilterText).Contains(t.Age.ToString()); } } return flag; }); } }
#endregion
private List<string> condition = new List<string>() { "Equal", "NotEqual", "Begin", "End", "In", "NotIn" };
private void ButtonFilter_Click(object sender, RoutedEventArgs e) { var btn = e.OriginalSource as Button; if (btn != null) { var tag = btn.Tag; var popup = this.FindName($"popup{tag}") as System.Windows.Controls.Primitives.Popup; if (popup != null) { popup.IsOpen = true; } if (btn.ContextMenu.IsOpen) { btn.ContextMenu.IsOpen = false; } else { btn.ContextMenu.Tag = tag; btn.ContextMenu.Width = 100; btn.ContextMenu.Height = 150; btn.ContextMenu.IsOpen = true; btn.ContextMenu.PlacementTarget = btn; btn.ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom; } } }
private void ContextMenu_MouseLeave(object sender, MouseEventArgs e) { var menu = e.OriginalSource as ContextMenu; if (menu != null) { menu.IsOpen = false; } }
private void ContextMenu_Click(object sender, RoutedEventArgs e) { var contextMenu = sender as ContextMenu; if (contextMenu == null) { return; } var menuItem = e.OriginalSource as MenuItem; if (menuItem == null) { return; } var tag1 = contextMenu.Tag.ToString();//点击的哪一个按钮 var tag2 = menuItem.Tag.ToString();//点击的是哪一个菜单 var pop = this.FindName($"popup{tag1}Menu"); var comb = this.FindName($"comb{tag1}Menu1"); HideParentPopup(tag1);//隐藏父Popup if (comb != null) { var combMenu = comb as ComboBox; combMenu.SelectedIndex = condition.IndexOf(tag2); } if (pop != null) { var popup = pop as System.Windows.Controls.Primitives.Popup; popup.IsOpen = true; popup.PlacementTarget = dgStudents; popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Center; } }
private void btnCancelFilter_Click(object sender, RoutedEventArgs e) { if (sender == null) { return; } var btn = sender as System.Windows.Controls.Button; if (btn != null) { var tag = btn.Tag.ToString(); HidePopupMenu(tag);//隐藏Popup控件 if (tag == "No") { ClearMenuFilter(this.txtNoMenu1, this.txtNoMenu2); } if (tag == "Name") { ClearMenuFilter(this.txtNameMenu1, this.txtNameMenu2); } if (tag == "Age") { ClearMenuFilter(this.txtAgeMenu1, this.txtAgeMenu2); } FilterMenuTask(); } }

private void btnOkFilter_Click(object sender, RoutedEventArgs e) { if (sender == null) { return; } var btn = sender as System.Windows.Controls.Button; if (btn != null) { var tag = btn.Tag.ToString(); HidePopupMenu(tag); FilterMenuTask(); } }
/// <summary> /// 隐藏父Popup /// </summary> /// <param name="tag"></param> private void HideParentPopup(string tag) { //点击右键菜单时,隐藏父Popup控件 if (tag == "No") { this.popupNo.IsOpen = false; } if (tag == "Name") { this.popupName.IsOpen = false; } if (tag == "Age") { this.popupAge.IsOpen = false; } }
/// <summary> /// 隐藏菜单弹出的Popup控件 /// </summary> /// <param name="tag"></param> private void HidePopupMenu(string tag) { var pop = this.FindName($"popup{tag}Menu"); if (pop != null) { var popup = pop as System.Windows.Controls.Primitives.Popup; popup.IsOpen = false; } }
/// <summary> /// 清除菜单中的文本过滤条件 /// </summary> /// <param name="txt1"></param> /// <param name="txt2"></param> private void ClearMenuFilter(TextBox txt1, TextBox txt2) { txt1?.Clear(); txt2?.Clear(); }
/// <summary> /// /// </summary> private void FilterMenuTask() { var cv = CollectionViewSource.GetDefaultView(this.dgStudents.ItemsSource); if (cv != null && cv.CanFilter) { cv.Filter = new Predicate<object>((obj) => { bool flag = true; var t = obj as Student; if (t != null) { string noText1 = this.txtNoMenu1.Text.Trim(); string noText2 = this.txtNoMenu2.Text.Trim(); int noConditionType1 = this.combNoMenu1.SelectedIndex; int noConditionType2 = this.combNoMenu2.SelectedIndex; string nameText1 = this.txtNameMenu1.Text.Trim(); string nameText2 = this.txtNameMenu2.Text.Trim(); int nameConditionType1 = this.combNameMenu1.SelectedIndex; int nameConditionType2 = this.combNameMenu2.SelectedIndex; string ageText1 = this.txtAgeMenu1.Text.Trim(); string ageText2 = this.txtAgeMenu2.Text.Trim(); int ageConditionType1 = this.combAgeMenu1.SelectedIndex; int ageConditionType2 = this.combAgeMenu2.SelectedIndex; bool? isNoAnd = this.rbNoAnd.IsChecked; bool? isNoOr = this.rbNoOr.IsChecked; bool? isNameAnd = this.rbNameAnd.IsChecked; bool? isNameOr = this.rbNameOr.IsChecked; bool? isAgeAnd = this.rbAgeAnd.IsChecked; bool? isAgeOr = this.rbAgeOr.IsChecked; bool flagNo = true; bool flagName = true; bool flagAge = true; flagNo = CheckConditions(noConditionType1, noConditionType2, t.No, noText1, noText2, isNoAnd, isNoOr); flagName = CheckConditions(nameConditionType1, nameConditionType2, t.Name, nameText1, nameText2, isNameAnd, isNameOr); flagAge = CheckConditions(ageConditionType1, ageConditionType2, t.Age.ToString(), ageText1, ageText2, isAgeAnd, isAgeOr); flag = flag && flagNo && flagName && flagAge; } return flag; }); } }
private bool CheckConditions(int conditionIndex1, int conditionIndex2, string source, string condition1, string condition2, bool? isAnd, bool? isOr) { bool flag = true; bool flag1 = true; bool flag2 = true; if (!string.IsNullOrEmpty(condition1) && !string.IsNullOrWhiteSpace(condition1) && conditionIndex1 != -1) { flag1 = CheckCondition(conditionIndex1, source, condition1); } if (!string.IsNullOrEmpty(condition2) && !string.IsNullOrWhiteSpace(condition2) && conditionIndex2 != -1) { flag2 = CheckCondition(conditionIndex2, source, condition2); } if (isAnd == true) { flag = flag1 && flag2; } if (isOr == true) { flag = flag1 || flag2; } return flag; }
private bool CheckCondition(int condtionIndex, string source, string condition) { bool flag = true; if (condtionIndex == 0) { flag = flag && source == condition; } if (condtionIndex == 1) { flag = flag && source != condition; } if (condtionIndex == 2) { flag = flag && source.StartsWith(condition); } if (condtionIndex == 3) { flag = flag && source.EndsWith(condition); } if (condtionIndex == 4) { flag = flag && source.Contains(condition); } if (condtionIndex == 5) { flag = flag && !source.Contains(condition); } return flag; } }}

学号,姓名,年龄三列过滤列表绑定内容模型一致,为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); } } }}


不足与思考


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

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

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


学习编程,从关注【老码识途】开始!

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多