如何为DataGridColumnHeader模板绑定DataTrigger实现自定义排序筛选
Absolutely, this approach is totally feasible and aligns perfectly with MVVM principles—let's break down how to implement it step by step, ditching the manual SorterThumb updates entirely.
1. Create Multi-Value Converters
We'll build converters that take two key inputs: your Sorters/Filters collection and the column's SortMemberPath, then return the relevant property (sort direction, sort order, filtered status) for the matching sorter/filter.
Sorter Property Converter
This converter fetches a specific property from the Sorter that matches the column's SortMemberPath:
using System; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Windows.Data; using System.ComponentModel; public class SorterPropertyConverter : IMultiValueConverter, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { // Validate inputs if (values.Length < 2 || values[0] is not ObservableCollection<Sorter> sorters || string.IsNullOrEmpty(values[1] as string)) return null; string sortMemberPath = values[1] as string; Sorter matchingSorter = sorters.FirstOrDefault(s => s.Tag == sortMemberPath); // Subscribe to collection and property changes for automatic UI updates SubscribeToChanges(sorters, matchingSorter); // Return the requested property from the matching sorter string propertyName = parameter as string; return typeof(Sorter).GetProperty(propertyName)?.GetValue(matchingSorter); } private void SubscribeToChanges(ObservableCollection<Sorter> sorters, Sorter matchingSorter) { // Listen for collection changes (add/remove sorters) WeakEventManager<ObservableCollection<Sorter>, NotifyCollectionChangedEventArgs> .AddHandler(sorters, nameof(ObservableCollection<Sorter>.CollectionChanged), OnSortersChanged); // Listen for property changes on the matching sorter if (matchingSorter != null) { WeakEventManager<Sorter, PropertyChangedEventArgs> .AddHandler(matchingSorter, nameof(Sorter.PropertyChanged), OnSorterPropertyChanged); } } private void OnSortersChanged(object sender, NotifyCollectionChangedEventArgs e) { // Trigger UI update when the sorters collection changes PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty)); } private void OnSorterPropertyChanged(object sender, PropertyChangedEventArgs e) { // Trigger UI update when a sorter's property (like SortDirection) changes PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty)); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException("Two-way binding is not supported for this converter"); } }
Filter Property Converter
Create an identical converter for your Filters collection (replace Sorter with Filter and adjust property names as needed).
2. Update Your XAML Template
Replace the SorterThumb and old bindings with MultiBinding that uses your new converters. First, declare the converters in your resources:
<Window.Resources> <local:SorterPropertyConverter x:Key="SorterPropertyConverter" /> <local:FilterPropertyConverter x:Key="FilterPropertyConverter" /> </Window.Resources>
Then modify the DataGridColumnHeader template's triggers and bindings:
<Style TargetType="{x:Type DataGridColumnHeader}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type DataGridColumnHeader}"> <Grid x:Name="fullHeader" Background="{StaticResource Brush.Background}" > <Grid Margin="0"> <!-- Column definitions remain the same --> <Grid.ColumnDefinitions> <ColumnDefinition Width="5" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="3" /> <ColumnDefinition Width="10" /> <ColumnDefinition Width="3" /> <ColumnDefinition Width="12" /> <ColumnDefinition Width="5" /> </Grid.ColumnDefinitions> <!-- Hover rectangle, content presenter remain the same --> <Rectangle x:Name="HoverRectangle" ... /> <ContentPresenter Grid.Column="1" ... /> <!-- Filter Symbol with binding to Filters collection --> <Path x:Name="FilterSymbol" Grid.Column="3" ... /> <!-- Sort Arrow and Order with bindings to Sorters collection --> <Grid Grid.Column="5"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Path x:Name="SortArrow" Grid.Row="0" ... /> <TextBlock x:Name="SortOrder" Grid.Row="1" FontSize="8" VerticalAlignment="Center" HorizontalAlignment="Center"> <TextBlock.Text> <MultiBinding Converter="{StaticResource SorterPropertyConverter}" ConverterParameter="SortOrder"> <Binding Path="DataContext.Sorters" RelativeSource="{RelativeSource AncestorType=DataGrid}" /> <Binding Path="SortMemberPath" RelativeSource="{RelativeSource AncestorType=DataGridColumn}" /> </MultiBinding> </TextBlock.Text> </TextBlock> </Grid> </Grid> <!-- Header grippers remain the same --> <Thumb x:Name="PART_LeftHeaderGripper" ... /> <Thumb x:Name="PART_RightHeaderGripper" ... /> </Grid> <ControlTemplate.Triggers> <!-- Trigger for hidden sort arrow when no sorter exists --> <DataTrigger Value="{x:Null}"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource SorterPropertyConverter}" ConverterParameter="CustomSortDirection"> <Binding Path="DataContext.Sorters" RelativeSource="{RelativeSource AncestorType=DataGrid}" /> <Binding Path="SortMemberPath" RelativeSource="{RelativeSource AncestorType=DataGridColumn}" /> </MultiBinding> </DataTrigger.Binding> <Setter TargetName="SortArrow" Property="Visibility" Value="Hidden" /> <Setter TargetName="SortOrder" Property="Visibility" Value="Hidden" /> </DataTrigger> <!-- Trigger for descending sort direction --> <DataTrigger Value="Descending"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource SorterPropertyConverter}" ConverterParameter="CustomSortDirection"> <Binding Path="DataContext.Sorters" RelativeSource="{RelativeSource AncestorType=DataGrid}" /> <Binding Path="SortMemberPath" RelativeSource="{RelativeSource AncestorType=DataGridColumn}" /> </MultiBinding> </DataTrigger.Binding> <Setter TargetName="SortArrow" Property="RenderTransform"> <Setter.Value> <RotateTransform Angle="180" /> </Setter.Value> </Setter> </DataTrigger> <!-- Trigger for hidden filter symbol when no filter exists --> <DataTrigger Value="False"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource FilterPropertyConverter}" ConverterParameter="IsFiltered"> <Binding Path="DataContext.Filters" RelativeSource="{RelativeSource AncestorType=DataGrid}" /> <Binding Path="SortMemberPath" RelativeSource="{RelativeSource AncestorType=DataGridColumn}" /> </MultiBinding> </DataTrigger.Binding> <Setter TargetName="FilterSymbol" Property="Visibility" Value="Hidden" /> </DataTrigger> <!-- Mouse hover trigger remains the same --> <Trigger Property='IsMouseOver' SourceName="fullHeader" Value='True'> ... </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
3. Critical Requirements
- INotifyPropertyChanged: Ensure your
SorterandFilterclasses implementINotifyPropertyChangedso property updates (likeCustomSortDirectionorIsFiltered) trigger UI changes. - Weak Event Listeners: The converter uses
WeakEventManagerto avoid memory leaks when collections or items are disposed.
This setup eliminates the need for the SorterThumb and automatically updates the header whenever your Sorters/Filters collection or individual items change—perfect for MVVM!
内容的提问来源于stack exchange,提问作者Bisjob

