feature(Cascader): Add ItemsSource support

This commit is contained in:
chinboy
2026-01-18 22:41:33 +08:00
parent e49bb6aac9
commit b0a7f27b65
6 changed files with 208 additions and 56 deletions
@@ -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<ICascaderViewItemData> _basicCascaderNodes = [];
public List<ICascaderViewItemData> BasicCascaderNodes
{
get => _basicCascaderNodes;
set => this.RaiseAndSetIfChanged(ref _basicCascaderNodes, value);
}
public CascaderViewModel(IScreen screen)
{
HostScreen = screen;
@@ -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">
<gallery:ShowCasePanel>
<gallery:ShowCaseItem
Title="Basic CascaderView"
Description="The most basic usage."
IsOccupyEntireRow="True">
Description="The most basic usage.">
<StackPanel Orientation="Vertical" Spacing="10">
<atom:CascaderView>
<atom:CascaderViewItem Header="Zhejiang">
@@ -21,9 +21,23 @@
</atom:CascaderViewItem>
</atom:CascaderViewItem>
</atom:CascaderView>
<atom:CascaderView/>
</StackPanel>
</gallery:ShowCaseItem>
<gallery:ShowCaseItem
Title="Generate by template"
Description="You can use the Template mechanism to generate tree nodes">
<atom:CascaderView
x:DataType="viewModels:CascaderViewModel"
ItemsSource="{Binding BasicCascaderNodes}">
<atom:CascaderView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:CascaderView.ItemTemplate>
</atom:CascaderView>
</gallery:ShowCaseItem>
</gallery:ShowCasePanel>
</UserControl>
@@ -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<CascaderViewModel>
{
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",
}
]
}
]
}
];
}
}
@@ -1059,32 +1059,78 @@ public partial class CascaderView : ItemsControl, IMotionAwareControl, IControlS
{
return;
}
// 保证当前 Item 的父亲都在了
var parents = new List<CascaderViewItem>();
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<CascaderViewItem>();
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<ICascaderViewItemData>();
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);
@@ -321,7 +321,6 @@ public class CascaderViewItem : HeaderedItemsControl, IRadioButton, ICascaderVie
#endregion
private readonly BorderRenderHelper _borderRenderHelper;
private CascaderViewItemHeader? _header;
private readonly Dictionary<CascaderViewItem, CompositeDisposable> _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<CascaderView>().FirstOrDefault();
Level = CalculateDistanceFromLogicalParent<CascaderView>(this) - 1;
if (DataContext is ICascaderViewItemData cascaderViewItemData)
{
var level = 0;
ITreeNode<ICascaderViewItemData>? current = cascaderViewItemData;
while (current != null)
{
level++;
current = current.ParentNode;
}
Level = level - 1;
}
else
{
Level = CalculateDistanceFromLogicalParent<CascaderView>(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)
{
@@ -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<bool> IsMotionEnabledProperty =
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<CascaderViewLevelList>();
public bool IsMotionEnabled
{
get => GetValue(IsMotionEnabledProperty);
set => SetValue(IsMotionEnabledProperty, value);
}
private readonly Dictionary<CascaderViewItem, CompositeDisposable> _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<CascaderViewItem>(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();