From 518391b37db2ff1e442f5369a4f2c789039924c2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 9 Dec 2022 14:48:41 +0100 Subject: [PATCH] Make ItemsSourceView ctors private. So in future we could have different `ItemSourceView` concrete implementations that e.g. have an inner `IReadOnlyList` in order to address #8764. --- src/Avalonia.Controls/ItemsControl.cs | 2 +- src/Avalonia.Controls/ItemsSourceView.cs | 71 ++++++++++++------- .../Repeater/ItemsRepeater.cs | 2 +- .../Selection/SelectionNodeBase.cs | 2 +- .../ItemsSourceViewTests.cs | 11 +-- 5 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index b797ae8295..f8aa38670f 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -96,7 +96,7 @@ namespace Avalonia.Controls /// public ItemsControl() { - _itemsView = new(_items); + _itemsView = ItemsSourceView.GetOrCreate(_items); _itemsView.PostCollectionChanged += ItemsCollectionChanged; UpdatePseudoClasses(0); } diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs index 27fa7cbe48..c8fc76255c 100644 --- a/src/Avalonia.Controls/ItemsSourceView.cs +++ b/src/Avalonia.Controls/ItemsSourceView.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the ItemsSourceView class for the specified data source. /// /// The data source. - public ItemsSourceView(IEnumerable source) + private protected ItemsSourceView(IEnumerable source) { _inner = source switch { @@ -166,6 +166,48 @@ namespace Avalonia.Controls }; } + /// + /// Gets or creates an for the specified enumerable. + /// + /// The enumerable. + /// + /// This method handles the following three cases: + /// - If is null, returns + /// - If is an returns the existing + /// + /// - Otherwise creates a new + /// + public static ItemsSourceView GetOrCreate(IEnumerable? items) + { + return items switch + { + ItemsSourceView isv => isv, + null => ItemsSourceView.Empty, + _ => new ItemsSourceView(items) + }; + } + + /// + /// Gets or creates an for the specified enumerable. + /// + /// The enumerable. + /// + /// This method handles the following three cases: + /// - If is null, returns + /// - If is an returns the existing + /// + /// - Otherwise creates a new + /// + public static ItemsSourceView GetOrCreate(IEnumerable? items) + { + return items switch + { + ItemsSourceView isv => isv, + null => ItemsSourceView.Empty, + _ => new ItemsSourceView(items) + }; + } + public IEnumerator GetEnumerator() { static IEnumerator EnumerateItems(IList list) @@ -210,7 +252,7 @@ namespace Avalonia.Controls internal string KeyFromIndex(int index) => throw new NotImplementedException(); } - public class ItemsSourceView : ItemsSourceView, IReadOnlyList + public sealed class ItemsSourceView : ItemsSourceView, IReadOnlyList { /// /// Gets an empty @@ -221,12 +263,12 @@ namespace Avalonia.Controls /// Initializes a new instance of the ItemsSourceView class for the specified data source. /// /// The data source. - public ItemsSourceView(IEnumerable source) + internal ItemsSourceView(IEnumerable source) : base(source) { } - private ItemsSourceView(IEnumerable source) + internal ItemsSourceView(IEnumerable source) : base(source) { } @@ -263,26 +305,5 @@ namespace Avalonia.Controls } IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator(); - - /// - /// Gets or creates an for the specified enumerable. - /// - /// The enumerable. - /// - /// This method handles the following three cases: - /// - If is null, returns - /// - If is an returns the existing - /// - /// - Otherwise creates a new - /// - public static new ItemsSourceView GetOrCreate(IEnumerable? items) - { - return items switch - { - ItemsSourceView isv => isv, - null => Empty, - _ => new ItemsSourceView(items) - }; - } } } diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index b82637b667..4de6a5188d 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -442,7 +442,7 @@ namespace Avalonia.Controls var newDataSource = newEnumerable as ItemsSourceView; if (newEnumerable != null && newDataSource == null) { - newDataSource = new ItemsSourceView(newEnumerable); + newDataSource = ItemsSourceView.GetOrCreate(newEnumerable); } OnDataSourcePropertyChanged(ItemsSourceView, newDataSource); diff --git a/src/Avalonia.Controls/Selection/SelectionNodeBase.cs b/src/Avalonia.Controls/Selection/SelectionNodeBase.cs index c5a1a6a7ce..01b89325af 100644 --- a/src/Avalonia.Controls/Selection/SelectionNodeBase.cs +++ b/src/Avalonia.Controls/Selection/SelectionNodeBase.cs @@ -24,7 +24,7 @@ namespace Avalonia.Controls.Selection if (ItemsView?.Inner is INotifyCollectionChanged inccOld) CollectionChangedEventManager.Instance.RemoveListener(inccOld, this); _source = value; - ItemsView = value is object ? ItemsSourceView.GetOrCreate(value) : null; + ItemsView = value is object ? ItemsSourceView.GetOrCreate(value) : null; if (ItemsView?.Inner is INotifyCollectionChanged inccNew) CollectionChangedEventManager.Instance.AddListener(inccNew, this); } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs index 22a9b28648..df842b21a7 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests public void Only_Subscribes_To_Source_CollectionChanged_When_CollectionChanged_Subscribed() { var source = new AvaloniaList(); - var target = new ItemsSourceView(source); + var target = ItemsSourceView.GetOrCreate(source); var debug = (INotifyCollectionChangedDebug)source; Assert.Null(debug.GetCollectionChangedSubscribers()); @@ -31,18 +31,11 @@ namespace Avalonia.Controls.UnitTests Assert.Null(debug.GetCollectionChangedSubscribers()); } - [Fact] - public void Cannot_Wrap_An_ItemsSourceView_In_Another() - { - var source = new ItemsSourceView(new string[0]); - Assert.Throws(() => new ItemsSourceView(source)); - } - [Fact] public void Cannot_Create_ItemsSourceView_With_Collection_That_Implements_INCC_But_Not_List() { var source = new InvalidCollection(); - Assert.Throws(() => new ItemsSourceView(source)); + Assert.Throws(() => ItemsSourceView.GetOrCreate(source)); } private class InvalidCollection : INotifyCollectionChanged, IEnumerable