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