WPF 意外的 ICollectionView 行为

WPF unexpected ICollectionView behavior

我是 WPF 的新手,只是在学习其工作原理。我的最终目标是为每个标题创建一个数据网格,显示与每个类别相关的 collection 项的总计。

但我看到了一些意外行为。我希望看到这个输出:

标题 1
类别 1
类别 2

标题 2
类别 3

我实际看到的是这样的:

标题 1
类别 3

标题 2
类别 3

我不知道为什么。希望有人能阐明一点。感谢您的帮助!

这是我的代码:

MainWindow.xaml

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <ItemsControl ItemsSource="{Binding Headings}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding Description}" />
                        <DataGrid ItemsSource="{Binding Categories}" AutoGenerateColumns="False">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="" Binding="{Binding Description}" Width="*" />
                            </DataGrid.Columns>
                        </DataGrid>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Window>

MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        public ObservableCollection<HeadingViewModel> Headings { get; }

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            var category1 = new CategoryViewModel()
            {
                Id = 1,
                Description = "Category 1",
                HeadingId = 1,
            };

            var category2 = new CategoryViewModel()
            {
                Id = 2,
                Description = "Category 2",
                HeadingId = 1,
            };

            var category3 = new CategoryViewModel()
            {
                Id = 3,
                Description = "Category 3",
                HeadingId = 2,
            };

            var categories = new ObservableCollection<CategoryViewModel>();
            categories.Add(category1);
            categories.Add(category2);
            categories.Add(category3);

            var heading1 = new HeadingViewModel(categories)
            {
                Id = 1,
                Description = "Heading 1",
            };

            var heading2 = new HeadingViewModel(categories)
            {
                Id = 2,
                Description = "Heading 2",
            };

            this.Headings = new ObservableCollection<HeadingViewModel>();
            this.Headings.Add(heading1);
            this.Headings.Add(heading2);
        }

HeadingViewModel.cs

public class HeadingViewModel : INotifyPropertyChanged
    {
        public ICollectionView Categories { get; }

        public HeadingViewModel(ObservableCollection<CategoryViewModel> categories)
        {
            this.Categories = CollectionViewSource.GetDefaultView(categories);
            this.Categories.Filter = CategoriesFilter;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private bool CategoriesFilter(object item)
        {
            var category = item as CategoryViewModel;
            return category.HeadingId == this.Id;
        }

        private int id;
        public int Id
        {
            get
            {
                return id;
            }
            set
            {
                this.id = value;
                OnPropertyChanged();
                Categories.Refresh();
            }
        }

        public string Description { get; set; }

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

CategoryViewModel.cs

public class CategoryViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public CategoryViewModel() {}

        public int Id { get; set; }

        public string Description { get; set; }

        private int? headingId;
        public int? HeadingId
        {
            get
            {
                return headingId;
            }
            set
            {
                this.headingId = value;
                OnPropertyChanged();
            }
        }

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

问题是,CollectionViewSource.GetDefaultView(categories) 将为相同的输入 return 相同的 ICollectionView 实例,无论您调用它的频率如何。

由于您将相同的 categories 传递给两个 HeadingViewModel 构造函数,第二个构造函数 this.Categories.Filter = CategoriesFilter 将覆盖两个标题的过滤器。

grek40 因告诉我问题出在哪里而获得金星 - 但这里是为其他遇到此问题的人提供的解决方案:

在 HeadingViewModel.cs 中更改此行:

this.Categories = CollectionViewSource.GetDefaultView(categories);

对此:

this.Categories = new CollectionViewSource { Source = categories }.View;

returns 解决问题的每个标题实例的视图源的新实例。