2015-12-29 214 views
3

所以我一直在努力讓我的TreeViews很長一段時間正確更新,所以我問是否有人可以告訴我爲什麼我的代碼沒有正確更新我的TreeView節點在增加或減少。我事先爲有點大規模的代碼轉儲道歉,但我覺得說明問題非常重要。WPF MVVM TreeView使用HierarchicalDataTemplate不更新

對於初學者我ObservableObject類

public abstract class ObservableObject : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    [NotifyPropertyChangedInvocator] 
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChangedEventHandler handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

TreeNodeBase類

public abstract class TreeNodeBase : ObservableObject 
{ 
    protected const string ChildNodesPropertyName = "ChildNodes"; 

    protected string name; 

    public string Name 
    { 
     get 
     { 
      return this.name; 
     } 

     set 
     { 
      this.name = value; 
      this.OnPropertyChanged(); 
     } 
    } 

    protected IList<TreeNode> childNodes; 

    protected TreeNodeBase(string name) 
    { 
     this.Name = name; 
     this.childNodes = new List<TreeNode>(); 
    } 

    public IEnumerable<TreeNode> ChildNodes 
    { 
     get 
     { 
      return this.childNodes; 
     } 
    } 

    public TreeNodeBase AddChildNode(string name) 
    { 
     var treeNode = new TreeNode(this, name); 
     this.childNodes.Add(treeNode); 
     this.OnPropertyChanged(ChildNodesPropertyName); 

     return treeNode; 
    } 

    public TreeNode RemoveChildNode(string name) 
    { 
     var nodeToRemove = this.childNodes.FirstOrDefault(node => node.Name.Equals(name)); 

     if (nodeToRemove != null) 
     { 
      this.childNodes.Remove(nodeToRemove); 
      this.OnPropertyChanged(ChildNodesPropertyName); 
     } 

     return nodeToRemove; 
    } 
} 

public class TreeNode : TreeNodeBase 
{ 
    public TreeNodeBase Parent { get; protected set; } 

    public TreeNode(TreeNodeBase parent, string name) 
     : base(name) 
    { 
     this.Parent = parent; 
    } 
} 

的TreeNodeRoot類

public class TreeViewRoot : TreeNodeBase 
{ 
    public TreeViewRoot(string name) 
     : base(name) 
    { 
    } 
} 

樹節點類

public class TreeNode : TreeNodeBase 
{ 
    public TreeNodeBase Parent { get; protected set; } 

    public TreeNode(TreeNodeBase parent, string name) 
     : base(name) 
    { 
     this.Parent = parent; 
    } 
} 

TreeView的用戶控件的XAML

<UserControl x:Class="TreeViewExperiment.TreeView" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:treeViewExperiment="clr-namespace:TreeViewExperiment" 
      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="400" 
      d:DataContext="{d:DesignInstance treeViewExperiment:TreeViewmodel}"> 

    <UserControl.DataContext> 
     <treeViewExperiment:TreeViewmodel/> 
    </UserControl.DataContext> 

    <Grid Background="White"> 
     <Grid.Resources> 
      <HierarchicalDataTemplate x:Key="TreeViewHierarchicalTemplate" ItemsSource="{Binding ChildNodes}"> 
       <TextBlock Text="{Binding Name}"/> 
      </HierarchicalDataTemplate> 

      <Style TargetType="Button"> 
       <Setter Property="FontFamily" Value="Verdana"/> 
       <Setter Property="FontWeight" Value="Bold"/> 
      </Style> 
     </Grid.Resources> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="6*"/> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <TreeView Grid.Row="0" x:Name="Tree" ItemsSource="{Binding RootLevelNodes}" ItemTemplate="{StaticResource TreeViewHierarchicalTemplate}"> 
      <i:Interaction.Triggers> 
       <i:EventTrigger EventName="SelectedItemChanged"> 
        <i:InvokeCommandAction 
         Command="{Binding SetSelectedNode}" 
         CommandParameter="{Binding SelectedItem, ElementName=Tree}"/> 
       </i:EventTrigger> 
      </i:Interaction.Triggers> 
     </TreeView> 

     <Grid Grid.Row="1" Height="25"> 
      <Grid.ColumnDefinitions> 
       <ColumnDefinition Width="4*"/> 
       <ColumnDefinition Width="2*"/> 
       <ColumnDefinition Width="2*"/> 
      </Grid.ColumnDefinitions> 

      <TextBox x:Name="NameTextBox" Grid.Column="0" VerticalAlignment="Center" FontFamily="Verdana"/> 
      <Button Grid.Column="1" Content="Add Node" Command="{Binding AddNode}" CommandParameter="{Binding Text, ElementName=NameTextBox}" Background="Green"/> 
      <Button Grid.Column="2" Content="Remove Node" Command="{Binding RemoveNode}" Background="Red"/> 
     </Grid> 
    </Grid> 
</UserControl> 

最後TreeViewmodel

public class TreeViewmodel : ObservableObject 
{ 
    public ICommand SetSelectedNode { get; private set; } 

    public ICommand AddNode { get; private set; } 

    public ICommand RemoveNode { get; private set; } 

    public TreeViewmodel() 
    { 
     this.SetSelectedNode = new ParamaterizedDelegateCommand(
      node => 
       { 
        this.SelectedTreeNode = (TreeNodeBase)node; 
       }); 

     this.AddNode = new ParamaterizedDelegateCommand(name => this.SelectedTreeNode.AddChildNode((string)name)); 

     this.RemoveNode = new DelegateCommand(
      () => 
       { 
        if (selectedTreeNode.GetType() == typeof(TreeNode)) 
        { 
         var parent = ((TreeNode)this.SelectedTreeNode).Parent; 
         parent.RemoveChildNode(this.SelectedTreeNode.Name); 
         this.SelectedTreeNode = parent; 
        } 
       }); 

     var adam = new TreeViewRoot("Adam"); 
     var steve = adam.AddChildNode("Steve"); 
     steve.AddChildNode("Jack"); 

     this.rootLevelNodes = new List<TreeViewRoot> { adam, new TreeViewRoot("Eve") }; 
    } 

    private TreeNodeBase selectedTreeNode; 

    private readonly IList<TreeViewRoot> rootLevelNodes; 

    public IEnumerable<TreeViewRoot> RootLevelNodes 
    { 
     get 
     { 
      return this.rootLevelNodes; 
     } 
    } 

    public TreeNodeBase SelectedTreeNode 
    { 
     get 
     { 
      return this.selectedTreeNode; 
     } 

     set 
     { 
      this.selectedTreeNode = value; 
      this.OnPropertyChanged(); 
     } 
    } 
} 

所以我知道,當子元素被添加刪除,當我調試它,我可以看到UI應該得到通知在兩種情況下調用ChildNodes屬性的訪問器,但UI上顯示的內容保持不變。

在過去我已經解決了這個問題,但使用ObservableCollections,這似乎是這類問題的大多數解決方案指向這裏在StackOverflow,但爲什麼不解決方案也工作?我錯過了什麼?

回答

3

問題在於你誤用了INotifyPropertyChanged。在您的代碼中,您將通知您的房產ChildNodes已更改的視圖,但這不是真實的,因爲TreeViewItem.ItemsSource仍等於您的ChildNodes屬性。

INotifyPropertyChanged將在yout中查看模型更改時的基礎集合對象時導致UI更新。

要獲得ItemsSource在收集新項目時發生更新,您需要使用實施INotifyCollectionChanged的收集。

由於MSDN says

您可以枚舉實現IEnumerable接口的集合。但是,要設置動態綁定以便集合中的插入或刪除自動更新UI,集合必須實現INotifyCollectionChanged接口。這個接口暴露了一個事件,當底層集合發生變化時應該引發這個事件。

這就是爲什麼所有人都建議使用ObservableCollection

編輯:

如果要公開只讀你應該檢查ReadOnlyObservableCollection<T> Class集合。它可用作ObservableCollection的包裝,可以非公開。

+0

這似乎是問題所在。然而,對於我來說,離開ObservableCollection仍然很重要,因爲我想向UI展示一個只讀的集合。我可能會將現有的列表分解到它自己的實現INotifyCollectionChanged接口的類中,並查看它是如何發生的。 – Thermonuclear

+0

也許你應該考慮使用[ReadOnlyObservableCollection Class](https://msdn.microsoft.com/en-us/library/ms668620%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396)? –