Browse Source

Use ItemsSourceView in ItemsControl.

Fixes #54.
pull/9677/head
Steven Kirk 3 years ago
parent
commit
3195df0910
  1. 2
      src/Avalonia.Controls/Carousel.cs
  2. 221
      src/Avalonia.Controls/ItemsControl.cs
  3. 240
      src/Avalonia.Controls/ItemsSourceView.cs
  4. 2
      src/Avalonia.Controls/MenuItem.cs
  5. 11
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  6. 2
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  7. 1
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  8. 6
      src/Avalonia.Controls/Selection/SelectionNodeBase.cs
  9. 9
      src/Avalonia.Controls/TreeView.cs
  10. 4
      src/Avalonia.Controls/TreeViewItem.cs
  11. 12
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  12. 22
      src/Avalonia.Controls/VirtualizingPanel.cs
  13. 25
      src/Avalonia.Controls/VirtualizingStackPanel.cs

2
src/Avalonia.Controls/Carousel.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls
/// </summary>
public void Next()
{
if (SelectedIndex < Items.Count() - 1)
if (SelectedIndex < ItemCount - 1)
{
++SelectedIndex;
}

221
src/Avalonia.Controls/ItemsControl.cs

@ -2,7 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@ -10,7 +10,6 @@ using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
@ -24,7 +23,7 @@ namespace Avalonia.Controls
/// Displays a collection of items.
/// </summary>
[PseudoClasses(":empty", ":singleitem")]
public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener, IChildIndexProvider
public class ItemsControl : TemplatedControl, IItemsPresenterHost, IChildIndexProvider
{
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
@ -62,6 +61,11 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
/// <summary>
/// Defines the <see cref="ItemsView"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, ItemsSourceView> ItemsViewProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, ItemsSourceView>(nameof(Items), o => o.ItemsView);
/// <summary>
/// Defines the <see cref="DisplayMemberBinding" /> property
@ -80,27 +84,21 @@ namespace Avalonia.Controls
}
private IEnumerable? _items = new AvaloniaList<object>();
private ItemsSourceView _itemsView;
private int _itemCount;
private ItemContainerGenerator? _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
private IDataTemplate? _displayMemberItemTemplate;
private Tuple<int, Control>? _containerBeingPrepared;
/// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class.
/// </summary>
static ItemsControl()
{
ItemsProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemsChanged(e));
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemsControl"/> class.
/// </summary>
public ItemsControl()
{
_itemsView = new(_items);
_itemsView.PostCollectionChanged += ItemsCollectionChanged;
UpdatePseudoClasses(0);
SubscribeToItems(_items);
}
/// <summary>
@ -164,6 +162,33 @@ namespace Avalonia.Controls
/// </summary>
public ItemsPresenter? Presenter { get; private set; }
/// <summary>
/// Gets a standardized view over <see cref="Items"/>.
/// </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);
}
}
private protected bool WrapFocus { get; set; }
event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
@ -192,7 +217,7 @@ namespace Avalonia.Controls
/// </returns>
public Control? ContainerFromItem(object item)
{
var index = Items?.IndexOf(item) ?? -1;
var index = ItemsView.IndexOf(item);
return index >= 0 ? ContainerFromIndex(index) : null;
}
@ -216,7 +241,7 @@ namespace Avalonia.Controls
public object? ItemFromContainer(Control container)
{
var index = IndexFromContainer(container);
return index >= 0 && index < ItemCount ? Items!.ElementAt(index) : null;
return index >= 0 && index < ItemsView.Count ? ItemsView[index] : null;
}
/// <summary>
@ -231,37 +256,6 @@ namespace Avalonia.Controls
_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);
}
/// <summary>
/// Gets the item at the specified index in a collection.
/// </summary>
/// <param name="items">The collection.</param>
/// <param name="index">The index.</param>
/// <returns>The item at the given index or null if the index is out of bounds.</returns>
protected static object? ElementAt(IEnumerable? items, int index)
{
if (index != -1 && index < items.Count())
{
return items!.ElementAt(index) ?? null;
}
else
{
return null;
}
}
/// <summary>
/// Creates or a container that can be used to display an item.
/// </summary>
@ -350,41 +344,6 @@ namespace Avalonia.Controls
// the WPF source it seems that this isn't done there.
}
/// <summary>
/// Gets the index of an item in a collection.
/// </summary>
/// <param name="items">The collection.</param>
/// <param name="item">The item.</param>
/// <returns>The index of the item or -1 if the item was not found.</returns>
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;
}
/// <summary>
/// Determines whether the specified item is (or is eligible to be) its own container.
/// </summary>
@ -445,7 +404,12 @@ namespace Avalonia.Controls
{
base.OnPropertyChanged(change);
if (change.Property == ItemCountProperty)
if (change.Property == ItemsProperty)
{
ItemsView = ItemsSourceView.GetOrCreate(change.GetNewValue<IEnumerable?>());
ItemCount = ItemsView.Count;
}
else if (change.Property == ItemCountProperty)
{
UpdatePseudoClasses(change.GetNewValue<int>());
}
@ -468,26 +432,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="Items"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
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);
SubscribeToItems(newValue);
}
/// <summary>
/// Refreshes the containers displayed by the control.
/// </summary>
@ -502,9 +446,9 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
protected virtual void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateItemCount();
ItemCount = _itemsView.Count;
switch (e.Action)
{
@ -576,22 +520,22 @@ namespace Avalonia.Controls
/// <param name="items">The items.</param>
private void AddControlItemsToLogicalChildren(IEnumerable? items)
{
var toAdd = new List<ILogical>();
if (items is null)
return;
if (items != null)
List<ILogical>? toAdd = null;
foreach (var i in items)
{
foreach (var i in items)
if (i is Control control && !LogicalChildren.Contains(control))
{
var control = i as Control;
if (control != null && !LogicalChildren.Contains(control))
{
toAdd.Add(control);
}
toAdd ??= new();
toAdd.Add(control);
}
}
LogicalChildren.AddRange(toAdd);
if (toAdd is not null)
LogicalChildren.AddRange(toAdd);
}
/// <summary>
@ -600,34 +544,22 @@ namespace Avalonia.Controls
/// <param name="items">The items.</param>
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
{
var toRemove = new List<ILogical>();
if (items is null)
return;
if (items != null)
List<ILogical>? toRemove = null;
foreach (var i in items)
{
foreach (var i in items)
if (i is Control control)
{
var control = i as Control;
if (control != null)
{
toRemove.Add(control);
}
toRemove ??= new();
toRemove.Add(control);
}
}
LogicalChildren.RemoveAll(toRemove);
}
/// <summary>
/// Subscribes to an <see cref="Items"/> collection.
/// </summary>
/// <param name="items">The items collection.</param>
private void SubscribeToItems(IEnumerable? items)
{
if (items is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.AddListener(incc, this);
}
if (toRemove is not null)
LogicalChildren.RemoveAll(toRemove);
}
private IDataTemplate? GetEffectiveItemTemplate()
@ -647,22 +579,6 @@ namespace Avalonia.Controls
return _displayMemberItemTemplate;
}
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);
@ -728,7 +644,8 @@ namespace Avalonia.Controls
bool IChildIndexProvider.TryGetTotalCount(out int count)
{
return Items.TryGetCountFast(out count);
count = ItemsView.Count;
return true;
}
}
}

240
src/Avalonia.Controls/ItemsSourceView.cs

@ -13,24 +13,22 @@ using Avalonia.Controls.Utils;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a standardized view of the supported interactions between a given ItemsSource
/// object and an <see cref="ItemsRepeater"/> control.
/// Represents a standardized view of the supported interactions between an items collection
/// and an items control.
/// </summary>
/// <remarks>
/// Components written to work with ItemsRepeater should consume the
/// <see cref="ItemsRepeater.Items"/> via ItemsSourceView since this provides a normalized
/// view of the Items. That way, each component does not need to know if the source is an
/// IEnumerable, an IList, or something else.
/// </remarks>
public class ItemsSourceView : INotifyCollectionChanged, IDisposable
public class ItemsSourceView : IReadOnlyList<object?>,
INotifyCollectionChanged,
ICollectionChangedListener
{
/// <summary>
/// Gets an empty <see cref="ItemsSourceView"/>
/// </summary>
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
private IList? _inner;
private readonly IList _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler? _postCollectionChanged;
private bool _listening;
/// <summary>
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
@ -38,15 +36,15 @@ namespace Avalonia.Controls
/// <param name="source">The data source.</param>
public ItemsSourceView(IEnumerable source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
_inner = source switch
{
ItemsSourceView _ => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged _ => throw new ArgumentException(
"Collection implements INotifyCollectionChanged by not IList.",
INotifyCollectionChanged => throw new ArgumentException(
"Collection implements INotifyCollectionChanged but not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
null => throw new ArgumentNullException(nameof(source)),
_ => new List<object>(source.Cast<object>())
};
}
@ -56,26 +54,10 @@ namespace Avalonia.Controls
/// </summary>
public int Count => Inner.Count;
/// <summary>
/// Gets a value that indicates whether the items source can provide a unique key for each item.
/// </summary>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// </remarks>
public bool HasKeyIndexMapping => false;
/// <summary>
/// Gets the inner collection.
/// </summary>
public IList Inner
{
get
{
if (_inner is null)
ThrowDisposed();
return _inner!;
}
}
public IList Inner => _inner;
/// <summary>
/// Retrieves the item at the specified index.
@ -84,6 +66,14 @@ namespace Avalonia.Controls
/// <returns>The item.</returns>
public object? this[int index] => GetAt(index);
/// <summary>
/// Gets a value that indicates whether the items source can provide a unique key for each item.
/// </summary>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// </remarks>
internal bool HasKeyIndexMapping => false;
/// <summary>
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
/// </summary>
@ -91,114 +81,133 @@ namespace Avalonia.Controls
{
add
{
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
}
AddListenerIfNecessary();
_collectionChanged += value;
}
remove
{
_collectionChanged -= value;
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= OnCollectionChanged;
}
RemoveListenerIfNecessary();
}
}
/// <inheritdoc/>
public void Dispose()
/// <summary>
/// Occurs when a collection has finished changing and all <see cref="CollectionChanged"/>
/// event handlers have been notified.
/// </summary>
internal event NotifyCollectionChangedEventHandler? PostCollectionChanged
{
if (_inner is INotifyCollectionChanged incc)
add
{
incc.CollectionChanged -= OnCollectionChanged;
AddListenerIfNecessary();
_postCollectionChanged += value;
}
_inner = null;
remove
{
_postCollectionChanged -= value;
RemoveListenerIfNecessary();
}
}
/// <summary>
/// Retrieves the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public object? GetAt(int index) => Inner[index];
public int IndexOf(object? item) => Inner.IndexOf(item);
public static ItemsSourceView GetOrCreate(IEnumerable? items)
private void AddListenerIfNecessary()
{
if (items is ItemsSourceView isv)
if (!_listening)
{
return isv;
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.AddListener(incc, this);
_listening = true;
}
else if (items is null)
{
return Empty;
}
else
}
private void RemoveListenerIfNecessary()
{
if (_listening && _collectionChanged is null && _postCollectionChanged is null)
{
return new ItemsSourceView(items);
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
_listening = false;
}
}
/// <summary>
/// Retrieves the index of the item that has the specified unique identifier (key).
/// Retrieves the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The key</returns>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// </remarks>
public string KeyFromIndex(int index)
{
throw new NotImplementedException();
}
/// <returns>The item.</returns>
public object? GetAt(int index) => Inner[index];
/// <summary>
/// Determines the index of a specific item in the collection.
/// </summary>
/// <param name="item">The object to locate in the collection.</param>
/// <returns>The index of value if found in the list; otherwise, -1.</returns>
public int IndexOf(object? item) => Inner.IndexOf(item);
/// <summary>
/// Retrieves the unique identifier (key) for the item at the specified index.
/// Gets or creates an <see cref="ItemsSourceView"/> for the specified enumerable.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The index.</returns>
/// <param name="items">The enumerable.</param>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// This method handles the following three cases:
/// - If <paramref name="items"/> is null, returns <see cref="Empty"/>
/// - If <paramref name="items"/> is an <see cref="ItemsSourceView"/> returns the existing
/// <see cref="ItemsSourceView"/>
/// - Otherwise creates a new <see cref="ItemsSourceView"/>
/// </remarks>
public int IndexFromKey(string key)
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
throw new NotImplementedException();
return items switch
{
ItemsSourceView isv => isv,
null => Empty,
_ => new ItemsSourceView(items)
};
}
internal void AddListener(ICollectionChangedListener listener)
public IEnumerator<object?> GetEnumerator()
{
if (Inner is INotifyCollectionChanged incc)
static IEnumerator<object> EnumerateItems(IList list)
{
CollectionChangedEventManager.Instance.AddListener(incc, listener);
foreach (var o in list)
yield return o;
}
var inner = Inner;
return inner switch
{
IEnumerable<object> e => e.GetEnumerator(),
_ => EnumerateItems(inner),
};
}
internal void RemoveListener(ICollectionChangedListener listener)
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
if (Inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.RemoveListener(incc, listener);
}
}
protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args)
void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
_collectionChanged?.Invoke(this, args);
_collectionChanged?.Invoke(this, e);
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
void ICollectionChangedListener.PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
OnItemsSourceChanged(e);
_postCollectionChanged?.Invoke(this, e);
}
private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView));
/// <summary>
/// Retrieves the index of the item that has the specified unique identifier (key).
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The key</returns>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// </remarks>
internal string KeyFromIndex(int index) => throw new NotImplementedException();
}
public class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
@ -227,9 +236,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
#pragma warning disable CS8603
public new T this[int index] => GetAt(index);
#pragma warning restore CS8603
/// <summary>
/// Retrieves the item at the specified index.
@ -238,23 +245,44 @@ namespace Avalonia.Controls
/// <returns>The item.</returns>
public new T GetAt(int index) => (T)Inner[index]!;
public IEnumerator<T> GetEnumerator() => Inner.Cast<T>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
public new IEnumerator<T> GetEnumerator()
{
if (items is ItemsSourceView<T> isv)
static IEnumerator<T> EnumerateItems(IList list)
{
return isv;
foreach (var o in list)
yield return (T)o;
}
else if (items is null)
var inner = Inner;
return inner switch
{
return Empty;
}
else
IEnumerable<T> e => e.GetEnumerator(),
_ => EnumerateItems(inner),
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
/// <summary>
/// Gets or creates an <see cref="ItemsSourceView{T}"/> for the specified enumerable.
/// </summary>
/// <param name="items">The enumerable.</param>
/// <remarks>
/// This method handles the following three cases:
/// - If <paramref name="items"/> is null, returns <see cref="Empty"/>
/// - If <paramref name="items"/> is an <see cref="ItemsSourceView"/> returns the existing
/// <see cref="ItemsSourceView"/>
/// - Otherwise creates a new <see cref="ItemsSourceView"/>
/// </remarks>
public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
{
return items switch
{
return new ItemsSourceView<T>(items);
}
ItemsSourceView<T> isv => isv,
null => Empty,
_ => new ItemsSourceView<T>(items)
};
}
}
}

2
src/Avalonia.Controls/MenuItem.cs

@ -659,7 +659,7 @@ namespace Avalonia.Controls
if (value)
{
foreach (var item in Items!.OfType<MenuItem>())
foreach (var item in ItemsView.OfType<MenuItem>())
{
item.TryUpdateCanExecute();
}

11
src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs

@ -23,9 +23,7 @@ namespace Avalonia.Controls.Presenters
_presenter = presenter;
_presenter.ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
if (_presenter.ItemsControl.Items is INotifyCollectionChanged incc)
incc.CollectionChanged += OnItemsChanged;
_presenter.ItemsControl.ItemsView.PostCollectionChanged += OnItemsChanged;
OnItemsChanged(null, CollectionUtils.ResetEventArgs);
}
@ -35,9 +33,7 @@ namespace Avalonia.Controls.Presenters
if (_presenter.ItemsControl is { } itemsControl)
{
itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
if (itemsControl.Items is INotifyCollectionChanged incc)
incc.CollectionChanged -= OnItemsChanged;
itemsControl.ItemsView.PostCollectionChanged -= OnItemsChanged;
ClearItemsControlLogicalChildren();
}
@ -117,8 +113,7 @@ namespace Avalonia.Controls.Presenters
case NotifyCollectionChangedAction.Reset:
ClearItemsControlLogicalChildren();
children.Clear();
if (_presenter.ItemsControl?.Items is { } items)
Add(0, items);
Add(0, _presenter.ItemsControl.ItemsView);
break;
}
}

2
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -380,7 +380,7 @@ namespace Avalonia.Controls.Primitives
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public void ScrollIntoView(object item) => ScrollIntoView(IndexOf(Items, item));
public void ScrollIntoView(object item) => ScrollIntoView(ItemsView.IndexOf(item));
/// <summary>
/// Tries to get the container that was the source of an event.

1
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -628,7 +628,6 @@ namespace Avalonia.Controls
oldValue.CollectionChanged -= OnItemsSourceViewChanged;
}
ItemsSourceView?.Dispose();
ItemsSourceView = newValue;
if (newValue != null)

6
src/Avalonia.Controls/Selection/SelectionNodeBase.cs

@ -21,10 +21,12 @@ namespace Avalonia.Controls.Selection
{
if (_source != value)
{
ItemsView?.RemoveListener(this);
if (ItemsView?.Inner is INotifyCollectionChanged inccOld)
CollectionChangedEventManager.Instance.RemoveListener(inccOld, this);
_source = value;
ItemsView = value is object ? ItemsSourceView<T>.GetOrCreate(value) : null;
ItemsView?.AddListener(this);
if (ItemsView?.Inner is INotifyCollectionChanged inccNew)
CollectionChangedEventManager.Instance.AddListener(inccNew, this);
}
}
}

9
src/Avalonia.Controls/TreeView.cs

@ -192,11 +192,8 @@ namespace Avalonia.Controls
void AddItems(ItemsControl itemsControl)
{
if (itemsControl.Items is { } items)
{
foreach (var item in items)
allItems.Add(item);
}
foreach (var item in itemsControl.ItemsView)
allItems.Add(item!);
foreach (var child in itemsControl.GetRealizedContainers())
{
@ -513,7 +510,7 @@ namespace Avalonia.Controls
}
else
{
SelectedItem = ElementAt(Items, 0);
SelectedItem = ItemsView[0];
}
}

4
src/Avalonia.Controls/TreeViewItem.cs

@ -150,7 +150,7 @@ namespace Avalonia.Controls
switch (e.Key)
{
case Key.Right:
if (Items != null && Items.Cast<object>().Any() && !IsExpanded)
if (ItemsView.Count > 0 && !IsExpanded)
{
IsExpanded = true;
e.Handled = true;
@ -158,7 +158,7 @@ namespace Avalonia.Controls
break;
case Key.Left:
if (Items is not null && Items.Cast<object>().Any() && IsExpanded)
if (ItemsView.Count > 0 && IsExpanded)
{
if (IsFocused)
{

12
src/Avalonia.Controls/VirtualizingCarouselPanel.cs

@ -95,7 +95,7 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize)
{
var items = ItemsControl?.Items as IList ?? Array.Empty<object?>();
var items = ItemsControl?.ItemsView ?? ItemsSourceView.Empty;
var index = (int)_offset.X;
if (index != _realizedIndex)
@ -195,7 +195,7 @@ namespace Avalonia.Controls
return null;
}
protected override void OnItemsChanged(IList items, NotifyCollectionChangedEventArgs e)
protected override void OnItemsChanged(IReadOnlyList<object?> items, NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(items, e);
@ -247,7 +247,7 @@ namespace Avalonia.Controls
InvalidateMeasure();
}
private Control GetOrCreateElement(IList items, int index)
private Control GetOrCreateElement(ItemsSourceView items, int index)
{
return GetRealizedElement(index) ??
GetItemIsOwnContainer(items, index) ??
@ -260,7 +260,7 @@ namespace Avalonia.Controls
return _realizedIndex == index ? _realized : null;
}
private Control? GetItemIsOwnContainer(IList items, int index)
private Control? GetItemIsOwnContainer(ItemsSourceView items, int index)
{
Debug.Assert(ItemsControl is not null);
@ -285,7 +285,7 @@ namespace Avalonia.Controls
return null;
}
private Control? GetRecycledElement(IList items, int index)
private Control? GetRecycledElement(ItemsSourceView items, int index)
{
Debug.Assert(ItemsControl is not null);
@ -303,7 +303,7 @@ namespace Avalonia.Controls
return null;
}
private Control CreateElement(IList items, int index)
private Control CreateElement(ItemsSourceView items, int index)
{
Debug.Assert(ItemsControl is not null);

22
src/Avalonia.Controls/VirtualizingPanel.cs

@ -99,7 +99,7 @@ namespace Avalonia.Controls
/// new collection, in which case the <see cref="NotifyCollectionChangedAction"/> will
/// be <see cref="NotifyCollectionChangedAction.Reset"/>.
/// </remarks>
protected virtual void OnItemsChanged(IList items, NotifyCollectionChangedEventArgs e)
protected virtual void OnItemsChanged(IReadOnlyList<object?> items, NotifyCollectionChangedEventArgs e)
{
}
@ -158,9 +158,7 @@ namespace Avalonia.Controls
ItemsControl = itemsControl;
ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
if (ItemsControl.Items is INotifyCollectionChanged incc)
incc.CollectionChanged += OnItemsControlItemsChanged;
ItemsControl.ItemsView.PostCollectionChanged += OnItemsControlItemsChanged;
}
internal void Detach()
@ -168,9 +166,7 @@ namespace Avalonia.Controls
var itemsControl = EnsureItemsControl();
itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
if (itemsControl.Items is INotifyCollectionChanged incc)
incc.CollectionChanged -= OnItemsControlItemsChanged;
itemsControl.ItemsView.PostCollectionChanged -= OnItemsControlItemsChanged;
ItemsControl = null;
Children.Clear();
@ -187,20 +183,18 @@ namespace Avalonia.Controls
private protected virtual void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ItemsControl.ItemsProperty)
if (e.Property == ItemsControl.ItemsViewProperty)
{
if (e.OldValue is INotifyCollectionChanged inccOld)
inccOld.CollectionChanged -= OnItemsControlItemsChanged;
var (oldValue, newValue) = e.GetOldAndNewValue<ItemsSourceView>();
oldValue.PostCollectionChanged -= OnItemsControlItemsChanged;
Refresh();
if (e.NewValue is INotifyCollectionChanged inccNew)
inccNew.CollectionChanged += OnItemsControlItemsChanged;
newValue.PostCollectionChanged += OnItemsControlItemsChanged;
}
}
private void OnItemsControlItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (_itemsControl?.Items is IList items)
OnItemsChanged(items, e);
OnItemsChanged(_itemsControl?.ItemsView ?? ItemsSourceView.Empty, e);
}
[DoesNotReturn]

25
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
@ -65,7 +64,7 @@ namespace Avalonia.Controls
try
{
var items = ItemsControl?.Items as IList;
var items = ItemsControl?.ItemsView;
if (items is null || items.Count == 0)
{
@ -149,7 +148,7 @@ namespace Avalonia.Controls
}
}
protected override void OnItemsChanged(IList items, NotifyCollectionChangedEventArgs e)
protected override void OnItemsChanged(IReadOnlyList<object?> items, NotifyCollectionChangedEventArgs e)
{
if (_realizedElements is null)
return;
@ -172,7 +171,7 @@ namespace Avalonia.Controls
protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
{
var count = (ItemsControl?.Items as IList)?.Count ?? 0;
var count = ItemsControl?.ItemsView.Count ?? 0;
if (count == 0 || from is not Control fromControl)
return null;
@ -239,7 +238,7 @@ namespace Avalonia.Controls
protected internal override Control? ScrollIntoView(int index)
{
var items = ItemsControl?.Items as IList;
var items = ItemsControl?.ItemsView;
if (_isInLayout || items is null || index < 0 || index >= items.Count)
return null;
@ -296,7 +295,7 @@ namespace Avalonia.Controls
return _realizedElements?.Elements ?? Array.Empty<Control>();
}
private MeasureViewport CalculateMeasureViewport(IList items)
private MeasureViewport CalculateMeasureViewport(ItemsSourceView items)
{
Debug.Assert(_realizedElements is not null);
@ -337,7 +336,7 @@ namespace Avalonia.Controls
};
}
private Size CalculateDesiredSize(Orientation orientation, IList items, double sizeV)
private Size CalculateDesiredSize(Orientation orientation, ItemsSourceView items, double sizeV)
{
var sizeU = EstimateElementSizeU() * items.Count;
@ -402,10 +401,10 @@ namespace Avalonia.Controls
private void GenerateElements(Size availableSize, ref MeasureViewport viewport)
{
Debug.Assert(ItemsControl?.Items is IList);
Debug.Assert(ItemsControl is not null);
Debug.Assert(_measureElements is not null);
var items = (IList)ItemsControl!.Items;
var items = ItemsControl!.ItemsView;
var horizontal = Orientation == Orientation.Horizontal;
var index = viewport.firstIndex;
var u = viewport.startU;
@ -426,7 +425,7 @@ namespace Avalonia.Controls
} while (u < viewport.viewportUEnd && index < items.Count);
}
private Control GetOrCreateElement(IList items, int index)
private Control GetOrCreateElement(ItemsSourceView items, int index)
{
var e = GetRealizedElement(index) ??
GetItemIsOwnContainer(items, index) ??
@ -443,7 +442,7 @@ namespace Avalonia.Controls
return _realizedElements?.GetElement(index);
}
private Control? GetItemIsOwnContainer(IList items, int index)
private Control? GetItemIsOwnContainer(ItemsSourceView items, int index)
{
if (items[index] is Control controlItem)
{
@ -466,7 +465,7 @@ namespace Avalonia.Controls
return null;
}
private Control? GetRecycledElement(IList items, int index)
private Control? GetRecycledElement(ItemsSourceView items, int index)
{
Debug.Assert(ItemsControl is not null);
@ -485,7 +484,7 @@ namespace Avalonia.Controls
return null;
}
private Control CreateElement(IList items, int index)
private Control CreateElement(ItemsSourceView items, int index)
{
Debug.Assert(ItemsControl is not null);

Loading…
Cancel
Save