diff --git a/src/AtomUI.Controls/TreeView/DefaultTreeViewInteractionHandler.cs b/src/AtomUI.Controls/TreeView/DefaultTreeViewInteractionHandler.cs index 7c5bc0aa0..9b85e803d 100644 --- a/src/AtomUI.Controls/TreeView/DefaultTreeViewInteractionHandler.cs +++ b/src/AtomUI.Controls/TreeView/DefaultTreeViewInteractionHandler.cs @@ -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; + } } } diff --git a/src/AtomUI.Controls/TreeView/ITreeViewItemData.cs b/src/AtomUI.Controls/TreeView/ITreeViewItemData.cs index b823a11a0..1d3d886a8 100644 --- a/src/AtomUI.Controls/TreeView/ITreeViewItemData.cs +++ b/src/AtomUI.Controls/TreeView/ITreeViewItemData.cs @@ -5,5 +5,6 @@ public interface ITreeViewItemData : ITreeNode bool? IsChecked { get; } bool IsSelected { get; } bool IsExpanded { get; } + bool IsIndicatorEnabled { get; } string? GroupName { get; } } diff --git a/src/AtomUI.Controls/TreeView/TreeView.DragAndDrop.cs b/src/AtomUI.Controls/TreeView/TreeView.DragAndDrop.cs index 646263cf2..3084e7cd3 100644 --- a/src/AtomUI.Controls/TreeView/TreeView.DragAndDrop.cs +++ b/src/AtomUI.Controls/TreeView/TreeView.DragAndDrop.cs @@ -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(); } diff --git a/src/AtomUI.Controls/TreeView/TreeView.cs b/src/AtomUI.Controls/TreeView/TreeView.cs index 5a0ee2c9f..42c9a61ba 100644 --- a/src/AtomUI.Controls/TreeView/TreeView.cs +++ b/src/AtomUI.Controls/TreeView/TreeView.cs @@ -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 CheckedItemsChangedEvent = + RoutedEvent.Register( + nameof(CheckedItemsChangedEvent), + RoutingStrategies.Bubble); + + public event EventHandler? CheckedItemsChanged + { + add => AddHandler(CheckedItemsChangedEvent, value); + remove => RemoveHandler(CheckedItemsChangedEvent, value); + } + + #endregion + #region 内部属性定义 internal static readonly StyledProperty MotionDurationProperty = @@ -218,7 +232,7 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS #endregion private readonly Dictionary _itemsBindingDisposables = new(); - + private static readonly IList Empty = Array.Empty(); private IList? _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 DoCheckedSubTree(TreeViewItem item) + { + var checkedItems = new HashSet(); 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 DoUnCheckedSubTree(TreeViewItem item) + { + var unCheckedItems = new HashSet(); + 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(); - nodes.AddRange(CollectionChildItems(current)); - CheckedItems.AddRange(nodes); - } - } - } - } - - private IList CollectionChildItems(ITreeViewItemData item) - { - var list = new List(); - list.Add(item); - foreach (var child in item.Children) - { - var childList = CollectionChildItems(child); - list.AddRange(childList); - } - - return list; - } - - private List ExpandTreeViewPaths(IList pathNodes) - { - if (pathNodes.Count == 0) - { - return []; - } - List items = new List(); - 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 FindTreeItemByPath(TreeNodePath treeNodePath) - { - if (treeNodePath.Length == 0) - { - return []; - } - var segments = treeNodePath.Segments; - IList items = Items.OfType().ToList(); - IList pathNodes = new List(); - 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 items) @@ -810,4 +649,264 @@ public partial class TreeView : AvaloniaTreeView, IMotionAwareControl, IControlS { container.SetCurrentValue(TreeViewItem.IsCheckedProperty, isChecked); } + + #region 默认展开选中 + + private IList FindTreeItemByPath(TreeNodePath treeNodePath) + { + if (treeNodePath.Length == 0) + { + return []; + } + var segments = treeNodePath.Segments; + IList items = Items.OfType().ToList(); + IList pathNodes = new List(); + 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 ExpandTreeViewPaths(IList pathNodes, bool expandLastRecursively = false) + { + if (pathNodes.Count == 0) + { + return []; + } + List items = new List(); + 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 CollapseTreeViewPaths(IList pathNodes, bool collapseLastRecursively = false) + { + if (pathNodes.Count == 0) + { + return []; + } + List items = new List(); + 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, ISet) SetupParentNodeCheckedStatus(TreeViewItem item) + { + var parent = item.Parent; + var checkedParents = new HashSet(); + var unCheckedParents = new HashSet(); + 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 } \ No newline at end of file diff --git a/src/AtomUI.Controls/TreeView/TreeViewCheckedItemsChangedEventArgs.cs b/src/AtomUI.Controls/TreeView/TreeViewCheckedItemsChangedEventArgs.cs new file mode 100644 index 000000000..5a05a4527 --- /dev/null +++ b/src/AtomUI.Controls/TreeView/TreeViewCheckedItemsChangedEventArgs.cs @@ -0,0 +1,30 @@ +using System.Collections; +using Avalonia.Interactivity; + +namespace AtomUI.Controls; + +public class TreeViewCheckedItemsChangedEventArgs : RoutedEventArgs +{ + /// + /// Initializes a new instance of the class. + /// + /// The event being raised. + /// The items removed from the check. + /// The items added to the check. + public TreeViewCheckedItemsChangedEventArgs(RoutedEvent routedEvent, IList removedItems, IList addedItems) + : base(routedEvent) + { + RemovedItems = removedItems; + AddedItems = addedItems; + } + + /// + /// Gets the items that were added to the selection. + /// + public IList AddedItems { get; } + + /// + /// Gets the items that were removed from the selection. + /// + public IList RemovedItems { get; } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/TreeView/TreeViewItem.cs b/src/AtomUI.Controls/TreeView/TreeViewItem.cs index f2a1de4f4..40cb7d11f 100644 --- a/src/AtomUI.Controls/TreeView/TreeViewItem.cs +++ b/src/AtomUI.Controls/TreeView/TreeViewItem.cs @@ -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 _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(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); + } + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TreeView/TreeViewItemData.cs b/src/AtomUI.Controls/TreeView/TreeViewItemData.cs index 08333f654..35d60f247 100644 --- a/src/AtomUI.Controls/TreeView/TreeViewItemData.cs +++ b/src/AtomUI.Controls/TreeView/TreeViewItemData.cs @@ -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 Children { get; } = new List(); + public IList Children { get; set; } = new List(); } \ No newline at end of file diff --git a/src/AtomUI.Controls/TreeView/TreeViewItemHeader.cs b/src/AtomUI.Controls/TreeView/TreeViewItemHeader.cs index 01bf946fb..973e2f542 100644 --- a/src/AtomUI.Controls/TreeView/TreeViewItemHeader.cs +++ b/src/AtomUI.Controls/TreeView/TreeViewItemHeader.cs @@ -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;