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>
/// Selects all items in the <see cref="ListBox"/>.
/// </summary>
public void SelectAll() => base.SelectAll();
public void SelectAll() => Selection.SelectAll();
/// <summary>
/// Deselects all items in the <see cref="ListBox"/>.
/// </summary>
public void UnselectAll() => base.UnselectAll();
public void UnselectAll() => Selection.Clear();
/// <inheritdoc/>
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; }
int SelectedIndex { get; set; }
IReadOnlyList<int> SelectedIndexes { get; }
object? SelectedItem { get; }
object? SelectedItem { get; set; }
IReadOnlyList<object?> SelectedItems { get; }
int AnchorIndex { get; set; }
int Count { get; }
@ -30,19 +30,37 @@ namespace Avalonia.Controls.Selection
void Deselect(int index);
void SelectRange(int start, int end);
void DeselectRange(int start, int end);
void SelectAll();
void Clear();
}
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>.Untyped? _selectedItemsUntyped;
private EventHandler<SelectionModelSelectionChangedEventArgs>? _untypedSelectionChanged;
[AllowNull] private T _initSelectedItem = default;
private bool _hasInitSelectedItem;
public SelectionModel()
{
@ -51,7 +53,18 @@ namespace Avalonia.Controls.Selection
using var update = BatchUpdate();
update.Operation.IsSourceUpdate = true;
TrimInvalidSelections(update.Operation);
if (_hasInitSelectedItem)
{
SelectedItem = _initSelectedItem;
_initSelectedItem = default;
_hasInitSelectedItem = false;
}
else
{
TrimInvalidSelections(update.Operation);
}
RaisePropertyChanged(nameof(Source));
}
}
@ -90,10 +103,37 @@ namespace Avalonia.Controls.Selection
public IReadOnlyList<int> SelectedIndexes => _selectedIndexes ??= new SelectedIndexes<T>(this);
[MaybeNull]
public T SelectedItem => GetItemAt(_selectedIndex);
[MaybeNull, AllowNull]
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
{
@ -127,7 +167,22 @@ namespace Avalonia.Controls.Selection
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
{
@ -226,8 +281,12 @@ namespace Avalonia.Controls.Selection
{
o.SelectedIndex = -1;
}
_initSelectedItem = default;
_hasInitSelectedItem = false;
}
public void SelectAll() => SelectRange(0, int.MaxValue);
public void Clear() => DeselectRange(0, int.MaxValue);
protected void RaisePropertyChanged(string propertyName)
@ -429,6 +488,9 @@ namespace Avalonia.Controls.Selection
{
o.SelectedIndex = o.AnchorIndex = start;
}
_initSelectedItem = default;
_hasInitSelectedItem = false;
}
[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
{
public class SelectingItemsControlTests
public partial class SelectingItemsControlTests
{
private MouseTestHelper _helper = new MouseTestHelper();
@ -56,7 +56,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
};
target.ApplyTemplate();
Prepare(target);
Assert.False(items[0].IsSelected);
Assert.False(items[1].IsSelected);
@ -77,8 +77,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
target.SelectedItem = items[1];
Assert.False(items[0].IsSelected);
@ -101,8 +101,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
target.SelectedItem = items[1];
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
Assert.False(items[0].IsSelected);
Assert.True(items[1].IsSelected);
@ -159,6 +158,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Template = Template();
target.EndInit();
Prepare(target);
Assert.Equal(0, target.SelectedIndex);
}
@ -181,6 +182,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
listBox.EndInit();
Prepare(listBox);
Assert.Equal("B", listBox.SelectedItem);
}
@ -223,7 +226,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
[Fact]
public void Setting_SelectedItem_Before_Initialize_Should_Retain()
public void Setting_SelectedItem_Before_Initialize_Should_Retain_Selection()
{
var listBox = new ListBox
{
@ -290,7 +293,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
[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
{
@ -324,8 +327,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
target.SelectedIndex = 1;
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
Assert.False(items[0].IsSelected);
Assert.True(items[1].IsSelected);
@ -480,8 +482,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
items.Add(new Item { IsSelected = true });
Assert.Equal(2, target.SelectedIndex);
@ -530,8 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
target.SelectedIndex = 1;
Assert.Equal(items[1], target.SelectedItem);
@ -568,8 +568,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Template = Template();
target.EndInit();
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
target.SelectedIndex = 0;
Assert.Equal(items[0], target.SelectedItem);
@ -635,8 +634,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
target.SelectedItem = items[1];
Assert.False(items[0].IsSelected);
@ -666,8 +664,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
target.SelectedItem = items[1];
Assert.False(items[0].IsSelected);
@ -757,8 +754,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
var called = false;
@ -897,8 +893,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = new[] { "Foo", "Bar", "Baz " },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var panel = target.Presenter.Panel;
@ -919,8 +914,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = items,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
_helper.Down(target.Presenter.Panel.Children[1]);
@ -1014,8 +1008,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[3]);
Assert.Equal(3, target.SelectedIndex);
@ -1030,8 +1023,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[3]);
Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes);
@ -1054,8 +1046,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
items.Insert(0, "Qux");
@ -1080,8 +1071,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
items.RemoveAt(0);
@ -1109,8 +1099,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[!ListBox.SelectedIndexProperty] = other[!ListBox.SelectedIndexProperty],
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
other.ApplyTemplate();
other.Presenter.ApplyTemplate();
@ -1140,8 +1129,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[!ListBox.SelectedItemProperty] = other[!ListBox.SelectedItemProperty],
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
other.ApplyTemplate();
other.Presenter.ApplyTemplate();
@ -1168,8 +1156,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
SelectedIndex = 1,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
items[1] = "Qux";
@ -1193,8 +1180,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Items = items,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
var raised = false;
target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true);
@ -1323,16 +1309,36 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Items = items;
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Prepare(target);
Assert.Equal(second, target.SelectedItem);
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) =>
new ItemsPresenter

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

@ -1304,8 +1304,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
set { base.SelectionMode = value; }
}
public new void SelectAll() => base.SelectAll();
public new void UnselectAll() => base.UnselectAll();
public void SelectAll() => Selection.SelectAll();
public void UnselectAll() => Selection.Clear();
public void SelectRange(int index) => UpdateSelection(index, true, 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);
}
[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

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

@ -4,6 +4,7 @@ using Avalonia.Collections;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Utils;
using Xunit;
using CollectionChangedEventManager = Avalonia.Controls.Utils.CollectionChangedEventManager;
#nullable enable
@ -14,7 +15,7 @@ namespace Avalonia.Controls.UnitTests.Selection
public class Source
{
[Fact]
public void Can_Select_Item_Before_Source_Assigned()
public void Can_Select_Index_Before_Source_Assigned()
{
var target = CreateTarget(false);
var raised = 0;
@ -38,7 +39,23 @@ namespace Avalonia.Controls.UnitTests.Selection
}
[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 raised = 0;
@ -57,7 +74,7 @@ namespace Avalonia.Controls.UnitTests.Selection
}
[Fact]
public void Initializing_Source_Removes_Invalid_Selection()
public void Initializing_Source_Removes_Invalid_Index_Selection()
{
var target = CreateTarget(false);
var raised = 0;
@ -82,6 +99,81 @@ namespace Avalonia.Controls.UnitTests.Selection
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]
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)
{
_target = target;
Avalonia.Controls.Utils.CollectionChangedEventManager.Instance.AddListener(data, this);
CollectionChangedEventManager.Instance.AddListener(data, this);
}
public void Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
@ -260,6 +352,26 @@ namespace Avalonia.Controls.UnitTests.Selection
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]
public void PropertyChanged_Is_Raised_When_SelectedIndex_Changes()
{

Loading…
Cancel
Save