使用HierarchicalDataTemplate的WPF MVVM TreeView无法更新

因此,很长一段时间以来,我一直在努力使TreeViews正确更新,因此我想问是否有人可以告诉我为什么我的代码在加法或减法中没有正确地更新TreeView节点.我为稍大的代码转储预先表示歉意,但我认为说明这个问题非常重要.

首先,我的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)
    {
    }
}

TreeNode类

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

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

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>

最后是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属性的get访问器,但UI上显示的内容保持不变.

过去,我已经绕过这个问题,但是使用ObservableCollections,这似乎是大多数此类问题的解决方案指向*的地方,但是为什么这种解决方案也行不通?我想念什么?

解决方法:

问题是您滥用INotifyPropertyChanged.在您的代码中,您正在通知视图您的属性ChildNodes已更改,但事实并非如此,因为TreeViewItem.ItemsSource仍等于您的ChildNodes属性.

当视图模型中的基础集合对象发生更改时,INotifyPropertyChanged将导致UI更新.

要在发生集合中的新项目时更新ItemsSource,您需要使用实现INotifyCollectionChanged的集合.

作为MSDN says

You can enumerate over any collection that implements the IEnumerable interface. 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. This interface exposes an event that should be raised whenever the underlying collection changes.

因此,SO上的每个人都建议使用ObservableCollection.

编辑:

如果要公开只读集合,则应检查ReadOnlyObservableCollection<T> Class.它用作ObservableCollection的包装,可以将其设为非公开.

上一篇:CodeGo.net>在树视图中更改子节点的背景色


下一篇:CodeGo.net>为Acumatica创建TreeView