From b0a7f27b65e4df3fe014e92ebfe237ae2fb9e773 Mon Sep 17 00:00:00 2001 From: chinboy Date: Sun, 18 Jan 2026 22:41:33 +0800 Subject: [PATCH] feature(Cascader): Add ItemsSource support --- .../ViewModels/DataEntry/CascaderViewModel.cs | 9 ++ .../Views/DataEntry/CascaderShowCase.axaml | 20 +++- .../Views/DataEntry/CascaderShowCase.axaml.cs | 53 ++++++++- .../Cascader/CascaderView.cs | 101 ++++++++++++++---- .../Cascader/CascaderViewItem.cs | 33 +++--- .../Cascader/CascaderViewLevelList.cs | 48 ++++++--- 6 files changed, 208 insertions(+), 56 deletions(-) diff --git a/controlgallery/AtomUIGallery/ShowCases/ViewModels/DataEntry/CascaderViewModel.cs b/controlgallery/AtomUIGallery/ShowCases/ViewModels/DataEntry/CascaderViewModel.cs index 309b8c34a..e34dc342f 100644 --- a/controlgallery/AtomUIGallery/ShowCases/ViewModels/DataEntry/CascaderViewModel.cs +++ b/controlgallery/AtomUIGallery/ShowCases/ViewModels/DataEntry/CascaderViewModel.cs @@ -1,4 +1,5 @@ using AtomUI.Controls; +using AtomUI.Desktop.Controls; using ReactiveUI; namespace AtomUIGallery.ShowCases.ViewModels; @@ -12,6 +13,14 @@ public class CascaderViewModel : ReactiveObject, IRoutableViewModel, IActivatabl public string UrlPathSegment { get; } = ID.ToString(); + private List _basicCascaderNodes = []; + + public List BasicCascaderNodes + { + get => _basicCascaderNodes; + set => this.RaiseAndSetIfChanged(ref _basicCascaderNodes, value); + } + public CascaderViewModel(IScreen screen) { HostScreen = screen; diff --git a/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml b/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml index 166e83ea8..51e959cc0 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml +++ b/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml @@ -2,12 +2,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="AtomUIGallery.ShowCases.Views.CascaderShowCase" xmlns:atom="https://atomui.net" + xmlns:viewModels="clr-namespace:AtomUIGallery.ShowCases.ViewModels" xmlns:gallery="https://atomui.net/oss-controls/gallery"> + Description="The most basic usage."> @@ -21,9 +21,23 @@ - + + + + + + + + + + + \ No newline at end of file diff --git a/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml.cs b/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml.cs index ebe323cda..1050885c8 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml.cs +++ b/controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/CascaderShowCase.axaml.cs @@ -1,3 +1,4 @@ +using AtomUI.Desktop.Controls; using AtomUIGallery.ShowCases.ViewModels; using ReactiveUI; using ReactiveUI.Avalonia; @@ -8,7 +9,57 @@ public partial class CascaderShowCase : ReactiveUserControl { public CascaderShowCase() { - this.WhenActivated(disposables => { }); + this.WhenActivated(disposables => + { + if (DataContext is CascaderViewModel vm) + { + InitBasicCascaderViewData(vm); + } + }); InitializeComponent(); } + + private void InitBasicCascaderViewData(CascaderViewModel viewModel) + { + viewModel.BasicCascaderNodes = [ + new CascaderViewItemData() + { + Header = "Zhejiang", + Value = "zhejiang", + Children = [ + new CascaderViewItemData() + { + Header = "Hangzhou", + Value = "hangzhou", + Children = [ + new CascaderViewItemData() + { + Header = "West Lake", + Value = "xihu", + } + ] + } + ] + }, + new CascaderViewItemData() + { + Header = "Jiangsu", + Value = "jiangsu", + Children = [ + new CascaderViewItemData() + { + Header = "Nanjing", + Value = "nanjing", + Children = [ + new CascaderViewItemData() + { + Header = "Zhong Hua Men", + Value = "zhonghuamen", + } + ] + } + ] + } + ]; + } } \ No newline at end of file diff --git a/src/AtomUI.Desktop.Controls/Cascader/CascaderView.cs b/src/AtomUI.Desktop.Controls/Cascader/CascaderView.cs index f1f53fc67..7add5ef54 100644 --- a/src/AtomUI.Desktop.Controls/Cascader/CascaderView.cs +++ b/src/AtomUI.Desktop.Controls/Cascader/CascaderView.cs @@ -1059,32 +1059,78 @@ public partial class CascaderView : ItemsControl, IMotionAwareControl, IControlS { return; } - - // 保证当前 Item 的父亲都在了 - var parents = new List(); - var parent = cascaderViewItem.Parent; - while (parent != null && parent is CascaderViewItem parentCascaderItem) - { - parents.Add(parentCascaderItem); - parent = parent.Parent; - } - - parents.Reverse(); try { _ignoreExpandAndCollapse = true; - foreach (var parentItem in parents) + + if (cascaderViewItem.ItemsSource == null) { - if (!parentItem.IsExpanded) + // 保证当前 Item 的父亲都在了 + var parents = new List(); + var parent = cascaderViewItem.Parent; + while (parent != null && parent is CascaderViewItem parentCascaderItem) { - var parentList = new CascaderViewLevelList() + parents.Add(parentCascaderItem); + parent = parent.Parent; + } + + parents.Reverse(); + + foreach (var parentItem in parents) + { + if (!parentItem.IsExpanded) { - ItemsPanel = ItemsPanel, - }; - BindUtils.RelayBind(this, IsMotionEnabledProperty, parentList, CascaderViewLevelList.IsMotionEnabledProperty); - parentList.ItemsSource = parentItem.Items; - _itemsPanel.Children.Add(parentList); - parentItem.IsExpanded = true; + var parentList = new CascaderViewLevelList() + { + ItemsPanel = ItemsPanel, + OwnerView = this + }; + if (parentItem.ItemsSource == null) + { + foreach (var item in parentItem.Items) + { + parentList.Items.Add(item); + } + } + + BindUtils.RelayBind(parentItem, CascaderViewItem.ItemsSourceProperty, parentList, + CascaderViewLevelList.ItemsSourceProperty); + BindUtils.RelayBind(parentItem, CascaderViewItem.ItemTemplateProperty, parentList, + CascaderViewLevelList.ItemTemplateProperty); + _itemsPanel.Children.Add(parentList); + parentItem.IsExpanded = true; + } + } + } + else + { + // 保证当前 Item 的父亲都在了 + var parents = new List(); + var parent = cascaderViewItem.DataContext as ICascaderViewItemData; + while (parent != null) + { + parents.Add(parent); + parent = parent.ParentNode as ICascaderViewItemData; + } + parents.Reverse(); + foreach (var parentItem in parents) + { + if (!parentItem.IsExpanded) + { + var parentList = new CascaderViewLevelList() + { + ItemsPanel = ItemsPanel, + OwnerView = this + }; + + BindUtils.RelayBind(this, ItemTemplateProperty, parentList, + CascaderViewLevelList.ItemTemplateProperty); + _itemsPanel.Children.Add(parentList); + if (parentItem is CascaderViewItemData cascaderViewItemData) + { + cascaderViewItemData.IsExpanded = true; + } + } } } @@ -1099,6 +1145,7 @@ public partial class CascaderView : ItemsControl, IMotionAwareControl, IControlS if (_itemsPanel.Children[count] is CascaderViewLevelList levelList) { levelList.ItemsSource = null; + levelList.Items.Clear(); } _itemsPanel.Children.RemoveAt(count); } @@ -1106,9 +1153,18 @@ public partial class CascaderView : ItemsControl, IMotionAwareControl, IControlS var childList = new CascaderViewLevelList() { ItemsPanel = ItemsPanel, + OwnerView = this }; - BindUtils.RelayBind(this, IsMotionEnabledProperty, childList, CascaderViewLevelList.IsMotionEnabledProperty); - childList.ItemsSource = cascaderViewItem.Items; + + BindUtils.RelayBind(cascaderViewItem, CascaderViewItem.ItemsSourceProperty, childList, CascaderViewLevelList.ItemsSourceProperty); + BindUtils.RelayBind(cascaderViewItem, CascaderViewItem.ItemTemplateProperty, childList, CascaderViewLevelList.ItemTemplateProperty); + if (cascaderViewItem.ItemsSource == null) + { + foreach (var item in cascaderViewItem.Items) + { + childList.Items.Add(item); + } + } _itemsPanel.Children.Add(childList); InvalidateMeasure(); } @@ -1138,6 +1194,7 @@ public partial class CascaderView : ItemsControl, IMotionAwareControl, IControlS if (_itemsPanel.Children[count] is CascaderViewLevelList levelList) { levelList.ItemsSource = null; + levelList.Items.Clear(); } _itemsPanel.Children.RemoveAt(count); diff --git a/src/AtomUI.Desktop.Controls/Cascader/CascaderViewItem.cs b/src/AtomUI.Desktop.Controls/Cascader/CascaderViewItem.cs index cb84a2ce1..626a1b604 100644 --- a/src/AtomUI.Desktop.Controls/Cascader/CascaderViewItem.cs +++ b/src/AtomUI.Desktop.Controls/Cascader/CascaderViewItem.cs @@ -321,7 +321,6 @@ public class CascaderViewItem : HeaderedItemsControl, IRadioButton, ICascaderVie #endregion - private readonly BorderRenderHelper _borderRenderHelper; private CascaderViewItemHeader? _header; private readonly Dictionary _itemsBindingDisposables = new(); internal bool AsyncLoaded; @@ -345,7 +344,6 @@ public class CascaderViewItem : HeaderedItemsControl, IRadioButton, ICascaderVie public CascaderViewItem() { - _borderRenderHelper = new BorderRenderHelper(); LogicalChildren.CollectionChanged += HandleLogicalChildrenChanged; Items.CollectionChanged += HandleCollectionChanged; } @@ -383,7 +381,21 @@ public class CascaderViewItem : HeaderedItemsControl, IRadioButton, ICascaderVie { base.OnAttachedToLogicalTree(e); _cascaderView = this.GetLogicalAncestors().OfType().FirstOrDefault(); - Level = CalculateDistanceFromLogicalParent(this) - 1; + if (DataContext is ICascaderViewItemData cascaderViewItemData) + { + var level = 0; + ITreeNode? current = cascaderViewItemData; + while (current != null) + { + level++; + current = current.ParentNode; + } + Level = level - 1; + } + else + { + Level = CalculateDistanceFromLogicalParent(this) - 1; + } if (ItemTemplate == null && _cascaderView?.ItemTemplate != null) { SetCurrentValue(ItemTemplateProperty, _cascaderView.ItemTemplate); @@ -611,21 +623,6 @@ public class CascaderViewItem : HeaderedItemsControl, IRadioButton, ICascaderVie return true; } - - public override void Render(DrawingContext context) - { - if (_header != null) - { - using var state = context.PushTransform(Matrix.CreateTranslation(_header.Bounds.X, 0)); - _borderRenderHelper.Render(context, - _header.Bounds.Size, - new Thickness(), - CornerRadius, - BackgroundSizing.InnerBorderEdge, - Background, - null); - } - } internal bool PointInHeaderBounds(PointerReleasedEventArgs e) { diff --git a/src/AtomUI.Desktop.Controls/Cascader/CascaderViewLevelList.cs b/src/AtomUI.Desktop.Controls/Cascader/CascaderViewLevelList.cs index bcb0f4c4c..542f1e1ac 100644 --- a/src/AtomUI.Desktop.Controls/Cascader/CascaderViewLevelList.cs +++ b/src/AtomUI.Desktop.Controls/Cascader/CascaderViewLevelList.cs @@ -1,25 +1,18 @@ using System.Collections.Specialized; +using System.Diagnostics; using System.Reactive.Disposables; -using AtomUI.Controls; using AtomUI.Data; using Avalonia; using Avalonia.Controls; namespace AtomUI.Desktop.Controls; -internal class CascaderViewLevelList : ItemsControl, IMotionAwareControl +internal class CascaderViewLevelList : ItemsControl { - public static readonly StyledProperty IsMotionEnabledProperty = - MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); - - public bool IsMotionEnabled - { - get => GetValue(IsMotionEnabledProperty); - set => SetValue(IsMotionEnabledProperty, value); - } - private readonly Dictionary _itemsBindingDisposables = new(); + internal CascaderView? OwnerView { get; set; } + public CascaderViewLevelList() { LogicalChildren.CollectionChanged += HandleLogicalChildrenChanged; @@ -46,13 +39,44 @@ internal class CascaderViewLevelList : ItemsControl, IMotionAwareControl } } + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + return new CascaderViewItem(); + } + + 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); + Debug.Assert(OwnerView != null); if (container is CascaderViewItem cascaderViewItem) { var disposables = new CompositeDisposable(2); - disposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, cascaderViewItem, IsMotionEnabledProperty)); + + if (item != null && item is not Visual && item is ICascaderViewItemData cascaderViewItemData) + { + CascaderViewItem.ApplyNodeData(cascaderViewItem, cascaderViewItemData, disposables); + } + + if (ItemTemplate != null) + { + disposables.Add(BindUtils.RelayBind(this, ItemTemplateProperty, cascaderViewItem, CascaderViewItem.HeaderTemplateProperty)); + } + + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.ItemFilterActionProperty, cascaderViewItem, CascaderViewItem.ItemFilterActionProperty)); + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.IsMotionEnabledProperty, cascaderViewItem, CascaderViewItem.IsMotionEnabledProperty)); + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.IsShowIconProperty, cascaderViewItem, CascaderViewItem.IsShowIconProperty)); + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.ToggleTypeProperty, cascaderViewItem, CascaderViewItem.ToggleTypeProperty)); + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.FilterHighlightForegroundProperty, cascaderViewItem, CascaderViewItem.FilterHighlightForegroundProperty)); + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.HasItemAsyncDataLoaderProperty, cascaderViewItem, + CascaderViewItem.HasItemAsyncDataLoaderProperty)); + disposables.Add(BindUtils.RelayBind(OwnerView, CascaderView.IsAutoExpandParentProperty, cascaderViewItem, + CascaderViewItem.IsAutoExpandParentProperty)); + if (_itemsBindingDisposables.TryGetValue(cascaderViewItem, out var oldDisposables)) { oldDisposables.Dispose();