feat: SectionPanel ChildrenSource and ChildrenTemplate

This commit is contained in:
2026-02-26 11:14:56 +03:00
parent 6bbe0d7e09
commit 2f2fc99662
+158 -2
View File
@@ -1,6 +1,10 @@
using System.Collections;
using System.Collections.Specialized;
using Avalonia; using Avalonia;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
@@ -64,6 +68,18 @@ public class SectionPanel : Panel
get => GetValue(CanScrollBackwardProperty); get => GetValue(CanScrollBackwardProperty);
set => SetValue(CanScrollBackwardProperty, value); set => SetValue(CanScrollBackwardProperty, value);
} }
public static readonly StyledProperty<IEnumerable?> ChildrenSourceProperty = AvaloniaProperty.Register<SectionPanel, IEnumerable?>(nameof(ChildrenSource), null, defaultBindingMode: Avalonia.Data.BindingMode.OneWay);
public IEnumerable? ChildrenSource
{
get => GetValue(ChildrenSourceProperty);
set => SetValue(ChildrenSourceProperty, value);
}
public static readonly StyledProperty<IDataTemplate?> ChildrenTemplateProperty = AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ChildrenTemplate), null);
public IDataTemplate? ChildrenTemplate
{
get => GetValue(ChildrenTemplateProperty);
set => SetValue(ChildrenTemplateProperty, value);
}
private readonly Dictionary<Control, int> _childIndeces = new Dictionary<Control, int>(); private readonly Dictionary<Control, int> _childIndeces = new Dictionary<Control, int>();
private bool _initialized = false; private bool _initialized = false;
@@ -78,13 +94,153 @@ public class SectionPanel : Panel
CanScrollBackward = Cyclic || CurrentIndex != 0; CanScrollBackward = Cyclic || CurrentIndex != 0;
panel.InvalidateArrange(); panel.InvalidateArrange();
}); });
ChildrenSourceProperty.Changed.AddClassHandler<SectionPanel>((panel, ea) =>
{
if (panel != this)
return;
(ea.OldValue as INotifyCollectionChanged)?.CollectionChanged -= panel.OnChildrenSourceCollectionChanged;
(ea.NewValue as INotifyCollectionChanged)?.CollectionChanged += panel.OnChildrenSourceCollectionChanged;
panel.Children.Clear();
if (ea.NewValue is IEnumerable enumerable)
{
IEnumerable<Control> controls;
if (ChildrenTemplate is null)
controls = enumerable.OfType<Control>();
else
controls = enumerable.Cast<object?>()
.Where(ChildrenTemplate.Match)
.Select(i =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = i;
return control;
})
.OfType<Control>();
foreach (var control in controls)
panel.Children.Add(control);
}
panel.InvalidateArrange();
});
Children.CollectionChanged += (_, ea) => Children.CollectionChanged += (_, ea) =>
{ {
CanScrollForward = Cyclic || CurrentIndex != Children.Count() - 1; CanScrollForward = (Cyclic && Children.Count() > 1) || CurrentIndex != Children.Count() - 1;
CanScrollBackward = Cyclic || CurrentIndex != 0; CanScrollBackward = (Cyclic && Children.Count() > 1) || CurrentIndex != 0;
}; };
} }
protected void OnChildrenSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs ea)
{
switch (ea.Action)
{
case NotifyCollectionChangedAction.Add:
{
if (ea.NewItems is null)
return;
var index = ea.NewStartingIndex >= 0
? ea.NewStartingIndex
: Children.Count;
IEnumerable<Control> controls;
if (ChildrenTemplate is null)
controls = ea.NewItems.OfType<Control>();
else
controls = ea.NewItems.Cast<object?>()
.Where(ChildrenTemplate.Match)
.Select(i =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = i;
return control;
})
.OfType<Control>();
foreach (Control control in controls)
Children.Insert(index++, control);
break;
}
case NotifyCollectionChangedAction.Remove:
{
if (ea.OldItems is null)
return;
foreach (Control control in ea.OldItems)
Children.Remove(control);
break;
}
case NotifyCollectionChangedAction.Replace:
{
if (ea.OldItems is not null)
foreach (Control control in ea.OldItems)
Children.Remove(control);
if (ea.NewItems is not null)
{
var index = ea.NewStartingIndex >= 0 ?
ea.NewStartingIndex :
Children.Count;
foreach (Control control in ea.NewItems)
Children.Insert(index++, control);
}
break;
}
case NotifyCollectionChangedAction.Move:
{
if (ea.OldItems is not IEnumerable<Control> oldControls || oldControls.Count() == 0)
return;
var control = oldControls.First();
if (ea.OldStartingIndex >= 0)
Children.RemoveAt(ea.OldStartingIndex);
if (ea.NewStartingIndex >= 0)
Children.Insert(ea.NewStartingIndex, control);
break;
}
case NotifyCollectionChangedAction.Reset:
{
Children.Clear();
if (ChildrenSource is null)
break;
IEnumerable<Control> controls;
if (ChildrenTemplate is null)
controls = ChildrenSource.OfType<Control>();
else
controls = ChildrenSource.Cast<object?>()
.Where(ChildrenTemplate.Match)
.Select(i =>
{
var control = ChildrenTemplate.Build(i);
control?.DataContext = i;
return control;
})
.OfType<Control>();
foreach (var control in controls)
Children.Add(control);
break;
}
}
InvalidateMeasure();
InvalidateArrange();
}
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
foreach (var child in Children) foreach (var child in Children)