简体   繁体   中英

Only fire event after last TreeViewItem datasource has changed

I have a TreeView in my View that is databound to a list of root Node s in my ViewModel. Those root Node s can have child Nodes . All nodes are of the same type and have the property IsSelected that is bound to the IsChecked dependency property of a CheckBox that's contained in the respective TreeViewItem . That CheckBox has set IsThreeState to 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 is a base class implementing INotifyPropertyChanged . It's been designed after this SO answer . If the set value actually changes, _SetField(ref object, object) returns true and notifies about the property change.

If the user clicks a CheckBox, that change should propagate the parent node's (up to the root node) IsSelected property and to the child node's IsSelected property, too. After the propagation of all Properties finished, I want to fire an event. But only when no further node will be changed. I then want to do something in the ViewModel, that takes some time, so it would be bad performance-wise if the event would fire with each changed property.

The behaviour should be the following:

  • If a node's IsSelected gets set to true or null , the parent node's IsSelected gets set to null if not all of the node's sibling's IsSelected are set to true or null (what then propagates up the tree).
  • If a node's IsSelected gets set to true or null , the parent node's IsSelected gets set to true if all of the node's sibling's IsSelected are set to true or null (what then propagates up the tree).
  • If a node's IsSelected gets set to false , all of its immediate child node's IsSelected get set to false , too (what then propagates down the tree).
  • A node set to null means that not all of its immediate child nodes have been selected.

So how can I achieve firing the PropertyChanged event (or another I'd implement) only after the last node has been changed?

I ended up doing what Alexandru suggested.

I introduced two events IsSelectedChangedPropagationStarted & IsSelectedChangedPropagationCompleted the first being raised before handling the selection and the latter being raised upon completion. The class looks similar to this now:

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

By implementing custom EventArgs I could also tell the listeners if something at all has changed so they can act accordingly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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