184 lines
6.7 KiB
C#
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;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|