Reputation: 249
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:
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).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).IsSelected
gets set to false
, all of its immediate child node's IsSelected
get set to false
, too (what then propagates down the tree).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?
Upvotes: 0
Views: 104
Reputation: 249
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.
Upvotes: 1