using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
///
/// Displays a collection of items.
///
[PseudoClasses(":empty", ":singleitem")]
public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener, IChildIndexProvider
{
///
/// The default value for the property.
///
private static readonly FuncTemplate DefaultPanel =
new FuncTemplate(() => new StackPanel());
///
/// Defines the property.
///
public static readonly DirectProperty ItemsProperty =
AvaloniaProperty.RegisterDirect(nameof(Items), o => o.Items, (o, v) => o.Items = v);
///
/// Defines the property.
///
public static readonly DirectProperty ItemCountProperty =
AvaloniaProperty.RegisterDirect(nameof(ItemCount), o => o.ItemCount);
///
/// Defines the property.
///
public static readonly StyledProperty> ItemsPanelProperty =
AvaloniaProperty.Register>(nameof(ItemsPanel), DefaultPanel);
///
/// Defines the property.
///
public static readonly StyledProperty ItemTemplateProperty =
AvaloniaProperty.Register(nameof(ItemTemplate));
private IEnumerable? _items = new AvaloniaList();
private int _itemCount;
private IItemContainerGenerator? _itemContainerGenerator;
private EventHandler? _childIndexChanged;
///
/// Initializes static members of the class.
///
static ItemsControl()
{
ItemsProperty.Changed.AddClassHandler((x, e) => x.ItemsChanged(e));
ItemTemplateProperty.Changed.AddClassHandler((x, e) => x.ItemTemplateChanged(e));
}
///
/// Initializes a new instance of the class.
///
public ItemsControl()
{
UpdatePseudoClasses(0);
SubscribeToItems(_items);
}
///
/// Gets the for the control.
///
public IItemContainerGenerator ItemContainerGenerator
{
get
{
if (_itemContainerGenerator == null)
{
_itemContainerGenerator = CreateItemContainerGenerator();
_itemContainerGenerator.ItemTemplate = ItemTemplate;
_itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
_itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
_itemContainerGenerator.Recycled += (_, e) => OnContainersRecycled(e);
}
return _itemContainerGenerator;
}
}
///
/// Gets or sets the items to display.
///
[Content]
public IEnumerable? Items
{
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
///
/// Gets the number of items in .
///
public int ItemCount
{
get => _itemCount;
private set => SetAndRaise(ItemCountProperty, ref _itemCount, value);
}
///
/// Gets or sets the panel used to display the items.
///
public ITemplate ItemsPanel
{
get { return GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
}
///
/// Gets or sets the data template used to display the items in the control.
///
public IDataTemplate? ItemTemplate
{
get { return GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
///
/// Gets the items presenter control.
///
public IItemsPresenter? Presenter
{
get;
protected set;
}
private protected bool WrapFocus { get; set; }
event EventHandler? IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}
///
void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter)
{
if (Presenter is IChildIndexProvider oldInnerProvider)
{
oldInnerProvider.ChildIndexChanged -= PresenterChildIndexChanged;
}
Presenter = presenter;
ItemContainerGenerator?.Clear();
if (Presenter is IChildIndexProvider innerProvider)
{
innerProvider.ChildIndexChanged += PresenterChildIndexChanged;
_childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.Empty);
}
}
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
}
void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
}
void ICollectionChangedListener.PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
ItemsCollectionChanged(sender, e);
}
///
/// Gets the item at the specified index in a collection.
///
/// The collection.
/// The index.
/// The item at the given index or null if the index is out of bounds.
protected static object? ElementAt(IEnumerable? items, int index)
{
if (index != -1 && index < items.Count())
{
return items!.ElementAt(index) ?? null;
}
else
{
return null;
}
}
///
/// Gets the index of an item in a collection.
///
/// The collection.
/// The item.
/// The index of the item or -1 if the item was not found.
protected static int IndexOf(IEnumerable? items, object item)
{
if (items != null && item != null)
{
var list = items as IList;
if (list != null)
{
return list.IndexOf(item);
}
else
{
int index = 0;
foreach (var i in items)
{
if (Equals(i, item))
{
return index;
}
++index;
}
}
}
return -1;
}
///
/// Creates the for the control.
///
///
/// An .
///
protected virtual IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator(this);
}
///
/// Called when new containers are materialized for the by its
/// .
///
/// The details of the containers.
protected virtual void OnContainersMaterialized(ItemContainerEventArgs e)
{
foreach (var container in e.Containers)
{
// If the item is its own container, then it will be added to the logical tree when
// it was added to the Items collection.
if (container.ContainerControl != null && container.ContainerControl != container.Item)
{
LogicalChildren.Add(container.ContainerControl);
}
}
}
///
/// Called when containers are dematerialized for the by its
/// .
///
/// The details of the containers.
protected virtual void OnContainersDematerialized(ItemContainerEventArgs e)
{
foreach (var container in e.Containers)
{
// If the item is its own container, then it will be removed from the logical tree
// when it is removed from the Items collection.
if (container.ContainerControl != container.Item)
{
LogicalChildren.Remove(container.ContainerControl);
}
}
}
///
/// Called when containers are recycled for the by its
/// .
///
/// The details of the containers.
protected virtual void OnContainersRecycled(ItemContainerEventArgs e)
{
}
///
/// Handles directional navigation within the .
///
/// The key events.
protected override void OnKeyDown(KeyEventArgs e)
{
if (!e.Handled)
{
var focus = FocusManager.Instance;
var direction = e.Key.ToNavigationDirection();
var container = Presenter?.Panel as INavigableContainer;
if (container == null ||
focus?.Current == null ||
direction == null ||
direction.Value.IsTab())
{
return;
}
IVisual? current = focus.Current;
while (current != null)
{
if (current.VisualParent == container && current is IInputElement inputElement)
{
var next = GetNextControl(container, direction.Value, inputElement, WrapFocus);
if (next != null)
{
focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
e.Handled = true;
}
break;
}
current = current.VisualParent;
}
}
base.OnKeyDown(e);
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ItemsControlAutomationPeer(this);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ItemCountProperty)
{
UpdatePseudoClasses(change.GetNewValue());
}
}
///
/// Called when the property changes.
///
/// The event args.
protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
{
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
if (oldValue is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
}
UpdateItemCount();
RemoveControlItemsFromLogicalChildren(oldValue);
AddControlItemsToLogicalChildren(newValue);
if (Presenter != null)
{
Presenter.Items = newValue;
}
SubscribeToItems(newValue);
}
///
/// Called when the event is
/// raised on .
///
/// The event sender.
/// The event args.
protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateItemCount();
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddControlItemsToLogicalChildren(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
Presenter?.ItemsChanged(e);
}
///
/// Given a collection of items, adds those that are controls to the logical children.
///
/// The items.
private void AddControlItemsToLogicalChildren(IEnumerable? items)
{
var toAdd = new List();
if (items != null)
{
foreach (var i in items)
{
var control = i as IControl;
if (control != null && !LogicalChildren.Contains(control))
{
toAdd.Add(control);
}
}
}
LogicalChildren.AddRange(toAdd);
}
///
/// Given a collection of items, removes those that are controls to from logical children.
///
/// The items.
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
{
var toRemove = new List();
if (items != null)
{
foreach (var i in items)
{
var control = i as IControl;
if (control != null)
{
toRemove.Add(control);
}
}
}
LogicalChildren.RemoveAll(toRemove);
}
///
/// Subscribes to an collection.
///
/// The items collection.
private void SubscribeToItems(IEnumerable? items)
{
if (items is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.AddListener(incc, this);
}
}
///
/// Called when the changes.
///
/// The event args.
private void ItemTemplateChanged(AvaloniaPropertyChangedEventArgs e)
{
if (_itemContainerGenerator != null)
{
_itemContainerGenerator.ItemTemplate = (IDataTemplate?)e.NewValue;
// TODO: Rebuild the item containers.
}
}
private void UpdateItemCount()
{
if (Items == null)
{
ItemCount = 0;
}
else if (Items is IList list)
{
ItemCount = list.Count;
}
else
{
ItemCount = Items.Count();
}
}
private void UpdatePseudoClasses(int itemCount)
{
PseudoClasses.Set(":empty", itemCount == 0);
PseudoClasses.Set(":singleitem", itemCount == 1);
}
protected static IInputElement? GetNextControl(
INavigableContainer container,
NavigationDirection direction,
IInputElement? from,
bool wrap)
{
var current = from;
for (;;)
{
var result = container.GetControl(direction, current, wrap);
if (result is null)
{
return null;
}
if (result.Focusable &&
result.IsEffectivelyEnabled &&
result.IsEffectivelyVisible)
{
return result;
}
current = result;
if (current == from)
{
return null;
}
switch (direction)
{
//We did not find an enabled first item. Move downwards until we find one.
case NavigationDirection.First:
direction = NavigationDirection.Down;
from = result;
break;
//We did not find an enabled last item. Move upwards until we find one.
case NavigationDirection.Last:
direction = NavigationDirection.Up;
from = result;
break;
}
}
}
private void PresenterChildIndexChanged(object? sender, ChildIndexChangedEventArgs e)
{
_childIndexChanged?.Invoke(this, e);
}
int IChildIndexProvider.GetChildIndex(ILogical child)
{
return Presenter is IChildIndexProvider innerProvider
? innerProvider.GetChildIndex(child) : -1;
}
bool IChildIndexProvider.TryGetTotalCount(out int count)
{
if (Presenter is IChildIndexProvider presenter
&& presenter.TryGetTotalCount(out count))
{
return true;
}
count = ItemCount;
return true;
}
}
}