JD2022-TU1/main/tools/modules/JD.Oasis.Module/ViewModel/TreeViewModel/Common/ThreeStateCheckableTreeNodeViewModel.cs

184 lines
6.7 KiB
C#

using System.Collections.Specialized;
using System.Runtime.CompilerServices;
namespace JD.Oasis.Module.ViewModel.TreeViewModel.Common
{
/// <summary>
/// Provides a common implementation of a tree node model that supports three-state checking.
/// </summary>
public class ThreeStateCheckableTreeNodeViewModel : CheckableTreeNodeViewModel
{
private bool _isUpdatingIsChecked;
private ThreeStateCheckableTreeNodeViewModel _parent;
/////////////////////////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR
/////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Initializes a new instance of the <see cref="ThreeStateCheckableTreeNodeViewModel"/> class.
/// </summary>
public ThreeStateCheckableTreeNodeViewModel(string name) : base(name)
{
Children.CollectionChanged += OnChildrenCollectionChanged;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// NON-PUBLIC PROCEDURES
/////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Occurs when the collection is changed.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> containing data related to this event.</param>
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;
}
}
}
}
/// <summary>
/// Applies recursive updates based on the checked state.
/// </summary>
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;
}
}
/// <summary>
/// Applies recursive updates based on the checked state.
/// </summary>
/// <param name="node">The node to update.</param>
/// <param name="newValue">The new value.</param>
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
/////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">The name of the property that changed</param>
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;
}
}
}
/// <summary>
/// Gets the parent node, whose reference is needed for check updates that affect the ancestor nodes.
/// </summary>
/// <value>The parent node, whose reference is needed for check updates that affect the ancestor nodes.</value>
public ThreeStateCheckableTreeNodeViewModel Parent
{
get => _parent;
private set => SetProperty(ref _parent, value);
}
/// <summary>
/// Sets the <c>IsChecked</c> property without recursion.
/// </summary>
/// <param name="newValue">The new value.</param>
public void SetIsCheckedWithoutRecursion(bool? newValue)
{
try
{
_isUpdatingIsChecked = true;
IsChecked = newValue;
}
finally
{
_isUpdatingIsChecked = false;
}
}
}
}