如何实现WPF C#中DataGrid模板列的子集合过滤功能?
WPF嵌套DataGrid子表列过滤实现方案
我在WPF C#中实现了一个员工信息DataGrid,主表为Employee,关联Contacts、Languages、Duties三个子表。通过DataGridTemplateColumn在主DataGrid内嵌套子DataGrid展示子表数据,显示效果符合预期,但针对Contacts这类模板列的过滤功能无法生效,请问该如何实现?
现有实现代码
XAML代码
<Window.Resources> <CollectionViewSource x:Key="CvsKey"> <CollectionViewSource.GroupDescriptions> <PropertyGroupDescription PropertyName="Employee"/> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources> <Grid> <DataGrid AutoGenerateColumns="False" x:Name="dataGrid1" ItemsSource="{Binding Employees}" IsReadOnly="True" RowDetailsVisibilityMode="Collapsed" CanUserAddRows="True"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding EmployeeID}" Header="EmployeeID"/> <DataGridTextColumn Binding="{Binding FirstName}" Header="FirstName"/> <DataGridTextColumn Binding="{Binding LastName}" Header="LastName"/> <DataGridTextColumn Binding="{Binding Age}" Header="Age"/> <DataGridTextColumn Binding="{Binding Gender}" Header="Gender"/> <DataGridTextColumn Binding="{Binding Email}" Header="Email"/> <DataGridTemplateColumn Header="Contacts" Width="75"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DataGrid ItemsSource="{Binding Contacts}" IsReadOnly="True" AutoGenerateColumns="False" HeadersVisibility="None"> <DataGrid.Columns> <DataGridTemplateColumn Width="*"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Telephone}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Header="Duties" Width="75"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DataGrid ItemsSource="{Binding Duties}" IsReadOnly="True" AutoGenerateColumns="False" HeadersVisibility="None"> <DataGrid.Columns> <DataGridTemplateColumn Width="*"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Duty}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Header="Languages" Width="75"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DataGrid ItemsSource="{Binding Languages}" IsReadOnly="True" AutoGenerateColumns="False" HeadersVisibility="None"> <DataGrid.Columns> <DataGridTemplateColumn Width="*"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding LanguageName}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </Grid>
后台代码
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Windows; using System.Windows.Input; namespace WpfApp1 { public partial class MainWindow : Window { private EmployeeViewModel _viewModel; public MainWindow() { InitializeComponent(); _viewModel = new EmployeeViewModel(); DataContext = _viewModel; } } public class EmployeeViewModel { private readonly EmployeeDbContext _context; public EmployeeViewModel() { using (var context = new EmployeeDbContext()) { Employees = context.Employees .Include(e => e.Duties) .Include(e => e.Contacts) .Include(e => e.Languages) .ToList(); } } public List<Employee> Employees { get; set; } = new List<Employee>(); public void SaveChanges() { _context.SaveChanges(); } } public class EmployeeDbContext : DbContext { public DbSet<Employee> Employees { get; set; } public DbSet<EmployeesContact> EmployeesContacts { get; set; } public DbSet<EmployeesDuty> EmployeesDuties { get; set; } public DbSet<EmployeesLanguage> EmployeesLanguages { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(localdb)\ProjectModels;Initial Catalog=EmployeesDB;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False"); } } public class Employee { public int EmployeeID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Gender { get; set; } public string Email { get; set; } public ObservableCollection<EmployeesContact> Contacts { get; set; } public ObservableCollection<EmployeesDuty> Duties { get; set; } public ObservableCollection<EmployeesLanguage> Languages { get; set; } } public class EmployeesContact { [Key] public int TelephoneID { get; set; } public int EmployeeID { get; set; } public string Telephone { get; set; } } public class EmployeesDuty { public int EmployeeID { get; set; } [Key] public int EmployeeDutyID { get; set; } public string Duty { get; set; } } public class EmployeesLanguage { public int EmployeeID { get; set; } [Key] public int EmployeeLanguageID { get; set; } public string LanguageName { get; set; } public string TalkingAbility { get; set; } public string ReadingAbility { get; set; } public string WritingAbility { get; set; } } }
解决方案
问题根源
主DataGrid的默认过滤逻辑仅作用于直接绑定的实体属性,无法自动穿透到嵌套子DataGrid绑定的集合属性。要实现子表列的过滤,需要手动处理两种场景:过滤主表仅保留包含符合条件子项的记录,或单独过滤每个子DataGrid的数据源。
场景一:过滤主表(显示包含符合条件子项的员工)
这种方式会隐藏所有不包含匹配子项的员工记录,步骤如下:
- 修改ViewModel实现INotifyPropertyChanged
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Data; public class EmployeeViewModel : INotifyPropertyChanged { private readonly EmployeeDbContext _context; private string _filterText; private ICollectionView _employeesView; public event PropertyChangedEventHandler PropertyChanged; public EmployeeViewModel() { using (var context = new EmployeeDbContext()) { var employees = context.Employees .Include(e => e.Duties) .Include(e => e.Contacts) .Include(e => e.Languages) .ToList(); EmployeesView = CollectionViewSource.GetDefaultView(employees); EmployeesView.Filter = FilterEmployees; } } public ICollectionView EmployeesView { get; set; } public string FilterText { get => _filterText; set { _filterText = value; OnPropertyChanged(); EmployeesView.Refresh(); } } private bool FilterEmployees(object item) { if (string.IsNullOrWhiteSpace(FilterText)) return true; var employee = item as Employee; if (employee == null) return false; // 检查Contacts是否匹配 if (employee.Contacts?.Any(c => c.Telephone.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) == true) return true; // 检查Duties是否匹配 if (employee.Duties?.Any(d => d.Duty.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) == true) return true; // 检查Languages是否匹配 if (employee.Languages?.Any(l => l.LanguageName.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) == true) return true; // 同时保留主表属性的过滤 return employee.FirstName.Contains(FilterText, StringComparison.OrdinalIgnoreCase) || employee.LastName.Contains(FilterText, StringComparison.OrdinalIgnoreCase) || employee.Email.Contains(FilterText, StringComparison.OrdinalIgnoreCase); } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 修改XAML添加过滤输入框并更新主DataGrid绑定
在Grid顶部添加过滤输入框:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBox Grid.Row="0" Margin="10" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" PlaceholderText="输入手机号、职责或语言进行过滤"/> <DataGrid Grid.Row="1" AutoGenerateColumns="False" x:Name="dataGrid1" ItemsSource="{Binding EmployeesView}" IsReadOnly="True" RowDetailsVisibilityMode="Collapsed" CanUserAddRows="True"> <!-- 原列定义保持不变 --> </DataGrid> </Grid>
场景二:单独过滤子DataGrid(仅过滤子表数据,主表记录全部保留)
这种方式会保留所有员工,但仅显示每个员工子表中符合条件的项,步骤如下:
- 为子集合创建可过滤的视图包装类
public class FilterableCollectionViewModel<T> : INotifyPropertyChanged { private string _filterText; private ICollectionView _collectionView; public event PropertyChangedEventHandler PropertyChanged; public FilterableCollectionViewModel(ObservableCollection<T> collection) { CollectionView = CollectionViewSource.GetDefaultView(collection); } public ICollectionView CollectionView { get; } public string FilterText { get => _filterText; set { _filterText = value; OnPropertyChanged(); CollectionView.Refresh(); } } public void SetFilter(Func<T, bool> filterFunc) { CollectionView.Filter = item => { if (string.IsNullOrWhiteSpace(FilterText)) return true; var model = item as T; return model != null && filterFunc(model); }; } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 修改Employee类添加子集合的过滤视图属性
public class Employee : INotifyPropertyChanged { public int EmployeeID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Gender { get; set; } public string Email { get; set; } private ObservableCollection<EmployeesContact> _contacts; public ObservableCollection<EmployeesContact> Contacts { get => _contacts; set { _contacts = value; ContactsFilterVM = new FilterableCollectionViewModel<EmployeesContact>(value); ContactsFilterVM.SetFilter(c => c.Telephone.Contains(ContactsFilterVM.FilterText, StringComparison.OrdinalIgnoreCase)); } } public FilterableCollectionViewModel<EmployeesContact> ContactsFilterVM { get; private set; } // Duties和Languages做同样的处理 private ObservableCollection<EmployeesDuty> _duties; public ObservableCollection<EmployeesDuty> Duties { get => _duties; set { _duties = value; DutiesFilterVM = new FilterableCollectionViewModel<EmployeesDuty>(value); DutiesFilterVM.SetFilter(d => d.Duty.Contains(DutiesFilterVM.FilterText, StringComparison.OrdinalIgnoreCase)); } } public FilterableCollectionViewModel<EmployeesDuty> DutiesFilterVM { get; private set; } private ObservableCollection<EmployeesLanguage> _languages; public ObservableCollection<EmployeesLanguage> Languages { get => _languages; set { _languages = value; LanguagesFilterVM = new FilterableCollectionViewModel<EmployeesLanguage>(value); LanguagesFilterVM.SetFilter(l => l.LanguageName.Contains(LanguagesFilterVM.FilterText, StringComparison.OrdinalIgnoreCase)); } } public FilterableCollectionViewModel<EmployeesLanguage> LanguagesFilterVM { get; private set; } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 修改XAML子DataGrid绑定并添加子过滤输入框
以Contacts列为例,修改模板列添加过滤输入框:
<DataGridTemplateColumn Header="Contacts" Width="150"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel> <TextBox Margin="2" Text="{Binding ContactsFilterVM.FilterText, UpdateSourceTrigger=PropertyChanged}" PlaceholderText="过滤手机号"/> <DataGrid ItemsSource="{Binding ContactsFilterVM.CollectionView}" IsReadOnly="True" AutoGenerateColumns="False" HeadersVisibility="None" RowHeight="20"> <DataGrid.Columns> <DataGridTemplateColumn Width="*"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Telephone}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn>
Duties和Languages列做同样的修改即可。
内容的提问来源于stack exchange,提问作者Meshka
相关产品推荐
相关产品推荐

