【问题标题】:Only fire event after last TreeViewItem datasource has changed仅在最后一个 TreeViewItem 数据源更改后触发事件
【发布时间】:2018-09-21 19:21:29
【问题描述】:

我的视图中有一个 TreeView,它数据绑定到我的 ViewModel 中的根 Nodes 列表。那些根Nodes 可以有子Nodes。所有节点都属于同一类型,并具有属性IsSelected,该属性绑定到CheckBoxIsChecked 依赖属性,该属性包含在各自的TreeViewItem 中。 CheckBox 已将 IsThreeState 设置为 false

public class Node : PropertyChangedBase, INode
{
    private bool? _isSelected;
    private IList<INode> _nodes;
    private INode _parent;

    public Node()
    { }

    public bool? IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_SetField(ref _isSelected, value))
            {
                _OnIsSelectedChanged();
            }
        }
    }
    public IList<INode> Nodes
    {
        get { return _nodes; }
        set { _SetField(ref _nodes, value); }
    }
    public INode Parent
    {
        get { return _parent; }
        set { _SetField(ref _parent, value); }
    }

    private void _OnIsSelectedChanged()
    {
        if (IsSelected.HasValue)
        {
            if (IsSelected.Value)
            {
                if (Parent != null)
                {
                    // Set IsSelected on all parenting nodes to:
                    //  - true, if all of their immediate child packages have been selected
                    //  - null, else
                }

                if (Nodes != null && Nodes.Count > 0)
                {
                    // Prevent running this method again by circumventing setting the property
                    _SetField(ref _isSelected, null);
                }
            }
            else
            {
                if (Parent != null)
                {
                    // Set IsSelected of the parent to null
                }

                if (Nodes != null)
                {
                    // Set IsSelected = false on all child nodes
                }
            }
        }
        else if (Parent != null)
        {
            // Set IsSelected on all parenting nodes to:
            //  - true, if all of their immediate child packages have been selected
            //  - null, else
        }
    }
}

PropertyChangedBase 是实现INotifyPropertyChanged 的基类。它是在this SO answer 之后设计的。如果设置值实际发生变化,_SetField(ref object, object) 返回true 并通知属性变化。

如果用户单击 CheckBox,则该更改应该传播父节点的(直到根节点)IsSelected 属性和子节点的 IsSelected 属性。所有属性的传播完成后,我想触发一个事件。但仅当不再更改节点时。然后我想在 ViewModel 中做一些事情,这需要一些时间,所以如果每个更改的属性都会触发事件,那么性能会很差。

行为应该如下:

  • 如果节点的IsSelected 设置为truenull,则父节点的IsSelected 设置为null,如果不是所有节点的兄弟节点的IsSelected 设置为true 或@ 987654346@(然后向上传播)。
  • 如果节点的IsSelected 设置为truenull,则父节点的IsSelected 设置为true,如果该节点的所有兄弟节点的IsSelected 设置为true 或@9876545453 @(然后传播到树上)。
  • 如果一个节点的IsSelected 设置为false,则其所有直接子节点的IsSelected 也设置为false(然后沿树传播)。
  • 设置为null 的节点表示尚未选择其所有直接子节点。

那么我怎样才能实现仅在最后一个节点更改后触发 PropertyChanged 事件(或我要实现的另一个事件)?

【问题讨论】:

  • 我能想到的最简单的伪代码来解释你想要实现的概念是:doStuffRecursive();火灾事件();。问题是我想不出一种“检测”StuffRecursiveFinished 的方法。我会想到某种计数(知道您应该为给定请求加载多少节点),您使用 Interlocked.Decrement() 以及何时减量为 0。但我相信这会很快变得非常复杂并且有更好的通过 WPF 机制的方式
  • @AlexandruClonțea 当然。 DoStuffRecursive 然后是 fireEvent。如果我的Node 上有一个公共方法设置IsSelected 而不调用_OnIsSelectedChanged,我可以在一个方法中更改所有节点,然后触发事件。不过,我必须锁定,以防止同时调用_OnIsSelectedChanged 两次。我会试试的。
  • @AlexandruClonțea 谢谢你的灵感。我最终做到了这一点:提高IsSelectedChangedPropagationStarted,执行传播,提高IsSelectedChangedPropagationCompleted。像魅力一样工作。我不敢相信,这不是我自己想出来的。有时你只需要在正确的方向上稍微推动一下。再次非常感谢。
  • 很高兴听到这个消息!如果可以,请添加更完整的答案,将来可能对其他人有所帮助!一旦获得,恭喜!

标签: c# wpf mvvm event-handling treeview


【解决方案1】:

我最终做了Alexandru 建议的事情。

我介绍了两个事件IsSelectedChangedPropagationStartedIsSelectedChangedPropagationCompleted,第一个在处理选择之前引发,后者在完成后引发。该类现在看起来与此类似:

public class Node : PropertyChangedBase, INode
{
    // #### Attributes
    private bool? _isSelected;
    private IList<INode> _nodes;
    private INode _parent;

    // #### Constructor
    public Node()
    { }

    // #### Properties
    public bool? IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_SetField(ref _isSelected, value))
            {
                _OnIsSelectedChanged();
            }
        }
    }
    public IList<INode> Nodes
    {
        get { return _nodes; }
        set { _SetField(ref _nodes, value); }
    }
    public INode Parent
    {
        get { return _parent; }
        set { _SetField(ref _parent, value); }
    }

    // #### Events
    public event EventHandler IsSelectedChangedPropagationStarted;
    public event EventHandler IsSelectedChangedPropagationCompleted;

    // #### Instance Methods
    private void _OnIsSelectedChanged()
    {
        IsSelectedChangedPropagationStarted?.Invoke(this, EventArgs.Empty);

        if (IsSelected.HasValue)
        {
            if (IsSelected.Value)
            {
                RecursivelySetAllParents();

                if (Nodes != null && Nodes.Count > 0)
                {
                    // Prevent running this method again by circumventing setting the property
                    _SetField(ref _isSelected, null);
                }
            }
            else
            {
                if (Parent != null)
                {
                    // Set IsSelected of the parent to null
                }

                RecursivelySetAllChildren();
            }
        }
        else if (Parent != null)
        {
            // Set IsSelected on all parenting nodes to:
            //  - true, if all of their immediate child packages have been selected
            //  - null, else
        }

        IsSelectedChangedPropagationCompleted?.Invoke(this, EventArgs.Empty);
    }
}

通过实现自定义EventArgs,我还可以告诉听众是否有任何变化,以便他们采取相应的行动。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-05
    • 2020-09-25
    • 1970-01-01
    • 2016-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多