diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs
index 2836937b79..b2663f3213 100644
--- a/src/Avalonia.Controls/ItemsSourceView.cs
+++ b/src/Avalonia.Controls/ItemsSourceView.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Controls.Utils;
@@ -15,29 +16,30 @@ using Avalonia.Controls.Utils;
namespace Avalonia.Controls
{
///
- /// Represents a standardized view of the supported interactions between a given
- /// or and its items.
+ /// Represents a standardized view of the supported interactions between a given ItemsSource
+ /// object and an control.
///
- public class ItemsSourceView : INotifyCollectionChanged, IDisposable, IReadOnlyList
+ ///
+ /// Components written to work with ItemsRepeater should consume the
+ /// 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.
+ ///
+ public class ItemsSourceView : INotifyCollectionChanged, IDisposable
{
///
- /// Gets an empty
+ /// Gets an empty
///
- public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
+ public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
- private readonly IList _inner;
+ private protected readonly IList _inner;
private INotifyCollectionChanged? _notifyCollectionChanged;
///
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
///
/// The data source.
- public ItemsSourceView(IEnumerable source)
- : this((IEnumerable)source)
- {
- }
-
- private protected ItemsSourceView(IEnumerable source)
+ public ItemsSourceView(IEnumerable source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
@@ -45,13 +47,13 @@ namespace Avalonia.Controls
{
_inner = list;
}
- else if (source is IEnumerable enumerable)
+ else if (source is IEnumerable objectEnumerable)
{
- _inner = new List(enumerable);
+ _inner = new List(objectEnumerable);
}
else
{
- _inner = new List(source.Cast());
+ _inner = new List(source.Cast());
}
ListenToCollectionChanges();
@@ -75,7 +77,7 @@ namespace Avalonia.Controls
///
/// The index.
/// The item.
- public T this[int index] => GetAt(index);
+ public object? this[int index] => GetAt(index);
///
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
@@ -96,13 +98,13 @@ namespace Avalonia.Controls
///
/// The index.
/// The item.
- public T GetAt(int index) => _inner is IList typed ? typed[index] : (T)_inner[index];
+ public object? GetAt(int index) => _inner[index];
public int IndexOf(object? item) => _inner.IndexOf(item);
- public static ItemsSourceView GetOrCreate(IEnumerable? items)
+ public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
- if (items is ItemsSourceView isv)
+ if (items is ItemsSourceView isv)
{
return isv;
}
@@ -112,7 +114,7 @@ namespace Avalonia.Controls
}
else
{
- return new ItemsSourceView(items);
+ return new ItemsSourceView(items);
}
}
@@ -142,11 +144,6 @@ namespace Avalonia.Controls
throw new NotImplementedException();
}
- public IEnumerator GetEnumerator() => _inner is IList typed ?
- typed.GetEnumerator() : _inner.Cast().GetEnumerator();
-
- IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
-
internal void AddListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
@@ -183,11 +180,61 @@ namespace Avalonia.Controls
}
}
- public class ItemsSourceView : ItemsSourceView
+ public class ItemsSourceView : ItemsSourceView, IReadOnlyList
{
- public ItemsSourceView(IEnumerable source)
+ ///
+ /// Gets an empty
+ ///
+ public new static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
+
+ ///
+ /// Initializes a new instance of the ItemsSourceView class for the specified data source.
+ ///
+ /// The data source.
+ public ItemsSourceView(IEnumerable source)
: base(source)
{
}
+
+ private ItemsSourceView(IEnumerable source)
+ : base(source)
+ {
+ }
+
+ ///
+ /// Retrieves the item at the specified index.
+ ///
+ /// The index.
+ /// The item.
+#pragma warning disable CS8603
+ public new T this[int index] => GetAt(index);
+#pragma warning restore CS8603
+
+ ///
+ /// Retrieves the item at the specified index.
+ ///
+ /// The index.
+ /// The item.
+ [return: MaybeNull]
+ public new T GetAt(int index) => (T)_inner[index];
+
+ public IEnumerator GetEnumerator() => _inner.Cast().GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
+
+ public static new ItemsSourceView GetOrCreate(IEnumerable? items)
+ {
+ if (items is ItemsSourceView isv)
+ {
+ return isv;
+ }
+ else if (items is null)
+ {
+ return Empty;
+ }
+ else
+ {
+ return new ItemsSourceView(items);
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index 0427aeeb40..f34a358925 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -32,46 +32,10 @@ namespace Avalonia.Controls.Selection
Source = source;
}
- public override IEnumerable? Source
+ public new IEnumerable? Source
{
- get => base.Source;
- set
- {
- if (base.Source != value)
- {
- if (_operation is object)
- {
- throw new InvalidOperationException("Cannot change source while update is in progress.");
- }
-
- if (base.Source is object && value is object)
- {
- using var update = BatchUpdate();
- update.Operation.SkipLostSelection = true;
- Clear();
- }
-
- base.Source = value;
-
- using (var update = BatchUpdate())
- {
- update.Operation.IsSourceUpdate = true;
-
- if (_hasInitSelectedItem)
- {
- SelectedItem = _initSelectedItem;
- _initSelectedItem = default;
- _hasInitSelectedItem = false;
- }
- else
- {
- TrimInvalidSelections(update.Operation);
- }
-
- RaisePropertyChanged(nameof(Source));
- }
- }
- }
+ get => base.Source as IEnumerable;
+ set => SetSource(value);
}
public bool SingleSelect
@@ -168,7 +132,7 @@ namespace Avalonia.Controls.Selection
IEnumerable? ISelectionModel.Source
{
get => Source;
- set => Source = (IEnumerable?)value;
+ set => SetSource(value);
}
object? ISelectionModel.SelectedItem
@@ -298,6 +262,44 @@ namespace Avalonia.Controls.Selection
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+ private void SetSource(IEnumerable? value)
+ {
+ if (base.Source != value)
+ {
+ if (_operation is object)
+ {
+ throw new InvalidOperationException("Cannot change source while update is in progress.");
+ }
+
+ if (base.Source is object && value is object)
+ {
+ using var update = BatchUpdate();
+ update.Operation.SkipLostSelection = true;
+ Clear();
+ }
+
+ base.Source = value;
+
+ using (var update = BatchUpdate())
+ {
+ update.Operation.IsSourceUpdate = true;
+
+ if (_hasInitSelectedItem)
+ {
+ SelectedItem = _initSelectedItem;
+ _initSelectedItem = default;
+ _hasInitSelectedItem = false;
+ }
+ else
+ {
+ TrimInvalidSelections(update.Operation);
+ }
+
+ RaisePropertyChanged(nameof(Source));
+ }
+ }
+ }
+
private protected override void OnIndexesChanged(int shiftIndex, int shiftDelta)
{
IndexesChanged?.Invoke(this, new SelectionModelIndexesChangedEventArgs(shiftIndex, shiftDelta));
@@ -511,7 +513,7 @@ namespace Avalonia.Controls.Selection
return default;
}
- return ItemsView.GetAt(index);
+ return ItemsView[index];
}
private int CoerceIndex(int index)
@@ -710,7 +712,7 @@ namespace Avalonia.Controls.Selection
public int SelectedIndex { get; set; }
public List? SelectedRanges { get; set; }
public List? DeselectedRanges { get; set; }
- public IReadOnlyList DeselectedItems { get; set; }
+ public IReadOnlyList? DeselectedItems { get; set; }
}
}
}
diff --git a/src/Avalonia.Controls/Selection/SelectionNodeBase.cs b/src/Avalonia.Controls/Selection/SelectionNodeBase.cs
index 4796e8b9ca..ff3b8f43a8 100644
--- a/src/Avalonia.Controls/Selection/SelectionNodeBase.cs
+++ b/src/Avalonia.Controls/Selection/SelectionNodeBase.cs
@@ -10,12 +10,12 @@ namespace Avalonia.Controls.Selection
{
public abstract class SelectionNodeBase : ICollectionChangedListener
{
- private IEnumerable? _source;
+ private IEnumerable? _source;
private bool _rangesEnabled;
private List? _ranges;
private int _collectionChanging;
- public virtual IEnumerable? Source
+ protected IEnumerable? Source
{
get => _source;
set
diff --git a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs
index 79b8da8f7b..345518e729 100644
--- a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs
@@ -237,6 +237,14 @@ namespace Avalonia.Controls.UnitTests.Selection
Assert.Equal(1, raised);
}
+
+ [Fact]
+ public void Can_Assign_ValueType_Collection_To_SelectionModel_Of_Object()
+ {
+ var target = (ISelectionModel)new SelectionModel();
+
+ target.Source = new[] { 1, 2, 3 };
+ }
}
public class SelectedIndex