using System.Collections.Specialized;
namespace JD.AssetBrowser.Model
{
///
/// Provides a common implementation of a tree node model that supports three-state checking.
///
public class ThreeStateCheckableTreeNodeModel : CheckableTreeNodeModel
{
private bool _isUpdatingIsChecked;
private ThreeStateCheckableTreeNodeModel _parent;
///
/// Initializes a new instance of the class.
///
public ThreeStateCheckableTreeNodeModel()
{
Children.CollectionChanged += OnChildrenCollectionChanged;
}
///
/// Occurs when the collection is changed.
///
/// The sender of the event.
/// The containing data related to this event.
private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e != null)
{
if (e.OldItems != null)
{
foreach (var oldItem in e.OldItems)
{
if (oldItem is ThreeStateCheckableTreeNodeModel oldChild)
oldChild.Parent = null;
}
}
if (e.NewItems != null)
{
foreach (var newItem in e.NewItems)
{
if (newItem is ThreeStateCheckableTreeNodeModel newChild)
newChild.Parent = this;
}
}
}
}
///
/// Applies recursive updates based on the checked state.
///
private void ApplyRecursiveUpdates()
{
if (!IsChecked.HasValue)
return;
foreach (var child in Children)
{
if (child is ThreeStateCheckableTreeNodeModel checkableChild)
ApplyRecursiveUpdates(checkableChild, IsChecked);
}
var ancestor = _parent;
while (ancestor != null)
{
var allChildrenChecked = true;
var allChildrenUnchecked = true;
foreach (var child in ancestor.Children)
{
if (child is ThreeStateCheckableTreeNodeModel checkableChild)
{
switch (checkableChild.IsChecked)
{
case true:
allChildrenUnchecked = false;
break;
case false:
allChildrenChecked = false;
break;
default:
allChildrenChecked = false;
allChildrenUnchecked = false;
break;
}
}
}
if (allChildrenChecked)
ancestor.SetIsCheckedWithoutRecursion(true);
else if (allChildrenUnchecked)
ancestor.SetIsCheckedWithoutRecursion(false);
else
ancestor.SetIsCheckedWithoutRecursion(null);
ancestor = ancestor.Parent;
}
}
///
/// Applies recursive updates based on the checked state.
///
/// The node to update.
/// The new value.
private void ApplyRecursiveUpdates(ThreeStateCheckableTreeNodeModel node, bool? newValue)
{
if ((node != null) && (node.IsChecked != newValue))
{
node.SetIsCheckedWithoutRecursion(newValue);
foreach (var child in node.Children)
{
if (child is ThreeStateCheckableTreeNodeModel checkableChild)
ApplyRecursiveUpdates(checkableChild, newValue);
}
}
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(IsChecked):
if (!_isUpdatingIsChecked)
ApplyRecursiveUpdates();
break;
}
}
///
/// Gets the parent node, whose reference is needed for check updates that affect the ancestor nodes.
///
/// The parent node, whose reference is needed for check updates that affect the ancestor nodes.
public ThreeStateCheckableTreeNodeModel Parent
{
get { return _parent; }
set { SetProperty(ref _parent, value); }
}
///
/// Sets the IsChecked property without recursion.
///
/// The new value.
public void SetIsCheckedWithoutRecursion(bool? newValue)
{
try
{
_isUpdatingIsChecked = true;
IsChecked = newValue;
}
finally
{
_isUpdatingIsChecked = false;
}
}
}
}