我正在使用依赖项属性GroupDescription根据列表视图项源的属性对
WPF列表视图中的项目进行分组.
我的问题是,只有在更改GroupDescription值时才更新分组,而只有在列表视图源中项目的bound属性更改后才会更新.
在下面的示例中,GroupDescription设置为city,从而产生“City:Hamburg”的组描述.但是当更改项目城市属性时,列表视图中的分组不会更新,这意味着在“城市:汉堡”组中将有一个城市“柏林”的项目.
只有在更新GroupDescription后才会更新分组.我试图找到一个使用PersonPropertyChanged方法的工作,该方法将GroupDescription更改为PersonId并立即返回City.然而,如果滚动位置例如在列表视图的中间或末尾,则此工作导致列表视图总是跳回到顶部.当使用包含具有更改属性的条目的列表视图时,这可能非常烦人.
有没有一种方法可以在项目属性更改后“更新”分组,而不会将列表视图跳回到顶部?
预先感谢您的任何帮助!
托马斯
GroupingListView.cs
using System.Windows.Controls; using System.Windows; using System.Windows.Data; namespace WpfApplication { /// <summary> /// Enhanced list view based on WPF ListView with dependency properties for GroupDescriptions /// </summary> public class GroupingListView : ListView { /// <summary> /// Dependency property for group descriptions /// </summary> public string GroupDescription { get { return (string)GetValue(GroupDescriptionProperty); } set { SetValue(GroupDescriptionProperty,value); } } /// <summary> /// Using a DependencyProperty as the backing store for GroupDescription. This enables animation,styling,binding,etc... /// </summary> public static readonly DependencyProperty GroupDescriptionProperty = DependencyProperty.Register("GroupDescription",typeof(string),typeof(GroupingListView),new UIPropertyMetadata(string.Empty,GroupDescriptionChanged)); private static void GroupDescriptionChanged(DependencyObject source,DependencyPropertyChangedEventArgs args) { var control = source as GroupingListView; // Stop if source is not of type DetailedListView if (control == null) return; // Stop if myView is not available,myView can not group,groupdescription missing\ // or the argument is empty var myView = (CollectionView)CollectionViewSource.Getdefaultview(control.ItemsSource); if (myView == null || !myView.Cangroup || (string) args.NewValue == string.Empty || myView.GroupDescriptions == null) { return; } myView.GroupDescriptions.Clear(); // If a group description already if(myView.GroupDescriptions.Count > 0) { var prop = myView.GroupDescriptions[0] as PropertyGroupDescription; if(prop != null) { if(!prop.PropertyName.Equals((string)args.NewValue)) { myView.GroupDescriptions.Clear(); } } } // Stop if at this point a group description still exists. This means the newValue is // equal to the old value and nothing needs to be changed if (myView.GroupDescriptions.Count != 0) return; // If this code is reached newValue is different than the current groupDescription value // therefore the newValue has to be added as PropertyGroupDescription var groupDescription = new PropertyGroupDescription((string)args.NewValue); // Clear and add the description only if it's not already existing myView.GroupDescriptions.Add(groupDescription); } } }
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication="clr-namespace:WpfApplication" Title="MainWindow" Height="300" Width="300"> <StackPanel> <Button Content="Change" Click="btnChangeCity" Height="22"/><Button Content="Change back" Click="btnChangeCityBack" Height="22"/> <WpfApplication:GroupingListView ItemsSource="{Binding Persons}" Height="200" GroupDescription="{Binding GroupDescription}" x:Name="GroupingListView"> <ListView.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <Grid HorizontalAlignment="Stretch"> <Border HorizontalAlignment="Stretch" BorderBrush="Transparent" BorderThickness="1" CornerRadius="3"> <Border HorizontalAlignment="Stretch" BorderBrush="LightGray" BorderThickness="0,1" CornerRadius="0"> <StackPanel Orientation="Horizontal"> <TextBlock Foreground="LightGray" Text="{Binding GroupDescription,ElementName=GroupingListView}"/> <TextBlock Foreground="LightGray" Text=" : "/> <TextBlock Foreground="LightGray" Text="{Binding Name}" HorizontalAlignment="Stretch"/> </StackPanel> </Border> </Border> </Grid> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle> <ListView.View> <GridView> <GridViewColumn Header="PersonId" Width="100"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock TextTrimming="CharacterEllipsis" Text="{Binding PersonId,Mode=Default}"/> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="City" Width="100"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock TextTrimming="CharacterEllipsis" Text="{Binding City,Mode=Default}"/> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </WpfApplication:GroupingListView> </StackPanel> </Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; namespace WpfApplication { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : INotifyPropertyChanged { public class Person : INotifyPropertyChanged { public Person(string personId,string city) { PersonId = personId; City = city; } private string _personId; private string _city; public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { PropertyChangedEventHandler pc = PropertyChanged; if (pc != null) pc(this,new PropertyChangedEventArgs(name)); } public string PersonId { get { return _personId; } set { _personId = value; OnPropertyChanged("PersonId"); } } public string City { get { return _city; } set { _city = value; OnPropertyChanged("City"); } } } public ObservableCollection<Person> Persons { get; set; } public string GroupDescription { get { return _groupDescription; } set { _groupDescription = value; OnPropertyChanged("GroupDescription"); } } private string _groupDescription; public MainWindow() { InitializeComponent(); DataContext = this; GroupDescription = "City"; Persons = new ObservableCollection<Person>(); Persons.CollectionChanged += PersonsCollectionChanged; Persons.Add(new Person("1","Hamburg")); Persons.Add(new Person("2","Hamburg")); Persons.Add(new Person("3","Hamburg")); Persons.Add(new Person("4","Hamburg")); Persons.Add(new Person("5","Hamburg")); Persons.Add(new Person("6","Hamburg")); Persons.Add(new Person("7","Hamburg")); Persons.Add(new Person("8","Hamburg")); Persons.Add(new Person("9","Berlin")); Persons.Add(new Person("10","Hamburg")); Persons.Add(new Person("11","Hamburg")); Persons.Add(new Person("12","Munich")); Persons.Add(new Person("13","Munich")); OnPropertyChanged("Persons"); } public void PersonsCollectionChanged(object sender,NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Remove) { foreach(Person item in e.OldItems) { //Removed items item.PropertyChanged -= PersonPropertyChanged; } } else if (e.Action == NotifyCollectionChangedAction.Add) { foreach(Person item in e.NewItems) { //Added items item.PropertyChanged += PersonPropertyChanged; } } } public void PersonPropertyChanged(object sender,PropertyChangedEventArgs e) { //GroupDescription = "PersonId"; //GroupDescription = "City"; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { PropertyChangedEventHandler pc = PropertyChanged; if (pc != null) pc(this,new PropertyChangedEventArgs(name)); } private void btnChangeCity(object sender,System.Windows.RoutedEventArgs e) { Persons[0].City = "Berlin"; } private void btnChangeCityBack(object sender,System.Windows.RoutedEventArgs e) { Persons[0].City = "Hamburg"; } } }
解决方法
我意识到现在已经很晚了,但是如果你使用的是.NET4.5或更高版本,你可以使用实时分组功能,我认为它可以完全按照你的需要进行操作.
例如,不是将ListView ItemsSource直接绑定到Persons,而是绑定到一个CollectionViewSource,它本身绑定到Persons:
<Window.Resources> <CollectionViewSource x:Key="PersonsViewSource" Source="{Binding Persons}" IsLiveGroupingRequested="True"> <CollectionViewSource.GroupDescriptions> <PropertyGroupDescription PropertyName="GroupName" /> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources>
如上所示,您只需添加属性IsLiveGroupingRequested =“True”,并添加要重新组合的属性名称.
当GroupName属性更改时(通过使用INotifyPropertyChanged),相关项将自动移动到ListView中的正确组,而不更改任何其他内容.