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