using System.Collections.Specialized; using System.Runtime.CompilerServices; namespace JD.Oasis.Module.ViewModel.TreeViewModel.Common { /// /// Provides a common implementation of a tree node model that supports three-state checking. /// public class ThreeStateCheckableTreeNodeViewModel : CheckableTreeNodeViewModel { private bool _isUpdatingIsChecked; private ThreeStateCheckableTreeNodeViewModel _parent; ///////////////////////////////////////////////////////////////////////////////////////////////////// // CONSTRUCTOR ///////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Initializes a new instance of the class. /// public ThreeStateCheckableTreeNodeViewModel(string name) : base(name) { Children.CollectionChanged += OnChildrenCollectionChanged; } ///////////////////////////////////////////////////////////////////////////////////////////////////// // NON-PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// /// 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 ThreeStateCheckableTreeNodeViewModel oldChild) oldChild.Parent = null; } } if (e.NewItems != null) { foreach (var newItem in e.NewItems) { if (newItem is ThreeStateCheckableTreeNodeViewModel 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 ThreeStateCheckableTreeNodeViewModel 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 ThreeStateCheckableTreeNodeViewModel 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(ThreeStateCheckableTreeNodeViewModel node, bool? newValue) { if ((node != null) && (node.IsChecked != newValue)) { node.SetIsCheckedWithoutRecursion(newValue); foreach (var child in node.Children) { if (child is ThreeStateCheckableTreeNodeViewModel checkableChild) ApplyRecursiveUpdates(checkableChild, newValue); } } } ///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Raises the event. /// /// The name of the property that changed protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) { // Call the base method base.OnPropertyChanged(propertyName); if (propertyName != null) { switch (propertyName) { case "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 ThreeStateCheckableTreeNodeViewModel Parent { get => _parent; private 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; } } } }