我使用依赖属性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,0,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";
}
}
}
答案 0 :(得分:13)
我意识到这一天已经很晚了,但是如果你使用的是.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>
:< / p>
IsLiveGroupingRequested="True"
如上所示,您只需添加属性GroupName
,然后添加要重新组合的属性名称。
当INotifyPropertyChanged
属性发生更改时(通过您使用ListView
),相关项目会自动移至{{1}}中的正确群组,而不会更改任何其他内容。
答案 1 :(得分:8)
我自己找到了一个有效的解决方案。
在PersonPropertyChanged方法中,我调用
ICollectionView view = CollectionViewSource.GetDefaultView(GroupingListView.ItemsSource);
view.Refresh();
我不确定这是否是完美的解决方案,但它可以解决我的问题。