Нужен ли иерархическому шаблону данных параметр INotifyPropertyChanged для свойства свойства наблюдаемого типа коллекции

#c# #wpf #binding #treeview #hierarchicaldatatemplate

#c# #wpf #привязка #просмотр дерева #hierarchicaldatatemplate

Вопрос:

У меня есть простой пример, в котором код выглядит следующим образом

 using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace WpfApplication7
{
    /// <summary>
    /// Логика взаимодействия для MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ObservableCollection<Item> Items { get; set; }

        public MainWindow()
        {
            Items = new ObservableCollection<Item>();
            InitializeComponent();
        }

        private void ButtonBase1_OnClick(object sender, RoutedEventArgs e)
        {
            Items.Add(new Item() { ItemName = DateTime.Now.ToString(), SubItems = new ObservableCollection<string>() {"1"} });
        }

        private void ButtonBase2_OnClick(object sender, RoutedEventArgs e)
        {
            var f = Items.FirstOrDefault();
            if (f!=null)
                f.SubItems.Add(f.SubItems.Count.ToString());
        }
    }

    public class Item
    {
        public string ItemName { get; set; }
        public ObservableCollection<string> SubItems { get; set; }
    }
}
  

и XAML такой

 <Window x:Class="WpfApplication7.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:WpfApplication7"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"  DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel>
        <TreeView ItemsSource="{Binding Path=Items}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
                    <TextBlock Text="{Binding ItemName}"></TextBlock>
                   <HierarchicalDataTemplate.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}"></TextBlock>
                        </DataTemplate>
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>

        <ListBox ItemsSource="{Binding Path=Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding ItemName}"></TextBlock>
                        <ListBox ItemsSource="{Binding SubItems}">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding}"></TextBlock>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </StackPanel>

                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Click="ButtonBase1_OnClick">321</Button>
        <Button Click="ButtonBase2_OnClick">123</Button>
    </StackPanel>
</Window>
  

Мне все еще не ясно, почему поведение отличается — списки обновляются с каждым небольшим изменением в обеих коллекциях (как предполагается для ObservableCollection). Но в TreeView только первый уровень обновляется правильно, в то время как подуровень постоянно обновляется. Если я добавлю параметр INotifyPropertyChanged в свойство SubItems, подуровень TreeView также будет обновлен правильно. Кто-нибудь может рассказать мне об этом?

Ответ №1:

Когда ваше окно открывается, оно начинает обрабатывать все данные из Items коллекции. После завершения нет никакого способа, кроме какого-либо уведомления об изменении элементов для window, чтобы увидеть, что данные изменились и содержимое должно быть обновлено. Итак, за помощью здесь приходят INotifyPropertyChanged и его двоюродный брат INotifyCollectionChanged .

Вы также можете рассмотреть возможность сброса ListBox. ItemsSource к нулю и обратно к элементам — это эффективно заставит элемент управления повторно обрабатывать данные и повторно отображать содержимое.

Комментарии:

1. Привет, Алекс. Вопрос в том, почему списки обновляются корректно без дополнительного INotify, в то время как TreeView обновляет только первый уровень, но не подуровень в данном случае. Итак, поведение списков правильное и должно быть таким. Но поведение TreeView странное.

2. @Kehel, я провожу некоторое время с вашим образцом в VS2013 и совсем заблудился: ( Когда я нажимаю 321, и в TreeView, и в List добавляется один элемент, а в TreeView слева есть стрелка, показывающая, что на втором уровне что-то есть. Затем нажмите 123, и у меня есть два элемента во вложенном списке , а также два элемента в расширяемых строках TreeView , которые не расширяются по умолчанию , поэтому похоже, что TreeView не обновлялся. Итак, у меня все работает нормально. Не могли бы вы добавить несколько скриншотов, показывающих, что у вас есть, и выделить, что именно идет не так.

3. Очень странно, может быть, это все из-за VS2015… Я должен проверить это в VS2013

Ответ №2:

Проверьте на VS2013 и еще раз на VS2015, не удается повторить проблему в описании. Предположим, что это было какое-то внешнее условие, а не код. В любом случае спасибо @AlexSeleznyov за беседу =)