|
|
@@ -34,8 +34,13 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="Items"/> property.
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<ItemsControl, IEnumerable?> ItemsProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable?>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
|
|
|
+ public static readonly DirectProperty<ItemsControl, IList?> ItemsProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<ItemsControl, IList?>(
|
|
|
+ nameof(Items),
|
|
|
+ o => o.Items,
|
|
|
+#pragma warning disable CS0618 // Type or member is obsolete
|
|
|
+ (o, v) => o.Items = v);
|
|
|
+#pragma warning restore CS0618 // Type or member is obsolete
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="ItemContainerTheme"/> property.
|
|
|
@@ -56,23 +61,23 @@ namespace Avalonia.Controls
|
|
|
AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Defines the <see cref="ItemTemplate"/> property.
|
|
|
+ /// Defines the <see cref="ItemsSource"/> property.
|
|
|
/// </summary>
|
|
|
- public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
|
|
|
- AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
|
|
|
+ public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
|
|
|
+ AvaloniaProperty.Register<ItemsControl, IEnumerable?>(nameof(ItemsSource));
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Defines the <see cref="ItemsView"/> property.
|
|
|
+ /// Defines the <see cref="ItemTemplate"/> property.
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<ItemsControl, ItemsSourceView> ItemsViewProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<ItemsControl, ItemsSourceView>(nameof(ItemsView), o => o.ItemsView);
|
|
|
+ public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
|
|
|
+ AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="DisplayMemberBinding" /> property
|
|
|
/// </summary>
|
|
|
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
|
|
|
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
|
|
|
-
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
|
|
|
/// </summary>
|
|
|
@@ -89,15 +94,15 @@ namespace Avalonia.Controls
|
|
|
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
|
|
|
/// </summary>
|
|
|
[AssignBinding]
|
|
|
+ [InheritDataTypeFromItems(nameof(ItemsSource))]
|
|
|
[InheritDataTypeFromItems(nameof(Items))]
|
|
|
public IBinding? DisplayMemberBinding
|
|
|
{
|
|
|
get => GetValue(DisplayMemberBindingProperty);
|
|
|
set => SetValue(DisplayMemberBindingProperty, value);
|
|
|
}
|
|
|
-
|
|
|
- private IEnumerable? _items = new AvaloniaList<object>();
|
|
|
- private ItemsSourceView _itemsView;
|
|
|
+
|
|
|
+ private readonly ItemCollection _items = new();
|
|
|
private int _itemCount;
|
|
|
private ItemContainerGenerator? _itemContainerGenerator;
|
|
|
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
|
|
|
@@ -110,9 +115,8 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public ItemsControl()
|
|
|
{
|
|
|
- _itemsView = ItemsSourceView.GetOrCreate(_items);
|
|
|
- _itemsView.PostCollectionChanged += ItemsCollectionChanged;
|
|
|
- UpdatePseudoClasses(0);
|
|
|
+ UpdatePseudoClasses();
|
|
|
+ _items.CollectionChanged += OnItemsViewCollectionChanged;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -128,11 +132,45 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Gets or sets the items to display.
|
|
|
/// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
|
|
|
+ /// and an <see cref="ItemsSource"/> property. The properties have the following differences:
|
|
|
+ ///
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
|
|
|
+ /// meaning that it cannot be styled </item>
|
|
|
+ /// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
|
|
|
+ /// is marked as the content property and will be used for items added via inline XAML.</item>
|
|
|
+ /// </list>
|
|
|
+ ///
|
|
|
+ /// In Avalonia 11 the two properties can be used almost interchangeably but this will change
|
|
|
+ /// in a later version. In order to be ready for this change, follow the following guidance:
|
|
|
+ ///
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
|
|
|
+ /// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
|
|
|
+ /// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
|
|
|
+ /// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
|
|
|
+ /// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
|
|
|
+ /// binding a collection of models which will be transformed by a data template.</item>
|
|
|
+ /// </list>
|
|
|
+ /// </remarks>
|
|
|
[Content]
|
|
|
- public IEnumerable? Items
|
|
|
+ public IList? Items
|
|
|
{
|
|
|
- get => _items;
|
|
|
- set => SetAndRaise(ItemsProperty, ref _items, value);
|
|
|
+ get => _items.GetItemsPropertyValue();
|
|
|
+
|
|
|
+ [Obsolete("Use ItemsSource to set or bind items.")]
|
|
|
+ set
|
|
|
+ {
|
|
|
+ var oldItems = _items.GetItemsPropertyValue();
|
|
|
+
|
|
|
+ if (value != oldItems)
|
|
|
+ {
|
|
|
+ _items.SetItems(value);
|
|
|
+ RaisePropertyChanged(ItemsProperty, oldItems, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -140,17 +178,24 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public ControlTheme? ItemContainerTheme
|
|
|
{
|
|
|
- get => GetValue(ItemContainerThemeProperty);
|
|
|
+ get => GetValue(ItemContainerThemeProperty);
|
|
|
set => SetValue(ItemContainerThemeProperty, value);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets the number of items in <see cref="Items"/>.
|
|
|
+ /// Gets the number of items being displayed by the <see cref="ItemsControl"/>.
|
|
|
/// </summary>
|
|
|
public int ItemCount
|
|
|
{
|
|
|
get => _itemCount;
|
|
|
- private set => SetAndRaise(ItemCountProperty, ref _itemCount, value);
|
|
|
+ private set
|
|
|
+ {
|
|
|
+ if (SetAndRaise(ItemCountProperty, ref _itemCount, value))
|
|
|
+ {
|
|
|
+ UpdatePseudoClasses();
|
|
|
+ _childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -162,13 +207,46 @@ namespace Avalonia.Controls
|
|
|
set => SetValue(ItemsPanelProperty, value);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a collection used to generate the content of the <see cref="ItemsControl"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
|
|
|
+ /// and an <see cref="ItemsSource"/> property. The properties have the following differences:
|
|
|
+ ///
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
|
|
|
+ /// meaning that it cannot be styled </item>
|
|
|
+ /// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
|
|
|
+ /// is marked as the content property and will be used for items added via inline XAML.</item>
|
|
|
+ /// </list>
|
|
|
+ ///
|
|
|
+ /// In Avalonia 11 the two properties can be used almost interchangeably but this will change
|
|
|
+ /// in a later version. In order to be ready for this change, follow the following guidance:
|
|
|
+ ///
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
|
|
|
+ /// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
|
|
|
+ /// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
|
|
|
+ /// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
|
|
|
+ /// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
|
|
|
+ /// binding a collection of models which will be transformed by a data template.</item>
|
|
|
+ /// </list>
|
|
|
+ /// </remarks>
|
|
|
+ public IEnumerable? ItemsSource
|
|
|
+ {
|
|
|
+ get => GetValue(ItemsSourceProperty);
|
|
|
+ set => SetValue(ItemsSourceProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Gets or sets the data template used to display the items in the control.
|
|
|
/// </summary>
|
|
|
+ [InheritDataTypeFromItems(nameof(ItemsSource))]
|
|
|
[InheritDataTypeFromItems(nameof(Items))]
|
|
|
public IDataTemplate? ItemTemplate
|
|
|
{
|
|
|
- get => GetValue(ItemTemplateProperty);
|
|
|
+ get => GetValue(ItemTemplateProperty);
|
|
|
set => SetValue(ItemTemplateProperty, value);
|
|
|
}
|
|
|
|
|
|
@@ -183,31 +261,9 @@ namespace Avalonia.Controls
|
|
|
public Panel? ItemsPanelRoot => Presenter?.Panel;
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets a standardized view over <see cref="Items"/>.
|
|
|
+ /// Gets a read-only view of the items in the <see cref="ItemsControl"/>.
|
|
|
/// </summary>
|
|
|
- /// <remarks>
|
|
|
- /// The <see cref="Items"/> property may be an enumerable which does not implement
|
|
|
- /// <see cref="IList"/> or may be null. This view can be used to provide a standardized
|
|
|
- /// view of the current items regardless of the type of the concrete collection, and
|
|
|
- /// without having to deal with null values.
|
|
|
- /// </remarks>
|
|
|
- public ItemsSourceView ItemsView
|
|
|
- {
|
|
|
- get => _itemsView;
|
|
|
- private set
|
|
|
- {
|
|
|
- if (ReferenceEquals(_itemsView, value))
|
|
|
- return;
|
|
|
-
|
|
|
- var oldValue = _itemsView;
|
|
|
- RemoveControlItemsFromLogicalChildren(_itemsView);
|
|
|
- _itemsView.PostCollectionChanged -= ItemsCollectionChanged;
|
|
|
- _itemsView = value;
|
|
|
- _itemsView.PostCollectionChanged += ItemsCollectionChanged;
|
|
|
- AddControlItemsToLogicalChildren(_itemsView);
|
|
|
- RaisePropertyChanged(ItemsViewProperty, oldValue, _itemsView);
|
|
|
- }
|
|
|
- }
|
|
|
+ public ItemsSourceView ItemsView => _items;
|
|
|
|
|
|
private protected bool WrapFocus { get; set; }
|
|
|
|
|
|
@@ -262,7 +318,7 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public bool AreHorizontalSnapPointsRegular
|
|
|
{
|
|
|
- get => GetValue(AreHorizontalSnapPointsRegularProperty);
|
|
|
+ get => GetValue(AreHorizontalSnapPointsRegularProperty);
|
|
|
set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
|
|
|
}
|
|
|
|
|
|
@@ -271,7 +327,7 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public bool AreVerticalSnapPointsRegular
|
|
|
{
|
|
|
- get => GetValue(AreVerticalSnapPointsRegularProperty);
|
|
|
+ get => GetValue(AreVerticalSnapPointsRegularProperty);
|
|
|
set => SetValue(AreVerticalSnapPointsRegularProperty, value);
|
|
|
}
|
|
|
|
|
|
@@ -295,7 +351,7 @@ namespace Avalonia.Controls
|
|
|
/// </returns>
|
|
|
public Control? ContainerFromItem(object item)
|
|
|
{
|
|
|
- var index = ItemsView.IndexOf(item);
|
|
|
+ var index = _items.IndexOf(item);
|
|
|
return index >= 0 ? ContainerFromIndex(index) : null;
|
|
|
}
|
|
|
|
|
|
@@ -319,7 +375,7 @@ namespace Avalonia.Controls
|
|
|
public object? ItemFromContainer(Control container)
|
|
|
{
|
|
|
var index = IndexFromContainer(container);
|
|
|
- return index >= 0 && index < ItemsView.Count ? ItemsView[index] : null;
|
|
|
+ return index >= 0 && index < _items.Count ? _items[index] : null;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -478,19 +534,13 @@ namespace Avalonia.Controls
|
|
|
{
|
|
|
base.OnPropertyChanged(change);
|
|
|
|
|
|
- if (change.Property == ItemsProperty)
|
|
|
- {
|
|
|
- ItemsView = ItemsSourceView.GetOrCreate(change.GetNewValue<IEnumerable?>());
|
|
|
- ItemCount = ItemsView.Count;
|
|
|
- }
|
|
|
- else if (change.Property == ItemCountProperty)
|
|
|
+ if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
|
|
|
{
|
|
|
- UpdatePseudoClasses(change.GetNewValue<int>());
|
|
|
- _childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
|
|
|
+ RefreshContainers();
|
|
|
}
|
|
|
- else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
|
|
|
+ else if (change.Property == ItemsSourceProperty)
|
|
|
{
|
|
|
- RefreshContainers();
|
|
|
+ _items.SetItemsSource(change.GetNewValue<IEnumerable?>());
|
|
|
}
|
|
|
else if (change.Property == ItemTemplateProperty)
|
|
|
{
|
|
|
@@ -517,24 +567,27 @@ namespace Avalonia.Controls
|
|
|
|
|
|
/// <summary>
|
|
|
/// Called when the <see cref="INotifyCollectionChanged.CollectionChanged"/> event is
|
|
|
- /// raised on <see cref="Items"/>.
|
|
|
+ /// raised on <see cref="ItemsView"/>.
|
|
|
/// </summary>
|
|
|
/// <param name="sender">The event sender.</param>
|
|
|
/// <param name="e">The event args.</param>
|
|
|
- protected virtual void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
|
+ private protected virtual void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
|
{
|
|
|
- ItemCount = _itemsView.Count;
|
|
|
-
|
|
|
- switch (e.Action)
|
|
|
+ if (!_items.IsReadOnly)
|
|
|
{
|
|
|
- case NotifyCollectionChangedAction.Add:
|
|
|
- AddControlItemsToLogicalChildren(e.NewItems);
|
|
|
- break;
|
|
|
+ switch (e.Action)
|
|
|
+ {
|
|
|
+ case NotifyCollectionChangedAction.Add:
|
|
|
+ AddControlItemsToLogicalChildren(e.NewItems);
|
|
|
+ break;
|
|
|
|
|
|
- case NotifyCollectionChangedAction.Remove:
|
|
|
- RemoveControlItemsFromLogicalChildren(e.OldItems);
|
|
|
- break;
|
|
|
+ case NotifyCollectionChangedAction.Remove:
|
|
|
+ RemoveControlItemsFromLogicalChildren(e.OldItems);
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ ItemCount = ItemsView.Count;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -578,7 +631,7 @@ namespace Avalonia.Controls
|
|
|
{
|
|
|
var itemContainerTheme = ItemContainerTheme;
|
|
|
|
|
|
- if (itemContainerTheme is not null &&
|
|
|
+ if (itemContainerTheme is not null &&
|
|
|
!container.IsSet(ThemeProperty) &&
|
|
|
((IStyleable)container).StyleKey == itemContainerTheme.TargetType)
|
|
|
{
|
|
|
@@ -609,10 +662,6 @@ namespace Avalonia.Controls
|
|
|
ClearContainerForItemOverride(container);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Given a collection of items, adds those that are controls to the logical children.
|
|
|
- /// </summary>
|
|
|
- /// <param name="items">The items.</param>
|
|
|
private void AddControlItemsToLogicalChildren(IEnumerable? items)
|
|
|
{
|
|
|
if (items is null)
|
|
|
@@ -633,10 +682,6 @@ namespace Avalonia.Controls
|
|
|
LogicalChildren.AddRange(toAdd);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Given a collection of items, removes those that are controls to from logical children.
|
|
|
- /// </summary>
|
|
|
- /// <param name="items">The items.</param>
|
|
|
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
|
|
|
{
|
|
|
if (items is null)
|
|
|
@@ -674,10 +719,10 @@ namespace Avalonia.Controls
|
|
|
return _displayMemberItemTemplate;
|
|
|
}
|
|
|
|
|
|
- private void UpdatePseudoClasses(int itemCount)
|
|
|
+ private void UpdatePseudoClasses()
|
|
|
{
|
|
|
- PseudoClasses.Set(":empty", itemCount == 0);
|
|
|
- PseudoClasses.Set(":singleitem", itemCount == 1);
|
|
|
+ PseudoClasses.Set(":empty", ItemCount == 0);
|
|
|
+ PseudoClasses.Set(":singleitem", ItemCount == 1);
|
|
|
}
|
|
|
|
|
|
protected static IInputElement? GetNextControl(
|