简体   繁体   English

使用HierarchicalDataTemplate的WPF MVVM TreeView不更新

[英]WPF MVVM TreeView using HierarchicalDataTemplate not updating

So I've been struggling with getting my TreeViews to update properly for a long time now and so I'm asking if anyone can tell me why my code isn't properly updating my TreeView nodes on additions or subtractions. 所以我一直在努力让我的TreeViews在很长一段时间内正确更新,所以我问是否有人能告诉我为什么我的代码没有在添加或减少时正确更新我的TreeView节点。 I apologize in advance for the somewhat massive code dump but I felt it was all important to illustrate the problem. 我提前为有点大规模的代码转储道歉,但我觉得解释这个问题非常重要。

For starters my ObservableObject class 对于初学者我的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 Class 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;
    }
}

The TreeNodeRoot class TreeNodeRoot类

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

The TreeNode Class TreeNode类

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

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

The TreeView UserControl Xaml TreeView UserControl 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>

Finally the TreeViewmodel 最后是TreeView模型

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();
        }
    }
}

So I know that the UI should be getting notified when child elements are added removed as when I debug it I can see that the get accessor on the ChildNodes property is called in both cases, yet what is displayed on the UI remains unchanged. 因此我知道在添加子元素时应该通知UI,因为我调试它时可以看到在两种情况下都会调用ChildNodes属性上的get访问器,但UI上显示的内容保持不变。

In the past I've gotten around this but using ObservableCollections and that seems to be what most solutions to this sort of problem point to here on StackOverflow, but why doesn't this solution also work? 在过去,我已经解决了这个问题,但是使用了ObservableCollections,这似乎就是这种问题的大多数解决方案都指向了StackOverflow,但为什么这个解决方案也不起作用呢? What am I missing? 我错过了什么?

The problem is that you are misusing INotifyPropertyChanged . 问题是你误用了INotifyPropertyChanged In your code you are notifying the view that your property ChildNodes changed but it isn't true as TreeViewItem.ItemsSource still equals your ChildNodes property. 在您的代码中,您通知视图您的属性ChildNodes更改,但它不是真的,因为TreeViewItem.ItemsSource仍然等于您的ChildNodes属性。

INotifyPropertyChanged will cause UI update when underlying collection object in yout view model changes. 当您查看模型更改中的基础集合对象时, INotifyPropertyChanged将导致UI更新。

To get ItemsSource updated when new item in collection occurs you need to use a collection which implements INotifyCollectionChanged . 要在集合中出现新项目时更新ItemsSource ,您需要使用实现INotifyCollectionChanged的集合。

As MSDN says : 正如MSDN所说

You can enumerate over any collection that implements the IEnumerable interface. 您可以枚举实现IEnumerable接口的任何集合。 However, to set up dynamic bindings so that insertions or deletions in the collection update the UI automatically, the collection must implement the INotifyCollectionChanged interface. 但是,要设置动态绑定以便集合中的插入或删除自动更新UI,集合必须实现INotifyCollectionChanged接口。 This interface exposes an event that should be raised whenever the underlying collection changes. 此接口公开了一个事件,只要底层集合发生更改,就应该引发该事件。

That's why everyone on SO advise to use ObservableCollection . 这就是为什么SO上的每个人都建议使用ObservableCollection

EDIT: 编辑:

If you want to expose read-only collection you should check ReadOnlyObservableCollection<T> Class . 如果要公开只读集合,则应检查ReadOnlyObservableCollection<T> Class It works as a wrapper for ObservableCollection which can be made non public. 它作为ObservableCollection的包装器,可以非公开。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM