TreeNodeBase - Houzkin/TreeStructures GitHub Wiki
We define the following as an interface representing a tree node.
/// <summary>Represents a node that forms a tree structure.</summary>
/// <typeparam name="TNode">The common base type for each node.</typeparam>
public interface ITreeNode<TNode> where TNode : ITreeNode<TNode> {
/// <summary>Gets the parent node.</summary>
TNode? Parent { get; }
/// <summary>Gets the child nodes.</summary>
IEnumerable<TNode> Children { get; }
}
/// <summary>Defines a mutable tree structure.</summary>
/// <typeparam name="TNode">The type of the nodes.</typeparam>
public interface IMutableTreeNode<TNode> : ITreeNode<TNode>
where TNode : IMutableTreeNode<TNode> {
/// <summary>Adds a child node.</summary>
/// <param name="child">The child node.</param>
/// <returns>The current node.</returns>
TNode AddChild(TNode child);
/// <summary>Adds a child node at the specified index.</summary>
/// <param name="index">The index.</param>
/// <param name="child">The child node.</param>
/// <returns>The current node.</returns>
TNode InsertChild(int index, TNode child);
/// <summary>Removes a child node.</summary>
/// <param name="child">The child node to remove.</param>
/// <returns>The removed node.</returns>
TNode RemoveChild(TNode child);
/// <summary>Removes all child nodes.</summary>
/// <returns>The removed nodes.</returns>
IReadOnlyList<TNode> ClearChildren();
}
/// <summary>Represents an observable tree structure.</summary>
/// <typeparam name="TNode">The type of the nodes.</typeparam>
public interface IObservableTreeNode<TNode> : ITreeNode<TNode>
where TNode : IObservableTreeNode<TNode> {
/// <summary>Called by <see cref="StructureChangedEventExecutor{TNode}"/> when the tree structure changes.</summary>
/// <param name="e"></param>
void OnStructureChanged(StructureChangedEventArgs<TNode> e);
/// <summary>
/// Notification event for when the tree structure changes.
/// </summary>
event EventHandler<StructureChangedEventArgs<TNode>>? StructureChanged;
}Only the properties defined in ITreeNode are publicly exposed in a read-only state.
Necessary methods are exposed and used in derived classes.
public abstract class TreeNodeBase<TNode> : ITreeNode<TNode>
where TNode : TreeNodeBase<TNode>, ITreeNode<TNode>{
protected IEnumerable<TNode> ChildNodes { get; }
protected abstract IEnumerable<TNode> SetupInnerChildCollection();
protected virtual IEnumerable<TNode> SetupPublicChildCollection(IEnumerable<TNode> innerCollection);
protected virtual bool CanAddChildNode(TNode child);
protected virtual void InsertChildProcess(int index, TNode child, Action<IEnumerable<TNode>, int, TNode>? action = null);
protected virtual void RemoveChildProcess(TNode child, Action<IEnumerable<TNode>, TNode>? action = null);
protected virtual void SetChildProcess(int index, TNode child, Action<IEnumerable<TNode>, int, TNode>? action = null);
protected virtual void ClearChildProcess(Action<IEnumerable<TNode>>? action = null);
protected virtual void ShiftChildProcess(int oldIndex, int newIndex, Action<IEnumerable<TNode>, int, int>? action = null);
protected virtual void DisposeProcess();
}The collection specified in SetupInnerChildCollection is set as the ChildNodes property.
ChildNodes is a collection directly manipulated in internal processes.
The collection specified in SetupPublicChildCollection with ChildNodes as an argument is set as the collection for external public access.
By setting up separate collections for internal processing and external access, we prevent external manipulation of the collection.
This is configured as follows in ObservableGeneralTreeNode.
/// <inheritdoc/>
protected override IEnumerable<TNode> SetupInnerChildCollection() => new ObservableCollection<TNode>();
/// <inheritdoc/>
protected override IEnumerable<TNode> SetupPublicChildCollection(IEnumerable<TNode> innerCollection)
=> new ReadOnlyObservableCollection<TNode>((innerCollection as ObservableCollection<TNode>)!);The CanAddChildNode method checks for node cycles and duplicates with sibling nodes, indicating whether the specified node can be added.
It is called during the processing of InsertChildProcess, SetChildProcess.
The Process method is comprised of six methods: InsertChildProcess, RemoveChildProcess, SetChildProcess, ClearChildProcess, ShiftChildProcess, DisposeProcess.
Parent nodes and child nodes achieve mutual references by calling each other's Process methods.
Actual collection operations can be specified by the action argument, and if null is specified, the default operation is executed (default operations are documented in XML comments).
Since ChildNodes is cast to IList or ICollection, if the collection specified in SetupInnerChildCollection does not implement these interfaces, you need to specify the action argument for each method.