c# - 更改绑定(bind)属性时,ListView 分组不会更新

标签 c# wpf listview data-binding grouping

我正在使用依赖属性 GroupDescription 根据我的 ListView 项目源的属性对 WPF ListView 中的项目进行分组。

我的问题是分组仅在 GroupDescription 值更改时更新,而不是在 ListView 源中的项目的绑定(bind)属性更改后更新。

在下面的示例中,GroupDescription 设置为城市,这导致组描述为“城市:汉堡”。但是,当更改项目城市属性时, ListView 中的分组不会更新,这意味着在“城市:汉堡”组中将有一个城市为“柏林”的项目。

分组仅在 GroupDescription 更新后更新。我试图找到一个使用 PersonPropertyChanged 方法的解决方法,该方法将 GroupDescription 更改为 PersonId 并立即返回到 City。但是,如果滚动位置例如在 ListView 的中间或末尾,则此变通方法会导致 ListView 总是跳回顶部。在处理包含数百个属性不断变化的条目的 ListView 时,这真的很烦人。

有没有一种方法可以在项目属性更改后“更新”分组而不用 ListView 跳回到顶部?

提前感谢您的帮助! 托马斯

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";
        }

    }
}

最佳答案

我意识到现在已经很晚了,但是如果您使用的是 .NET4.5 或更高版本,您可以使用实时分组功能,我认为它会完全满足您的需求。

例如,不是将 ListView ItemsSource 直接绑定(bind)到 Persons,而是绑定(bind)到 CollectionViewSource 它本身绑定(bind)到 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 中的正确组中, 无需更改任何其他内容。

关于c# - 更改绑定(bind)属性时,ListView 分组不会更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10878500/

相关文章:

c# - 在 C# 中处理资源

c# - try/catch 在 UnhandledException 处理程序中不起作用

c# - 从浏览器打开关联文档(PDF、DOCS 等)

c# - "ceq"MSIL 命令和 object.InternalEquals 之间的区别

android - 使用 SimpleCursorAdapter 和 SQLite 在 ListView 中反转顺序

android - 如何在不使用 notifyDataSetChanged() 的情况下更新 Listview 中的某些数据?

c# - 按名称查找 WPF 控件

c# - 使用 ODP.NET 从 Oracle 存储过程获取日期值

c# - 在 WPF 控件可见性更改上应用动画

android - ListView 在加载更多时重复数据