212 lines
6.1 KiB
C#
212 lines
6.1 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Collections.ObjectModel;
|
|||
|
|
using System.Collections.Specialized;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Windows;
|
|||
|
|
using System.Windows.Controls;
|
|||
|
|
using System.Windows.Controls.Primitives;
|
|||
|
|
using System.Windows.Markup;
|
|||
|
|
|
|||
|
|
namespace WPFluent.Controls;
|
|||
|
|
|
|||
|
|
[ContentProperty(nameof(View))]
|
|||
|
|
public class TreeModelListView : ListView
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Internal collection of rows representing visible nodes, actually displayed in the ListView
|
|||
|
|
/// </summary>
|
|||
|
|
public TreeModelRowCollection<TreeModelNode> Rows { get; private set; }
|
|||
|
|
|
|||
|
|
public ITreeModel Model
|
|||
|
|
{
|
|||
|
|
get => (ITreeModel)GetValue(ModelProperty);
|
|||
|
|
set => SetValue(ModelProperty, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty ModelProperty =
|
|||
|
|
DependencyProperty.Register(nameof(Model), typeof(ITreeModel), typeof(TreeModelListView), new PropertyMetadata(null!, ModelChangedCallback));
|
|||
|
|
|
|||
|
|
public static void ModelChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|||
|
|
{
|
|||
|
|
if (d is TreeModelListView self)
|
|||
|
|
{
|
|||
|
|
self.Root.Children.Clear();
|
|||
|
|
self.Rows.Clear();
|
|||
|
|
self.CreateChildrenNodes(self.Root);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public CornerRadius CornerRadius
|
|||
|
|
{
|
|||
|
|
get => (CornerRadius)GetValue(CornerRadiusProperty);
|
|||
|
|
set => SetValue(CornerRadiusProperty, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty CornerRadiusProperty =
|
|||
|
|
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(TreeModelListView), new PropertyMetadata(new CornerRadius(3)));
|
|||
|
|
|
|||
|
|
internal TreeModelNode Root { get; set; } = null!;
|
|||
|
|
|
|||
|
|
public ReadOnlyCollection<TreeModelNode> Nodes => Root.Nodes;
|
|||
|
|
|
|||
|
|
internal TreeModelNode? PendingFocusNode { get; set; } = null;
|
|||
|
|
|
|||
|
|
public ICollection<TreeModelNode> SelectedNodes => SelectedItems.Cast<TreeModelNode>().ToArray();
|
|||
|
|
|
|||
|
|
public TreeModelNode? SelectedNode => SelectedItems.Count > 0 ? SelectedItems[0] as TreeModelNode : null;
|
|||
|
|
|
|||
|
|
public TreeModelListView()
|
|||
|
|
{
|
|||
|
|
Rows = [];
|
|||
|
|
Root = new TreeModelNode(this, null!)
|
|||
|
|
{
|
|||
|
|
IsExpanded = true
|
|||
|
|
};
|
|||
|
|
ItemsSource = Rows;
|
|||
|
|
ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ItemContainerGeneratorStatusChanged(object? sender, EventArgs e)
|
|||
|
|
{
|
|||
|
|
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated && PendingFocusNode != null)
|
|||
|
|
{
|
|||
|
|
TreeModelListViewItem? item = ItemContainerGenerator.ContainerFromItem(PendingFocusNode) as TreeModelListViewItem;
|
|||
|
|
item?.Focus();
|
|||
|
|
PendingFocusNode = null!;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override DependencyObject GetContainerForItemOverride()
|
|||
|
|
{
|
|||
|
|
return new TreeModelListViewItem();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override bool IsItemItsOwnContainerOverride(object item)
|
|||
|
|
{
|
|||
|
|
return item is TreeModelListViewItem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
|
|||
|
|
{
|
|||
|
|
if (element is TreeModelListViewItem { } ti && item is TreeModelNode { } node)
|
|||
|
|
{
|
|||
|
|
ti.Node = item as TreeModelNode;
|
|||
|
|
base.PrepareContainerForItemOverride(element, node.Content);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void SetIsExpanded(TreeModelNode node, bool value)
|
|||
|
|
{
|
|||
|
|
if (value)
|
|||
|
|
{
|
|||
|
|
if (!node.IsExpandedOnce)
|
|||
|
|
{
|
|||
|
|
node.IsExpandedOnce = true;
|
|||
|
|
node.AssignIsExpanded(value);
|
|||
|
|
CreateChildrenNodes(node);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
node.AssignIsExpanded(value);
|
|||
|
|
CreateChildrenRows(node);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
DropChildrenRows(node, false);
|
|||
|
|
node.AssignIsExpanded(value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void CreateChildrenNodes(TreeModelNode node)
|
|||
|
|
{
|
|||
|
|
IEnumerable? children = GetChildren(node);
|
|||
|
|
if (children != null)
|
|||
|
|
{
|
|||
|
|
int rowIndex = Rows.IndexOf(node);
|
|||
|
|
node.ChildrenSource = (children as INotifyCollectionChanged)!;
|
|||
|
|
foreach (object obj in children)
|
|||
|
|
{
|
|||
|
|
TreeModelNode child = new(this, obj);
|
|||
|
|
child.HasChildren = HasChildren(child);
|
|||
|
|
node.Children.Add(child);
|
|||
|
|
}
|
|||
|
|
Rows.InsertRange(rowIndex + 1, [.. node.Children]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void CreateChildrenRows(TreeModelNode node)
|
|||
|
|
{
|
|||
|
|
int index = Rows.IndexOf(node);
|
|||
|
|
if (index >= 0 || node == Root) // ignore invisible nodes
|
|||
|
|
{
|
|||
|
|
var nodes = node.AllVisibleChildren.ToArray();
|
|||
|
|
Rows.InsertRange(index + 1, nodes);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void DropChildrenRows(TreeModelNode node, bool removeParent)
|
|||
|
|
{
|
|||
|
|
int start = Rows.IndexOf(node);
|
|||
|
|
if (start >= 0 || node == Root) // ignore invisible nodes
|
|||
|
|
{
|
|||
|
|
int count = node.VisibleChildrenCount;
|
|||
|
|
if (removeParent)
|
|||
|
|
{
|
|||
|
|
count++;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
start++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Rows.RemoveRange(start, count);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerable? GetChildren(TreeModelNode parent)
|
|||
|
|
{
|
|||
|
|
if (Model != null)
|
|||
|
|
{
|
|||
|
|
return Model.GetChildren(parent?.Content!);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool HasChildren(TreeModelNode parent)
|
|||
|
|
{
|
|||
|
|
if (parent == Root)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
else if (Model != null)
|
|||
|
|
{
|
|||
|
|
return Model.HasChildren(parent?.Content!);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void InsertNewNode(TreeModelNode parent, object tag, int rowIndex, int index)
|
|||
|
|
{
|
|||
|
|
TreeModelNode node = new(this, tag);
|
|||
|
|
if (index >= 0 && index < parent.Children.Count)
|
|||
|
|
{
|
|||
|
|
parent.Children.Insert(index, node);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
index = parent.Children.Count;
|
|||
|
|
parent.Children.Add(node);
|
|||
|
|
}
|
|||
|
|
Rows.Insert(rowIndex + index + 1, node);
|
|||
|
|
}
|
|||
|
|
}
|