Browse Source

Started integrating new SelectionModel.

pull/4533/head
Steven Kirk 6 years ago
parent
commit
a9b04a5c2c
  1. 4
      src/Avalonia.Controls/ListBox.cs
  2. 947
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  3. 28
      src/Avalonia.Controls/Selection/ISelectionModel.cs
  4. 72
      src/Avalonia.Controls/Selection/SelectionModel.cs
  5. 259
      src/Avalonia.Controls/Utils/SelectedItemsSync.cs
  6. 98
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  7. 4
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  8. 32
      tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Multiple.cs
  9. 120
      tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs

4
src/Avalonia.Controls/ListBox.cs

@ -95,12 +95,12 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Selects all items in the <see cref="ListBox"/>. /// Selects all items in the <see cref="ListBox"/>.
/// </summary> /// </summary>
public void SelectAll() => base.SelectAll(); public void SelectAll() => Selection.SelectAll();
/// <summary> /// <summary>
/// Deselects all items in the <see cref="ListBox"/>. /// Deselects all items in the <see cref="ListBox"/>.
/// </summary> /// </summary>
public void UnselectAll() => base.UnselectAll(); public void UnselectAll() => Selection.Clear();
/// <inheritdoc/> /// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()

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

File diff suppressed because it is too large

28
src/Avalonia.Controls/Selection/ISelectionModel.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.Selection
bool SingleSelect { get; set; } bool SingleSelect { get; set; }
int SelectedIndex { get; set; } int SelectedIndex { get; set; }
IReadOnlyList<int> SelectedIndexes { get; } IReadOnlyList<int> SelectedIndexes { get; }
object? SelectedItem { get; } object? SelectedItem { get; set; }
IReadOnlyList<object?> SelectedItems { get; } IReadOnlyList<object?> SelectedItems { get; }
int AnchorIndex { get; set; } int AnchorIndex { get; set; }
int Count { get; } int Count { get; }
@ -30,19 +30,37 @@ namespace Avalonia.Controls.Selection
void Deselect(int index); void Deselect(int index);
void SelectRange(int start, int end); void SelectRange(int start, int end);
void DeselectRange(int start, int end); void DeselectRange(int start, int end);
void SelectAll();
void Clear(); void Clear();
} }
public static class SelectionModelExtensions public static class SelectionModelExtensions
{ {
public static void SelectAll(this ISelectionModel model) public static IDisposable BatchUpdate(this ISelectionModel model)
{ {
model.SelectRange(0, int.MaxValue); return new BatchUpdateOperation(model);
} }
public static void SelectRangeFromAnchor(this ISelectionModel model, int to) public struct BatchUpdateOperation : IDisposable
{ {
model.SelectRange(model.AnchorIndex, to); private readonly ISelectionModel _owner;
private bool _isDisposed;
public BatchUpdateOperation(ISelectionModel owner)
{
_owner = owner;
_isDisposed = false;
owner.BeginBatchUpdate();
}
public void Dispose()
{
if (!_isDisposed)
{
_owner?.EndBatchUpdate();
_isDisposed = true;
}
}
} }
} }
} }

72
src/Avalonia.Controls/Selection/SelectionModel.cs

@ -20,6 +20,8 @@ namespace Avalonia.Controls.Selection
private SelectedItems<T>? _selectedItems; private SelectedItems<T>? _selectedItems;
private SelectedItems<T>.Untyped? _selectedItemsUntyped; private SelectedItems<T>.Untyped? _selectedItemsUntyped;
private EventHandler<SelectionModelSelectionChangedEventArgs>? _untypedSelectionChanged; private EventHandler<SelectionModelSelectionChangedEventArgs>? _untypedSelectionChanged;
[AllowNull] private T _initSelectedItem = default;
private bool _hasInitSelectedItem;
public SelectionModel() public SelectionModel()
{ {
@ -51,7 +53,18 @@ namespace Avalonia.Controls.Selection
using var update = BatchUpdate(); using var update = BatchUpdate();
update.Operation.IsSourceUpdate = true; update.Operation.IsSourceUpdate = true;
TrimInvalidSelections(update.Operation);
if (_hasInitSelectedItem)
{
SelectedItem = _initSelectedItem;
_initSelectedItem = default;
_hasInitSelectedItem = false;
}
else
{
TrimInvalidSelections(update.Operation);
}
RaisePropertyChanged(nameof(Source)); RaisePropertyChanged(nameof(Source));
} }
} }
@ -90,10 +103,37 @@ namespace Avalonia.Controls.Selection
public IReadOnlyList<int> SelectedIndexes => _selectedIndexes ??= new SelectedIndexes<T>(this); public IReadOnlyList<int> SelectedIndexes => _selectedIndexes ??= new SelectedIndexes<T>(this);
[MaybeNull] [MaybeNull, AllowNull]
public T SelectedItem => GetItemAt(_selectedIndex); public T SelectedItem
{
get => ItemsView is object ? GetItemAt(_selectedIndex) : _initSelectedItem;
set
{
if (ItemsView is object)
{
SelectedIndex = ItemsView.IndexOf(value!);
}
else
{
Clear();
_initSelectedItem = value;
_hasInitSelectedItem = true;
}
}
}
public IReadOnlyList<T> SelectedItems => _selectedItems ??= new SelectedItems<T>(this); public IReadOnlyList<T> SelectedItems
{
get
{
if (ItemsView is null && _hasInitSelectedItem)
{
return new[] { _initSelectedItem };
}
return _selectedItems ??= new SelectedItems<T>(this);
}
}
public int AnchorIndex public int AnchorIndex
{ {
@ -127,7 +167,22 @@ namespace Avalonia.Controls.Selection
set => Source = (IEnumerable<T>?)value; set => Source = (IEnumerable<T>?)value;
} }
object? ISelectionModel.SelectedItem => SelectedItem; object? ISelectionModel.SelectedItem
{
get => SelectedItem;
set
{
if (value is T t)
{
SelectedItem = t;
}
else
{
SelectedIndex = -1;
}
}
}
IReadOnlyList<object?> ISelectionModel.SelectedItems IReadOnlyList<object?> ISelectionModel.SelectedItems
{ {
@ -226,8 +281,12 @@ namespace Avalonia.Controls.Selection
{ {
o.SelectedIndex = -1; o.SelectedIndex = -1;
} }
_initSelectedItem = default;
_hasInitSelectedItem = false;
} }
public void SelectAll() => SelectRange(0, int.MaxValue);
public void Clear() => DeselectRange(0, int.MaxValue); public void Clear() => DeselectRange(0, int.MaxValue);
protected void RaisePropertyChanged(string propertyName) protected void RaisePropertyChanged(string propertyName)
@ -429,6 +488,9 @@ namespace Avalonia.Controls.Selection
{ {
o.SelectedIndex = o.AnchorIndex = start; o.SelectedIndex = o.AnchorIndex = start;
} }
_initSelectedItem = default;
_hasInitSelectedItem = false;
} }
[return: MaybeNull] [return: MaybeNull]

259
src/Avalonia.Controls/Utils/SelectedItemsSync.cs

@ -0,0 +1,259 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Selection;
#nullable enable
namespace Avalonia.Controls.Utils
{
/// <summary>
/// Synchronizes an <see cref="ISelectionModel"/> with a list of SelectedItems.
/// </summary>
internal class SelectedItemsSync
{
private IList? _selectedItems;
private bool _updatingItems;
private bool _updatingModel;
private bool _initializeOnSourceAssignment;
public SelectedItemsSync(ISelectionModel model)
{
model = model ?? throw new ArgumentNullException(nameof(model));
Model = model;
}
public ISelectionModel Model { get; private set; }
public IList GetOrCreateSelectedItems()
{
if (_selectedItems == null)
{
var items = new AvaloniaList<object?>(Model.SelectedItems);
items.CollectionChanged += ItemsCollectionChanged;
Model.SelectionChanged += SelectionModelSelectionChanged;
_selectedItems = items;
}
return _selectedItems;
}
public void SetSelectedItems(IList? items)
{
items ??= new AvaloniaList<object>();
if (items.IsFixedSize)
{
throw new NotSupportedException(
"Cannot assign fixed size selection to SelectedItems.");
}
if (_selectedItems is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= ItemsCollectionChanged;
}
if (_selectedItems == null)
{
Model.SelectionChanged += SelectionModelSelectionChanged;
}
try
{
_updatingModel = true;
_selectedItems = items;
if (Model.Source is object)
{
using (Model.BatchUpdate())
{
Model.Clear();
Add(items);
}
}
else if (!_initializeOnSourceAssignment)
{
Model.PropertyChanged += SelectionModelPropertyChanged;
_initializeOnSourceAssignment = true;
}
if (_selectedItems is INotifyCollectionChanged incc2)
{
incc2.CollectionChanged += ItemsCollectionChanged;
}
}
finally
{
_updatingModel = false;
}
}
public void SetModel(ISelectionModel model)
{
model = model ?? throw new ArgumentNullException(nameof(model));
if (_selectedItems != null)
{
Model.PropertyChanged -= SelectionModelPropertyChanged;
Model.SelectionChanged -= SelectionModelSelectionChanged;
Model = model;
Model.SelectionChanged += SelectionModelSelectionChanged;
_initializeOnSourceAssignment = false;
try
{
_updatingItems = true;
_selectedItems.Clear();
foreach (var i in model.SelectedItems)
{
_selectedItems.Add(i);
}
}
finally
{
_updatingItems = false;
}
}
}
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_updatingItems)
{
return;
}
if (_selectedItems == null)
{
throw new AvaloniaInternalException("CollectionChanged raised but we don't have items.");
}
void Remove()
{
foreach (var i in e.OldItems)
{
var index = IndexOf(Model.Source, i);
if (index != -1)
{
Model.Deselect(index);
}
}
}
try
{
using var operation = Model.BatchUpdate();
_updatingModel = true;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Add(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
Remove();
break;
case NotifyCollectionChangedAction.Replace:
Remove();
Add(e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
Model.Clear();
Add(_selectedItems);
break;
}
}
finally
{
_updatingModel = false;
}
}
private void Add(IList newItems)
{
foreach (var i in newItems)
{
var index = IndexOf(Model.Source, i);
if (index != -1)
{
Model.Select(index);
}
}
}
private void SelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_initializeOnSourceAssignment &&
_selectedItems != null &&
e.PropertyName == nameof(ISelectionModel.Source))
{
try
{
_updatingModel = true;
Add(_selectedItems);
_initializeOnSourceAssignment = false;
}
finally
{
_updatingModel = false;
}
}
}
private void SelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
if (_updatingModel)
{
return;
}
if (_selectedItems == null)
{
throw new AvaloniaInternalException("SelectionModelChanged raised but we don't have items.");
}
try
{
var deselected = e.DeselectedItems.ToList();
var selected = e.SelectedItems.ToList();
_updatingItems = true;
foreach (var i in deselected)
{
_selectedItems.Remove(i);
}
foreach (var i in selected)
{
_selectedItems.Add(i);
}
}
finally
{
_updatingItems = false;
}
}
private static int IndexOf(object? source, object? item)
{
if (source is IList l)
{
return l.IndexOf(item);
}
else if (source is ItemsSourceView v)
{
return v.IndexOf(item);
}
return -1;
}
}
}

98
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -19,7 +19,7 @@ using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives namespace Avalonia.Controls.UnitTests.Primitives
{ {
public class SelectingItemsControlTests public partial class SelectingItemsControlTests
{ {
private MouseTestHelper _helper = new MouseTestHelper(); private MouseTestHelper _helper = new MouseTestHelper();
@ -56,7 +56,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(), Template = Template(),
}; };
target.ApplyTemplate(); Prepare(target);
Assert.False(items[0].IsSelected); Assert.False(items[0].IsSelected);
Assert.False(items[1].IsSelected); Assert.False(items[1].IsSelected);
@ -77,8 +77,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(), Template = Template(),
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
target.SelectedItem = items[1]; target.SelectedItem = items[1];
Assert.False(items[0].IsSelected); Assert.False(items[0].IsSelected);
@ -101,8 +101,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}; };
target.SelectedItem = items[1]; target.SelectedItem = items[1];
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
Assert.False(items[0].IsSelected); Assert.False(items[0].IsSelected);
Assert.True(items[1].IsSelected); Assert.True(items[1].IsSelected);
@ -159,6 +158,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Template = Template(); target.Template = Template();
target.EndInit(); target.EndInit();
Prepare(target);
Assert.Equal(0, target.SelectedIndex); Assert.Equal(0, target.SelectedIndex);
} }
@ -181,6 +182,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
listBox.EndInit(); listBox.EndInit();
Prepare(listBox);
Assert.Equal("B", listBox.SelectedItem); Assert.Equal("B", listBox.SelectedItem);
} }
@ -223,7 +226,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
[Fact] [Fact]
public void Setting_SelectedItem_Before_Initialize_Should_Retain() public void Setting_SelectedItem_Before_Initialize_Should_Retain_Selection()
{ {
var listBox = new ListBox var listBox = new ListBox
{ {
@ -290,7 +293,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
[Fact] [Fact]
public void Setting_SelectedIndex_Before_Initialize_With_AlwaysSelected_Should_Retain() public void Setting_SelectedIndex_Before_Initialize_With_AlwaysSelected_Should_Retain_Selection()
{ {
var listBox = new ListBox var listBox = new ListBox
{ {
@ -324,8 +327,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}; };
target.SelectedIndex = 1; target.SelectedIndex = 1;
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
Assert.False(items[0].IsSelected); Assert.False(items[0].IsSelected);
Assert.True(items[1].IsSelected); Assert.True(items[1].IsSelected);
@ -480,8 +482,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(), Template = Template(),
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
items.Add(new Item { IsSelected = true }); items.Add(new Item { IsSelected = true });
Assert.Equal(2, target.SelectedIndex); Assert.Equal(2, target.SelectedIndex);
@ -530,8 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(), Template = Template(),
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
target.SelectedIndex = 1; target.SelectedIndex = 1;
Assert.Equal(items[1], target.SelectedItem); Assert.Equal(items[1], target.SelectedItem);
@ -568,8 +568,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Template = Template(); target.Template = Template();
target.EndInit(); target.EndInit();
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
target.SelectedIndex = 0; target.SelectedIndex = 0;
Assert.Equal(items[0], target.SelectedItem); Assert.Equal(items[0], target.SelectedItem);
@ -635,8 +634,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(), Template = Template(),
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
target.SelectedItem = items[1]; target.SelectedItem = items[1];
Assert.False(items[0].IsSelected); Assert.False(items[0].IsSelected);
@ -666,8 +664,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(), Template = Template(),
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
target.SelectedItem = items[1]; target.SelectedItem = items[1];
Assert.False(items[0].IsSelected); Assert.False(items[0].IsSelected);
@ -757,8 +754,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1, SelectedIndex = 1,
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
var called = false; var called = false;
@ -897,8 +893,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = new[] { "Foo", "Bar", "Baz " }, Items = new[] { "Foo", "Bar", "Baz " },
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
_helper.Down((Interactive)target.Presenter.Panel.Children[1]); _helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var panel = target.Presenter.Panel; var panel = target.Presenter.Panel;
@ -919,8 +914,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = items, Items = items,
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
_helper.Down(target.Presenter.Panel.Children[1]); _helper.Down(target.Presenter.Panel.Children[1]);
@ -1014,8 +1008,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
_helper.Down((Interactive)target.Presenter.Panel.Children[3]); _helper.Down((Interactive)target.Presenter.Panel.Children[3]);
Assert.Equal(3, target.SelectedIndex); Assert.Equal(3, target.SelectedIndex);
@ -1030,8 +1023,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
_helper.Down((Interactive)target.Presenter.Panel.Children[3]); _helper.Down((Interactive)target.Presenter.Panel.Children[3]);
Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes);
@ -1054,8 +1046,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1, SelectedIndex = 1,
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
items.Insert(0, "Qux"); items.Insert(0, "Qux");
@ -1080,8 +1071,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1, SelectedIndex = 1,
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
items.RemoveAt(0); items.RemoveAt(0);
@ -1109,8 +1099,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[!ListBox.SelectedIndexProperty] = other[!ListBox.SelectedIndexProperty], [!ListBox.SelectedIndexProperty] = other[!ListBox.SelectedIndexProperty],
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
other.ApplyTemplate(); other.ApplyTemplate();
other.Presenter.ApplyTemplate(); other.Presenter.ApplyTemplate();
@ -1140,8 +1129,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[!ListBox.SelectedItemProperty] = other[!ListBox.SelectedItemProperty], [!ListBox.SelectedItemProperty] = other[!ListBox.SelectedItemProperty],
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
other.ApplyTemplate(); other.ApplyTemplate();
other.Presenter.ApplyTemplate(); other.Presenter.ApplyTemplate();
@ -1168,8 +1156,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1, SelectedIndex = 1,
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
items[1] = "Qux"; items[1] = "Qux";
@ -1193,8 +1180,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = items, Items = items,
}; };
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
var raised = false; var raised = false;
target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true); target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true);
@ -1323,16 +1309,36 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Items = items; target.Items = items;
target.ApplyTemplate(); Prepare(target);
target.Presenter.ApplyTemplate();
Assert.Equal(second, target.SelectedItem); Assert.Equal(second, target.SelectedItem);
Assert.Equal(1, target.SelectedIndex); Assert.Equal(1, target.SelectedIndex);
} }
private FuncControlTemplate Template() private static void Prepare(SelectingItemsControl target)
{
var root = new TestRoot
{
Child = target,
Width = 100,
Height = 100,
Styles =
{
new Style(x => x.Is<SelectingItemsControl>())
{
Setters =
{
new Setter(ListBox.TemplateProperty, Template()),
},
},
},
};
root.LayoutManager.ExecuteInitialLayoutPass();
}
private static FuncControlTemplate Template()
{ {
return new FuncControlTemplate<SelectingItemsControl>((control, scope) => return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter new ItemsPresenter

4
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -1304,8 +1304,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
set { base.SelectionMode = value; } set { base.SelectionMode = value; }
} }
public new void SelectAll() => base.SelectAll(); public void SelectAll() => Selection.SelectAll();
public new void UnselectAll() => base.UnselectAll(); public void UnselectAll() => Selection.Clear();
public void SelectRange(int index) => UpdateSelection(index, true, true); public void SelectRange(int index) => UpdateSelection(index, true, true);
public void Toggle(int index) => UpdateSelection(index, true, false, true); public void Toggle(int index) => UpdateSelection(index, true, false, true);
} }

32
tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Multiple.cs

@ -120,6 +120,38 @@ namespace Avalonia.Controls.UnitTests.Selection
Assert.Equal(0, raised); Assert.Equal(0, raised);
} }
[Fact]
public void Initializing_Source_Respects_Range_SourceItem_Order()
{
var target = CreateTarget(false);
target.SelectRange(2, 2);
target.SelectedItem = "bar";
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(1, target.SelectedIndex);
Assert.Equal(new[] { 1 }, target.SelectedIndexes);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
}
[Fact]
public void Initializing_Source_Respects_SourceItem_Range_Order()
{
var target = CreateTarget(false);
target.SelectedItem = "baz";
target.SelectRange(1, 1);
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(1, target.SelectedIndex);
Assert.Equal(new[] { 1 }, target.SelectedIndexes);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
}
} }
public class SelectedIndex public class SelectedIndex

120
tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs

@ -4,6 +4,7 @@ using Avalonia.Collections;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Xunit; using Xunit;
using CollectionChangedEventManager = Avalonia.Controls.Utils.CollectionChangedEventManager;
#nullable enable #nullable enable
@ -14,7 +15,7 @@ namespace Avalonia.Controls.UnitTests.Selection
public class Source public class Source
{ {
[Fact] [Fact]
public void Can_Select_Item_Before_Source_Assigned() public void Can_Select_Index_Before_Source_Assigned()
{ {
var target = CreateTarget(false); var target = CreateTarget(false);
var raised = 0; var raised = 0;
@ -38,7 +39,23 @@ namespace Avalonia.Controls.UnitTests.Selection
} }
[Fact] [Fact]
public void Initializing_Source_Retains_Valid_Selection() public void Can_Select_Item_Before_Source_Assigned()
{
var target = CreateTarget(false);
var raised = 0;
target.SelectionChanged += (s, e) => ++raised;
target.SelectedItem = "bar";
Assert.Equal(-1, target.SelectedIndex);
Assert.Empty(target.SelectedIndexes);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new string?[] { "bar" }, target.SelectedItems);
Assert.Equal(0, raised);
}
[Fact]
public void Initializing_Source_Retains_Valid_Index_Selection()
{ {
var target = CreateTarget(false); var target = CreateTarget(false);
var raised = 0; var raised = 0;
@ -57,7 +74,7 @@ namespace Avalonia.Controls.UnitTests.Selection
} }
[Fact] [Fact]
public void Initializing_Source_Removes_Invalid_Selection() public void Initializing_Source_Removes_Invalid_Index_Selection()
{ {
var target = CreateTarget(false); var target = CreateTarget(false);
var raised = 0; var raised = 0;
@ -82,6 +99,81 @@ namespace Avalonia.Controls.UnitTests.Selection
Assert.Equal(1, raised); Assert.Equal(1, raised);
} }
[Fact]
public void Initializing_Source_Retains_Valid_Item_Selection()
{
var target = CreateTarget(false);
var raised = 0;
target.SelectedItem = "bar";
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndexes);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { 1 }, e.SelectedIndexes);
Assert.Equal(new string[] { "bar" }, e.SelectedItems);
++raised;
};
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(1, target.SelectedIndex);
Assert.Equal(new[] { 1 }, target.SelectedIndexes);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
Assert.Equal(1, raised);
}
[Fact]
public void Initializing_Source_Removes_Invalid_Item_Selection()
{
var target = CreateTarget(false);
var raised = 0;
target.SelectedItem = "qux";
target.SelectionChanged += (s, e) => ++raised;
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(-1, target.SelectedIndex);
Assert.Empty(target.SelectedIndexes);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
Assert.Equal(0, raised);
}
[Fact]
public void Initializing_Source_Respects_SourceIndex_SourceItem_Order()
{
var target = CreateTarget(false);
target.SelectedIndex = 0;
target.SelectedItem = "bar";
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(1, target.SelectedIndex);
Assert.Equal(new[] { 1 }, target.SelectedIndexes);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
}
[Fact]
public void Initializing_Source_Respects_SourceItem_SourceIndex_Order()
{
var target = CreateTarget(false);
target.SelectedItem = "foo";
target.SelectedIndex = 1;
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(1, target.SelectedIndex);
Assert.Equal(new[] { 1 }, target.SelectedIndexes);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
}
[Fact] [Fact]
public void Changing_Source_First_Clears_Old_Selection() public void Changing_Source_First_Clears_Old_Selection()
{ {
@ -240,7 +332,7 @@ namespace Avalonia.Controls.UnitTests.Selection
public MockBinding(SelectionModel<string?> target, AvaloniaList<string> data) public MockBinding(SelectionModel<string?> target, AvaloniaList<string> data)
{ {
_target = target; _target = target;
Avalonia.Controls.Utils.CollectionChangedEventManager.Instance.AddListener(data, this); CollectionChangedEventManager.Instance.AddListener(data, this);
} }
public void Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) public void Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
@ -260,6 +352,26 @@ namespace Avalonia.Controls.UnitTests.Selection
public class SelectedItem public class SelectedItem
{ {
[Fact]
public void Setting_SelectedItem_To_Valid_Item_Updates_Selection()
{
var target = CreateTarget();
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndexes);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { 1 }, e.SelectedIndexes);
Assert.Equal(new[] { "bar" }, e.SelectedItems);
++raised;
};
target.SelectedItem = "bar";
Assert.Equal(1, raised);
}
[Fact] [Fact]
public void PropertyChanged_Is_Raised_When_SelectedIndex_Changes() public void PropertyChanged_Is_Raised_When_SelectedIndex_Changes()
{ {

Loading…
Cancel
Save