WPF TreeView не обновляется при обновлении ObservableCollection

222
13 апреля 2017, 15:18

Есть следующий код XAML:

<TreeView x:Name="MyTreeitems" ItemsSource="{Binding Path=Items}" FontSize="12" FontWeight="Normal">
<TreeView.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local2:TreeModel}"  ItemsSource="{Binding Path=RelatedItems}">
        <StackPanel Orientation="Horizontal">
            <Image x:Name="nodeImg" Width="16" Height="16" Source="{Binding CollapsedImageSource}" />
            <TextBlock Margin="2,0,0,0" Text="{Binding Name}"/>
        </StackPanel>
        <HierarchicalDataTemplate.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Path=SubItems}">
                <StackPanel Orientation="Horizontal">
                    <Image x:Name="nodeImg" Width="16" Height="16" Source="{Binding CollapsedImageSource}" />
                    <TextBlock Margin="2,0,0,0" Text="{Binding Name}" />
                </StackPanel>
                <HierarchicalDataTemplate.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Path=SubsubItems}">
                        <StackPanel Orientation="Horizontal">
                            <Image x:Name="nodeImg" Width="16" Height="16" Source="{Binding CollapsedImageSource}" />
                            <TextBlock Margin="2,0,0,0" Text="{Binding Name}" />
                        </StackPanel>
                        <HierarchicalDataTemplate.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal" MouseDown="lbCarotages_PreviewMouseDown">
                                    <Image x:Name="nodeImg" Width="16" Height="16" Source="{Binding LeftImageSource}" />
                                    <TextBlock Text="{Binding Path=Description}"/>
                                    <Image Width="16" Margin="0 0 5 0" Source="{Binding Path=Name, ConverterParameter=Curve, Converter={StaticResource convCurveDefinedToImage}}"/>
                                </StackPanel>
                            </DataTemplate>
                        </HierarchicalDataTemplate.ItemTemplate>
                    </HierarchicalDataTemplate>
                </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
        </HierarchicalDataTemplate.ItemTemplate>
    </HierarchicalDataTemplate>
</TreeView.Resources>

Формирую Items = new ObservableCollection();

TreeModel:

public class TreeModel : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { _name = value;
            OnPropChng("Name");
        }
    }
    public string Description { get; set; }
    public BitmapImage CollapsedImageSource { get; set; }
    public BitmapImage ExpandedImageSource { get; set; }
    private readonly ObservableCollection<TreeModel1> _relatedItems = new ObservableCollection<TreeModel1>();
    public ObservableCollection<TreeModel1> RelatedItems
    {
        get { return _relatedItems; }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropChng(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}
public class TreeModel1
{
    public string Name { get; set; }
    public string Description { get; set; }
    public BitmapImage CollapsedImageSource { get; set; }
    public BitmapImage ExpandedImageSource { get; set; }
    private readonly ObservableCollection<TreeModel2> _subItems = new ObservableCollection<TreeModel2>();
    public ObservableCollection<TreeModel2> SubItems
    {
        get
        {
            return _subItems;
        }
    }
}
public class TreeModel2
{
    public string Name { get; set; }
    public string Description { get; set; }
    public BitmapImage CollapsedImageSource { get; set; }
    public BitmapImage ExpandedImageSource { get; set; }
    private readonly ObservableCollection<TreeModel3> _subsubItems = new ObservableCollection<TreeModel3>();
    public ObservableCollection<TreeModel3> SubsubItems
    {
        get
        {
            return _subsubItems;
        } 
    }
}
public class TreeModel3
{
    public string Name { get; set; }
    public BitmapImage LeftImageSource { get; set; }
    public string Description { get; set; }
    public bool IsDefined { get; set; }
}

Тогда дерево отображается идеально.

Но когда меняю что-нить в Items, элементарно Items[0].Name = "новое наименование" TreeView не изменяется.

Не могу разобраться что к чему.

Answer 1

Хорошим дополнением к ответам о реализации INotifyPropertyChanged на моделях будет совет о задании значений свойств в сеттере через метод SetProperty, как это сделано в Prism.

protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) 
{ 
    if (Equals(storage, value)) return false; 
    storage = value; 
    RaisePropertyChanged(propertyName); 
    return true; 
} 

Идея в том, чтобы пробрасывать событие изменения свойства только в случае его реального изменения (а не по факту вызова сеттера). В самом деле, в некоторых случаях лишние вызовы события могут создавать лаги интерфейса.

Пример использования:

public object Prop
{
    get
    {
        return _prop;
    }
    set
    {
        SetProperty(ref _prop, value);
    }
}
Answer 2

наследуем классы от интерфейса INotifyPropertyChanged

добавляем event public event PropertyChangedEventHandler PropertyChanged;

и метод

protected bool NotifiyPropertyChanged<T>(ref T oldValue, T newValue, [CallerMemberName]string property = "")
{
  if (oldValue == null && newValue == null) return false;
  if (oldValue != null && newValue != null)
    if (oldValue is byte[] && newValue is byte[])
      if ((oldValue as byte[]).Count() == (newValue as byte[]).Count())
        if ((newValue as byte[]).SequenceEqual((oldValue as byte[])))
          return false;
  if (!(oldValue is byte[]) || !(newValue is byte[]))
    if (Equals(oldValue, newValue))
      return false;
  oldValue = newValue;
  PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
  return true;
}

при изменении любого из свойств вызывать его. Например:

protected string m_Name = "";
public string Name { 
  get {return m_Name; }
  set { NotifiyPropertyChanged(ref m_Name, value); }
}

в Вашем случае не все классы наследуются от интерфейса INotifyPropertyChanged

UPD: спасибо Gardes за CallerMemberName

UPD: dm.dymov за <T>

UPD: Добавлен Equals для byte[]

Answer 3

Дополнение к ответу @Дмитрий Чистик

Еще одна реализация интерфейса INotifyPropertyChanged, более удобная. Не приходиться заботится о передаче имени свойства в метод и соблюдении правильности написания.

public class BaseVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Использовать так:

public class TreeModelVM : BaseVM
{
    private string _name = string.Empty;
    public string Name 
    { 
        get { return _name; } 
        set
        {   
            if (_name != value)
            { 
                _name = value;
                RaisePropertyChanged();
            }
        }
    }
}
READ ALSO
C# TabPage Почему не показывается Image?

C# TabPage Почему не показывается Image?

В TabControl Добавляю ImageList

335
Работа в фоновом режиме (Xamarin)

Работа в фоновом режиме (Xamarin)

Подскажите как можно решить данный кейс для iOS 9+, Android 44+ и WP 8

274
Проблема с экспортом decimal чисел в excel

Проблема с экспортом decimal чисел в excel

Доброго времени суток! Возник вопрос при экспорте чисел decimal в файл ExcelЗадача следующая: из базы в datagridview на форме записываются данные, потом...

246
System.Threading.Timers или System.Timers.Timer

System.Threading.Timers или System.Timers.Timer

Необходимо инициализировать порядка 1000 таймеровКаждый таймер должен обрабатывать свой метод, метод в аргументе принимает грубо говоря...

193