mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-21 08:32:21 +08:00
Add template generate items for tree
This commit is contained in:
@@ -115,11 +115,11 @@ internal class DefaultTreeViewInteractionHandler : ITreeViewInteractionHandler
|
||||
|
||||
if (e.InitialPressMouseButton == MouseButton.Left && item != null)
|
||||
{
|
||||
// if (item.PointInHeaderBounds(e))
|
||||
// {
|
||||
// Click(item);
|
||||
// e.Handled = true;
|
||||
// }
|
||||
if (item.PointInHeaderBounds(e))
|
||||
{
|
||||
Click(item);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,5 +5,6 @@ public interface ITreeViewItemData : ITreeNode<ITreeViewItemData>
|
||||
bool? IsChecked { get; }
|
||||
bool IsSelected { get; }
|
||||
bool IsExpanded { get; }
|
||||
bool IsIndicatorEnabled { get; }
|
||||
string? GroupName { get; }
|
||||
}
|
||||
|
||||
@@ -290,11 +290,7 @@ public partial class TreeView
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == DefaultCheckedPathsProperty)
|
||||
{
|
||||
ConfigureDefaultCheckedPaths();
|
||||
}
|
||||
else if (change.Property == DefaultSelectedPathsProperty)
|
||||
if (change.Property == DefaultSelectedPathsProperty)
|
||||
{
|
||||
ConfigureDefaultSelectedPaths();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.VisualTree;
|
||||
using DynamicData;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
@@ -198,6 +197,21 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共事件定义
|
||||
|
||||
public static readonly RoutedEvent<TreeViewCheckedItemsChangedEventArgs> CheckedItemsChangedEvent =
|
||||
RoutedEvent.Register<TreeView, TreeViewCheckedItemsChangedEventArgs>(
|
||||
nameof(CheckedItemsChangedEvent),
|
||||
RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<TreeViewCheckedItemsChangedEventArgs>? CheckedItemsChanged
|
||||
{
|
||||
add => AddHandler(CheckedItemsChangedEvent, value);
|
||||
remove => RemoveHandler(CheckedItemsChangedEvent, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部属性定义
|
||||
|
||||
internal static readonly StyledProperty<TimeSpan> MotionDurationProperty =
|
||||
@@ -218,7 +232,7 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
#endregion
|
||||
|
||||
private readonly Dictionary<TreeViewItem, CompositeDisposable> _itemsBindingDisposables = new();
|
||||
|
||||
private static readonly IList<ITreeViewItemData> Empty = Array.Empty<ITreeViewItemData>();
|
||||
private IList<ITreeViewItemData>? _checkedItems;
|
||||
private bool _syncingCheckedItems;
|
||||
|
||||
@@ -235,7 +249,7 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
{
|
||||
InteractionHandler = interactionHandler ?? throw new ArgumentNullException(nameof(interactionHandler));
|
||||
this.RegisterResources();
|
||||
Items.CollectionChanged += HandleCollectionChanged;
|
||||
LogicalChildren.CollectionChanged += HandleCollectionChanged;
|
||||
}
|
||||
|
||||
private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -321,8 +335,30 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var checkedItems = DoCheckedSubTree(item);
|
||||
try
|
||||
{
|
||||
_syncingCheckedItems = true;
|
||||
foreach (var checkedItem in checkedItems)
|
||||
{
|
||||
if (!CheckedItems.Contains(checkedItem))
|
||||
{
|
||||
CheckedItems.Add(checkedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingCheckedItems = false;
|
||||
}
|
||||
}
|
||||
|
||||
private ISet<TreeViewItem> DoCheckedSubTree(TreeViewItem item)
|
||||
{
|
||||
var checkedItems = new HashSet<TreeViewItem>();
|
||||
item.SetCurrentValue(TreeViewItem.IsCheckedProperty, true);
|
||||
checkedItems.Add(item);
|
||||
if (item.Presenter?.Panel == null && this.GetVisualRoot() is ILayoutRoot visualRoot)
|
||||
{
|
||||
var layoutManager = visualRoot.GetLayoutManager();
|
||||
@@ -336,21 +372,46 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
var control = TreeContainerFromItem(childItem);
|
||||
if (control is TreeViewItem treeViewItem && treeViewItem.IsEffectiveCheckable())
|
||||
{
|
||||
CheckedSubTree(treeViewItem);
|
||||
var childCheckedItems = DoCheckedSubTree(treeViewItem);
|
||||
checkedItems.UnionWith(childCheckedItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetupParentNodeCheckedStatus(item);
|
||||
var (checkedParentItems, _) = SetupParentNodeCheckedStatus(item);
|
||||
checkedItems.UnionWith(checkedParentItems);
|
||||
return checkedItems;
|
||||
}
|
||||
|
||||
|
||||
public void UnCheckedSubTree(TreeViewItem item)
|
||||
{
|
||||
if (!item.IsEffectiveCheckable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var unCheckedItems = DoUnCheckedSubTree(item);
|
||||
try
|
||||
{
|
||||
_syncingCheckedItems = true;
|
||||
foreach (var unCheckedItem in unCheckedItems)
|
||||
{
|
||||
CheckedItems.Remove(unCheckedItem);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingCheckedItems = false;
|
||||
}
|
||||
}
|
||||
|
||||
public ISet<TreeViewItem> DoUnCheckedSubTree(TreeViewItem item)
|
||||
{
|
||||
var unCheckedItems = new HashSet<TreeViewItem>();
|
||||
if (item.IsChecked == true)
|
||||
{
|
||||
unCheckedItems.Add(item);
|
||||
}
|
||||
item.SetCurrentValue(TreeViewItem.IsCheckedProperty, false);
|
||||
if (item.Presenter?.Panel == null && this.GetVisualRoot() is ILayoutRoot visualRoot)
|
||||
{
|
||||
@@ -365,12 +426,14 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
var control = TreeContainerFromItem(childItem);
|
||||
if (control is TreeViewItem treeViewItem && treeViewItem.IsEffectiveCheckable())
|
||||
{
|
||||
UnCheckedSubTree(treeViewItem);
|
||||
var childUnCheckedItems = DoUnCheckedSubTree(treeViewItem);
|
||||
unCheckedItems.UnionWith(childUnCheckedItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetupParentNodeCheckedStatus(item);
|
||||
var (_, unCheckedParentItems) = SetupParentNodeCheckedStatus(item);
|
||||
unCheckedItems.UnionWith(unCheckedParentItems);
|
||||
return unCheckedItems;
|
||||
}
|
||||
|
||||
private void UpdatePseudoClasses()
|
||||
@@ -405,45 +468,9 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
treeViewItem.OwnerTreeView = this;
|
||||
var disposables = new CompositeDisposable(8);
|
||||
|
||||
if (item != null && item is not Visual)
|
||||
if (item != null && item is not Visual && item is ITreeViewItemData treeViewItemData)
|
||||
{
|
||||
if (!treeViewItem.IsSet(TreeViewItem.HeaderProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.HeaderProperty, item);
|
||||
}
|
||||
|
||||
if (item is ITreeViewItemData treeViewItemData)
|
||||
{
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IconProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IconProperty, treeViewItemData.Icon);
|
||||
}
|
||||
|
||||
if (treeViewItem.ItemKey == null)
|
||||
{
|
||||
treeViewItem.ItemKey = treeViewItemData.ItemKey;
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsCheckedProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, treeViewItemData.IsChecked);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsSelectedProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, treeViewItemData.IsSelected);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsEnabledProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsEnabledProperty, treeViewItemData.IsEnabled);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsExpandedProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsExpandedProperty, treeViewItemData.IsExpanded);
|
||||
}
|
||||
}
|
||||
TreeViewItem.ApplyNodeData(treeViewItem, treeViewItemData);
|
||||
}
|
||||
|
||||
if (ItemTemplate != null)
|
||||
@@ -491,144 +518,7 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
InteractionHandler.Detach(this);
|
||||
}
|
||||
|
||||
private void SetupParentNodeCheckedStatus(TreeViewItem item)
|
||||
{
|
||||
var parent = item.Parent;
|
||||
while (parent is TreeViewItem parentTreeItem)
|
||||
{
|
||||
var isAllChecked = false;
|
||||
var isAnyChecked = false;
|
||||
|
||||
if (parentTreeItem.Items.Count > 0)
|
||||
{
|
||||
isAllChecked = parentTreeItem.Items.All(childItem =>
|
||||
{
|
||||
if (childItem is TreeViewItem treeViewItem)
|
||||
{
|
||||
return treeViewItem.IsChecked.HasValue && treeViewItem.IsChecked.Value;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
isAnyChecked = parentTreeItem.Items.Any(childItem =>
|
||||
{
|
||||
if (childItem is TreeViewItem treeViewItem)
|
||||
{
|
||||
return treeViewItem.IsEnabled && (!treeViewItem.IsChecked.HasValue || treeViewItem.IsChecked.HasValue && treeViewItem.IsChecked.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (isAllChecked)
|
||||
{
|
||||
parentTreeItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, true);
|
||||
}
|
||||
else if (isAnyChecked)
|
||||
{
|
||||
parentTreeItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
parentTreeItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, false);
|
||||
}
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureDefaultExpandedPaths()
|
||||
{
|
||||
if (DefaultExpandedPaths != null)
|
||||
{
|
||||
foreach (var defaultExpandedPath in DefaultExpandedPaths)
|
||||
{
|
||||
var pathNodes = FindTreeItemByPath(defaultExpandedPath);
|
||||
ExpandTreeViewPaths(pathNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureDefaultSelectedPaths()
|
||||
{
|
||||
if (DefaultSelectedPaths != null)
|
||||
{
|
||||
foreach (var defaultSelectedPath in DefaultSelectedPaths)
|
||||
{
|
||||
var pathNodes = FindTreeItemByPath(defaultSelectedPath);
|
||||
if (pathNodes.Count > 0)
|
||||
{
|
||||
SelectedItems.Add(pathNodes.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureDefaultCheckedPaths()
|
||||
{
|
||||
if (DefaultCheckedPaths != null)
|
||||
{
|
||||
foreach (var defaultCheckedPath in DefaultCheckedPaths)
|
||||
{
|
||||
var pathNodes = FindTreeItemByPath(defaultCheckedPath);
|
||||
if (pathNodes.Count > 0)
|
||||
{
|
||||
var current = pathNodes.Last();
|
||||
var nodes = new List<ITreeViewItemData>();
|
||||
nodes.AddRange(CollectionChildItems(current));
|
||||
CheckedItems.AddRange(nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IList<ITreeViewItemData> CollectionChildItems(ITreeViewItemData item)
|
||||
{
|
||||
var list = new List<ITreeViewItemData>();
|
||||
list.Add(item);
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
var childList = CollectionChildItems(child);
|
||||
list.AddRange(childList);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<TreeViewItem> ExpandTreeViewPaths(IList<ITreeViewItemData> pathNodes)
|
||||
{
|
||||
if (pathNodes.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<TreeViewItem> items = new List<TreeViewItem>();
|
||||
try
|
||||
{
|
||||
ItemsControl current = this;
|
||||
foreach (var pathNode in pathNodes)
|
||||
{
|
||||
var child = GetTreeViewItemContainer(pathNode, current);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
items.Add(child);
|
||||
current = child;
|
||||
child.IsMotionEnabled = false;
|
||||
child.IsExpanded = true;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.IsMotionEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TreeViewItem? GetTreeViewItemContainer(ITreeViewItemData childNode, ItemsControl current)
|
||||
{
|
||||
if (current.Presenter?.Panel == null && this.GetVisualRoot() is ILayoutRoot visualRoot)
|
||||
@@ -639,57 +529,6 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
return current.ContainerFromItem(childNode) as TreeViewItem;
|
||||
}
|
||||
|
||||
private IList<ITreeViewItemData> FindTreeItemByPath(TreeNodePath treeNodePath)
|
||||
{
|
||||
if (treeNodePath.Length == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
var segments = treeNodePath.Segments;
|
||||
IList<ITreeViewItemData> items = Items.OfType<ITreeViewItemData>().ToList();
|
||||
IList<ITreeViewItemData> pathNodes = new List<ITreeViewItemData>();
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
bool childFound = false;
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
if (item.ItemKey != null && item.ItemKey.Value == segment)
|
||||
{
|
||||
items = item.Children;
|
||||
childFound = true;
|
||||
pathNodes.Add(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!childFound)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return pathNodes;
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
||||
if (DefaultSelectedPaths?.Count > 0 || DefaultCheckedPaths?.Count > 0)
|
||||
{
|
||||
CollapseAll(false);
|
||||
}
|
||||
|
||||
if (IsDefaultExpandAll)
|
||||
{
|
||||
ExpandAll(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureDefaultExpandedPaths();
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeToCheckedItems()
|
||||
{
|
||||
if (_checkedItems is INotifyCollectionChanged incc)
|
||||
@@ -772,14 +611,14 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
break;
|
||||
}
|
||||
|
||||
// if (added?.Count > 0 || removed?.Count > 0)
|
||||
// {
|
||||
// var changed = new SelectionChangedEventArgs(
|
||||
// SelectingItemsControl.SelectionChangedEvent,
|
||||
// removed ?? Empty,
|
||||
// added ?? Empty);
|
||||
// RaiseEvent(changed);
|
||||
// }
|
||||
if (added?.Count > 0 || removed?.Count > 0)
|
||||
{
|
||||
var changed = new TreeViewCheckedItemsChangedEventArgs(
|
||||
CheckedItemsChangedEvent,
|
||||
removed ?? Empty,
|
||||
added ?? Empty);
|
||||
RaiseEvent(changed);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckedItemsAdded(IList<ITreeViewItemData> items)
|
||||
@@ -810,4 +649,264 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS
|
||||
{
|
||||
container.SetCurrentValue(TreeViewItem.IsCheckedProperty, isChecked);
|
||||
}
|
||||
|
||||
#region 默认展开选中
|
||||
|
||||
private IList<ITreeViewItemData> FindTreeItemByPath(TreeNodePath treeNodePath)
|
||||
{
|
||||
if (treeNodePath.Length == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
var segments = treeNodePath.Segments;
|
||||
IList<ITreeViewItemData> items = Items.OfType<ITreeViewItemData>().ToList();
|
||||
IList<ITreeViewItemData> pathNodes = new List<ITreeViewItemData>();
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
bool childFound = false;
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
if (item.ItemKey != null && item.ItemKey.Value == segment)
|
||||
{
|
||||
items = item.Children;
|
||||
childFound = true;
|
||||
pathNodes.Add(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!childFound)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return pathNodes;
|
||||
}
|
||||
|
||||
private List<TreeViewItem> ExpandTreeViewPaths(IList<ITreeViewItemData> pathNodes, bool expandLastRecursively = false)
|
||||
{
|
||||
if (pathNodes.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<TreeViewItem> items = new List<TreeViewItem>();
|
||||
try
|
||||
{
|
||||
ItemsControl current = this;
|
||||
for (var i = 0; i < pathNodes.Count; i++)
|
||||
{
|
||||
var pathNode = pathNodes[i];
|
||||
var child = GetTreeViewItemContainer(pathNode, current);
|
||||
if (child != null)
|
||||
{
|
||||
items.Add(child);
|
||||
current = child;
|
||||
child.IsMotionEnabled = false;
|
||||
if (i == pathNodes.Count - 1 && expandLastRecursively)
|
||||
{
|
||||
ExpandSubTree(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
child.SetCurrentValue(TreeViewItem.IsExpandedProperty, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.IsMotionEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<TreeViewItem> CollapseTreeViewPaths(IList<ITreeViewItemData> pathNodes, bool collapseLastRecursively = false)
|
||||
{
|
||||
if (pathNodes.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<TreeViewItem> items = new List<TreeViewItem>();
|
||||
try
|
||||
{
|
||||
ItemsControl current = this;
|
||||
for (var i = 0; i < pathNodes.Count; i++)
|
||||
{
|
||||
var pathNode = pathNodes[i];
|
||||
var child = GetTreeViewItemContainer(pathNode, current);
|
||||
if (child != null)
|
||||
{
|
||||
items.Add(child);
|
||||
current = child;
|
||||
child.IsMotionEnabled = false;
|
||||
if (i == pathNodes.Count - 1 && collapseLastRecursively)
|
||||
{
|
||||
CollapseSubTree(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
child.SetCurrentValue(TreeViewItem.IsExpandedProperty, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.IsMotionEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (ISet<TreeViewItem>, ISet<TreeViewItem>) SetupParentNodeCheckedStatus(TreeViewItem item)
|
||||
{
|
||||
var parent = item.Parent;
|
||||
var checkedParents = new HashSet<TreeViewItem>();
|
||||
var unCheckedParents = new HashSet<TreeViewItem>();
|
||||
while (parent is TreeViewItem parentTreeItem && parentTreeItem.IsEnabled)
|
||||
{
|
||||
var isAllChecked = false;
|
||||
var isAnyChecked = false;
|
||||
|
||||
if (parentTreeItem.Items.Count > 0)
|
||||
{
|
||||
isAllChecked = parentTreeItem.Items.All(childItem =>
|
||||
{
|
||||
if (childItem != null)
|
||||
{
|
||||
var container = TreeContainerFromItem(childItem);
|
||||
if (container is TreeViewItem treeViewItem)
|
||||
{
|
||||
return !treeViewItem.IsEffectiveCheckable() || treeViewItem.IsChecked.HasValue && treeViewItem.IsChecked.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
isAnyChecked = parentTreeItem.Items.Any(childItem =>
|
||||
{
|
||||
if (childItem != null)
|
||||
{
|
||||
var container = TreeContainerFromItem(childItem);
|
||||
if (container is TreeViewItem treeViewItem)
|
||||
{
|
||||
return treeViewItem.IsEffectiveCheckable() && (!treeViewItem.IsChecked.HasValue || treeViewItem.IsChecked.HasValue && treeViewItem.IsChecked.Value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (parentTreeItem.IsChecked == true && !isAllChecked)
|
||||
{
|
||||
unCheckedParents.Add(parentTreeItem);
|
||||
}
|
||||
|
||||
if (isAllChecked)
|
||||
{
|
||||
parentTreeItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, true);
|
||||
}
|
||||
else if (isAnyChecked)
|
||||
{
|
||||
parentTreeItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
parentTreeItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, false);
|
||||
}
|
||||
|
||||
if (parentTreeItem.IsChecked == true)
|
||||
{
|
||||
checkedParents.Add(parentTreeItem);
|
||||
}
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
return (checkedParents, unCheckedParents);
|
||||
}
|
||||
|
||||
private void ConfigureDefaultCheckedPaths()
|
||||
{
|
||||
if (DefaultCheckedPaths != null)
|
||||
{
|
||||
foreach (var defaultCheckedPath in DefaultCheckedPaths)
|
||||
{
|
||||
var pathNodes = FindTreeItemByPath(defaultCheckedPath);
|
||||
if (pathNodes.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var items = ExpandTreeViewPaths(pathNodes, true);
|
||||
if (items.Count > 0)
|
||||
{
|
||||
var target = items.Last();
|
||||
target.SetCurrentValue(TreeViewItem.IsCheckedProperty, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CollapseTreeViewPaths(pathNodes, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureDefaultExpandedPaths()
|
||||
{
|
||||
if (DefaultExpandedPaths != null)
|
||||
{
|
||||
foreach (var defaultExpandedPath in DefaultExpandedPaths)
|
||||
{
|
||||
var pathNodes = FindTreeItemByPath(defaultExpandedPath);
|
||||
ExpandTreeViewPaths(pathNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureDefaultSelectedPaths()
|
||||
{
|
||||
if (DefaultSelectedPaths != null)
|
||||
{
|
||||
foreach (var defaultSelectedPath in DefaultSelectedPaths)
|
||||
{
|
||||
var pathNodes = FindTreeItemByPath(defaultSelectedPath);
|
||||
|
||||
if (pathNodes.Count > 0)
|
||||
{
|
||||
var targetNode = pathNodes.Last();
|
||||
if (!SelectedItems.Contains(targetNode))
|
||||
{
|
||||
SelectedItems.Add(targetNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
||||
ConfigureDefaultSelectedPaths();
|
||||
ConfigureDefaultCheckedPaths();
|
||||
|
||||
if (IsDefaultExpandAll)
|
||||
{
|
||||
ExpandAll(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureDefaultExpandedPaths();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class TreeViewCheckedItemsChangedEventArgs : RoutedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TreeViewCheckedItemsChangedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="routedEvent">The event being raised.</param>
|
||||
/// <param name="removedItems">The items removed from the check.</param>
|
||||
/// <param name="addedItems">The items added to the check.</param>
|
||||
public TreeViewCheckedItemsChangedEventArgs(RoutedEvent routedEvent, IList<ITreeViewItemData> removedItems, IList<ITreeViewItemData> addedItems)
|
||||
: base(routedEvent)
|
||||
{
|
||||
RemovedItems = removedItems;
|
||||
AddedItems = addedItems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items that were added to the selection.
|
||||
/// </summary>
|
||||
public IList<ITreeViewItemData> AddedItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items that were removed from the selection.
|
||||
/// </summary>
|
||||
public IList<ITreeViewItemData> RemovedItems { get; }
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using AtomUI.Controls.Themes;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reactive.Disposables;
|
||||
using AtomUI.Controls.Themes;
|
||||
using AtomUI.Controls.Utils;
|
||||
using AtomUI.Data;
|
||||
using AtomUI.IconPkg;
|
||||
using AtomUI.MotionScene;
|
||||
using AtomUI.Theme.Data;
|
||||
@@ -265,6 +268,7 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
||||
private readonly BorderRenderHelper _borderRenderHelper;
|
||||
private TreeViewItemHeader? _header;
|
||||
private IDisposable? _borderThicknessDisposable;
|
||||
private readonly Dictionary<TreeViewItem, CompositeDisposable> _itemsBindingDisposables = new();
|
||||
|
||||
static TreeViewItem()
|
||||
{
|
||||
@@ -281,7 +285,29 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
||||
|
||||
public TreeViewItem()
|
||||
{
|
||||
_borderRenderHelper = new BorderRenderHelper();
|
||||
_borderRenderHelper = new BorderRenderHelper();
|
||||
LogicalChildren.CollectionChanged += HandleItemsCollectionChanged;
|
||||
}
|
||||
|
||||
private void HandleItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
if (item is TreeViewItem treeViewItem)
|
||||
{
|
||||
if (_itemsBindingDisposables.TryGetValue(treeViewItem, out var disposable))
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
_itemsBindingDisposables.Remove(treeViewItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
@@ -456,44 +482,6 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
if (OwnerTreeView is not null)
|
||||
{
|
||||
if (IsChecked != false)
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
SetCurrentValue(IsCheckedProperty, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnerTreeView.CheckedSubTree(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var allChecked = false;
|
||||
var hasAnyChecked = false;
|
||||
|
||||
if (Items.Count > 0)
|
||||
{
|
||||
allChecked = Items.All(item => OwnerTreeView.CheckedItems.Contains(item));
|
||||
hasAnyChecked = Items.Any(item => OwnerTreeView.CheckedItems.Contains(item));
|
||||
}
|
||||
if (allChecked)
|
||||
{
|
||||
SetCurrentValue(IsCheckedProperty, true);
|
||||
}
|
||||
else if (hasAnyChecked)
|
||||
{
|
||||
SetCurrentValue(IsCheckedProperty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
@@ -543,7 +531,7 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
||||
if (!IsLeaf && !isLastChild && _itemsPresenterMotionActor?.IsVisible == true)
|
||||
{
|
||||
var switcherMiddleBottom = new Point(switcherButtonRect.X + switcherButtonRect.Width / 2, switcherButtonRect.Bottom);
|
||||
var blockStartPoint = new Point(switcherMiddleBottom.X, switcherMiddleBottom.Y);
|
||||
var blockStartPoint = new Point(switcherMiddleBottom.X, switcherMiddleBottom.Y);
|
||||
var blockEndPoint = new Point(blockStartPoint.X, DesiredSize.Height);
|
||||
context.DrawLine(new Pen(BorderBrush, penWidth), blockStartPoint, blockEndPoint);
|
||||
}
|
||||
@@ -582,11 +570,6 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
||||
includeChildren ? Bounds.Height : _header?.Bounds.Height ?? default));
|
||||
}
|
||||
|
||||
internal Thickness FrameMargin()
|
||||
{
|
||||
return _header?.Margin ?? default;
|
||||
}
|
||||
|
||||
internal bool IsInDragBounds(Point point)
|
||||
{
|
||||
return GetDragBounds(true).Contains(point);
|
||||
@@ -633,4 +616,111 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
||||
}
|
||||
|
||||
internal void RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
|
||||
|
||||
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||
{
|
||||
return new TreeViewItem();
|
||||
}
|
||||
|
||||
protected override bool NeedsContainerOverride(
|
||||
object? item,
|
||||
int index,
|
||||
out object? recycleKey)
|
||||
{
|
||||
return NeedsContainer<TreeViewItem>(item, out recycleKey);
|
||||
}
|
||||
|
||||
protected override void ContainerForItemPreparedOverride(
|
||||
Control container,
|
||||
object? item,
|
||||
int index)
|
||||
{
|
||||
base.ContainerForItemPreparedOverride(container, item, index);
|
||||
if (container is TreeViewItem treeViewItem)
|
||||
{
|
||||
treeViewItem.OwnerTreeView = OwnerTreeView;
|
||||
var disposables = new CompositeDisposable(8);
|
||||
|
||||
if (item != null && item is not Visual && item is ITreeViewItemData treeViewItemData)
|
||||
{
|
||||
ApplyNodeData(treeViewItem, treeViewItemData);
|
||||
}
|
||||
|
||||
if (ItemTemplate != null)
|
||||
{
|
||||
disposables.Add(BindUtils.RelayBind(this, ItemTemplateProperty, treeViewItem, TreeViewItem.HeaderTemplateProperty));
|
||||
}
|
||||
|
||||
disposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, treeViewItem, TreeViewItem.IsMotionEnabledProperty));
|
||||
disposables.Add(BindUtils.RelayBind(this, NodeHoverModeProperty, treeViewItem, TreeViewItem.NodeHoverModeProperty));
|
||||
disposables.Add(BindUtils.RelayBind(this, IsShowLineProperty, treeViewItem, TreeViewItem.IsShowLineProperty));
|
||||
disposables.Add(BindUtils.RelayBind(this, IsShowIconProperty, treeViewItem, TreeViewItem.IsShowIconProperty));
|
||||
disposables.Add(BindUtils.RelayBind(this, IsShowLeafIconProperty, treeViewItem,
|
||||
TreeViewItem.IsShowLeafIconProperty));
|
||||
disposables.Add(BindUtils.RelayBind(this, IsSwitcherRotationProperty, treeViewItem, TreeViewItem.IsSwitcherRotationProperty));
|
||||
disposables.Add(BindUtils.RelayBind(this, ToggleTypeProperty, treeViewItem, TreeViewItem.ToggleTypeProperty));
|
||||
|
||||
PrepareTreeViewItem(treeViewItem, item, index, disposables);
|
||||
|
||||
if (_itemsBindingDisposables.TryGetValue(treeViewItem, out var oldDisposables))
|
||||
{
|
||||
oldDisposables.Dispose();
|
||||
_itemsBindingDisposables.Remove(treeViewItem);
|
||||
}
|
||||
_itemsBindingDisposables.Add(treeViewItem, disposables);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(container), "The container type is incorrect, it must be type TreeViewItem.");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void PrepareTreeViewItem(TreeViewItem treeViewItem, object? item, int index, CompositeDisposable compositeDisposable)
|
||||
{
|
||||
}
|
||||
|
||||
internal static void ApplyNodeData(TreeViewItem treeViewItem, ITreeViewItemData treeViewItemData)
|
||||
{
|
||||
if (treeViewItemData is not Visual)
|
||||
{
|
||||
if (!treeViewItem.IsSet(TreeViewItem.HeaderProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.HeaderProperty, treeViewItem);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IconProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IconProperty, treeViewItemData.Icon);
|
||||
}
|
||||
|
||||
if (treeViewItem.ItemKey == null)
|
||||
{
|
||||
treeViewItem.ItemKey = treeViewItemData.ItemKey;
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsCheckedProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, treeViewItemData.IsChecked);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsSelectedProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, treeViewItemData.IsSelected);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsEnabledProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsEnabledProperty, treeViewItemData.IsEnabled);
|
||||
}
|
||||
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsExpandedProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsExpandedProperty, treeViewItemData.IsExpanded);
|
||||
}
|
||||
if (!treeViewItem.IsSet(TreeViewItem.IsIndicatorEnabledProperty))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsIndicatorEnabledProperty, treeViewItemData.IsIndicatorEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@ public class TreeViewItemData : ITreeViewItemData
|
||||
public TreeNodeKey? ItemKey { get; set; }
|
||||
public object? Header { get; set; }
|
||||
public Icon? Icon { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool? IsChecked { get; set; }
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool? IsChecked { get; set; } = false;
|
||||
public bool IsSelected { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
public bool IsIndicatorEnabled { get; set; } = true;
|
||||
public string? GroupName { get; set; }
|
||||
|
||||
public IList<ITreeViewItemData> Children { get; } = new List<ITreeViewItemData>();
|
||||
public IList<ITreeViewItemData> Children { get; set; } = new List<ITreeViewItemData>();
|
||||
}
|
||||
@@ -3,13 +3,10 @@ using AtomUI.Animations;
|
||||
using AtomUI.Controls.Themes;
|
||||
using AtomUI.Controls.Utils;
|
||||
using AtomUI.IconPkg;
|
||||
using AtomUI.Theme.Data;
|
||||
using AtomUI.Theme.Styling;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
|
||||
Reference in New Issue
Block a user