Browse Source

Added SelectionMode.AutoSelect.

pull/3509/head
Steven Kirk 6 years ago
parent
commit
0a608d47dc
  1. 87
      src/Avalonia.Controls/SelectionModel.cs
  2. 3
      src/Avalonia.Controls/SelectionNode.cs
  3. 203
      tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs

87
src/Avalonia.Controls/SelectionModel.cs

@ -17,6 +17,8 @@ namespace Avalonia.Controls
{
private readonly SelectionNode _rootNode;
private bool _singleSelect;
private bool _autoSelect;
private int _operationCount;
private IReadOnlyList<IndexPath>? _selectedIndicesCached;
private IReadOnlyList<object?>? _selectedItemsCached;
private SelectionModelChildrenRequestedEventArgs? _childrenRequestedEventArgs;
@ -38,21 +40,25 @@ namespace Avalonia.Controls
{
if (_rootNode.Source != value)
{
var wasNull = _rootNode.Source == null;
var raiseChanged = _rootNode.Source == null && SelectedIndices.Count > 0;
if (_rootNode.Source != null)
{
using (var operation = new Operation(this))
if (_rootNode.Source != null)
{
ClearSelection(resetAnchor: true);
using (var operation = new Operation(this))
{
ClearSelection(resetAnchor: true);
}
}
}
_rootNode.Source = value;
ApplyAutoSelect();
RaisePropertyChanged("Source");
if (wasNull)
if (raiseChanged)
{
var e = new SelectionModelSelectionChangedEventArgs(
null,
@ -92,12 +98,25 @@ namespace Avalonia.Controls
}
}
public bool RetainSelectionOnReset
public bool RetainSelectionOnReset
{
get => _rootNode.RetainSelectionOnReset;
set => _rootNode.RetainSelectionOnReset = value;
}
public bool AutoSelect
{
get => _autoSelect;
set
{
if (_autoSelect != value)
{
_autoSelect = value;
ApplyAutoSelect();
}
}
}
public IndexPath AnchorIndex
{
get
@ -356,18 +375,21 @@ namespace Avalonia.Controls
{
using var operation = new Operation(this);
SelectImpl(index, select: false);
ApplyAutoSelect();
}
public void Deselect(int groupIndex, int itemIndex)
{
using var operation = new Operation(this);
SelectWithGroupImpl(groupIndex, itemIndex, select: false);
ApplyAutoSelect();
}
public void DeselectAt(IndexPath index)
{
using var operation = new Operation(this);
SelectWithPathImpl(index, select: false);
ApplyAutoSelect();
}
public bool? IsSelected(int index)
@ -508,6 +530,7 @@ namespace Avalonia.Controls
{
using var operation = new Operation(this);
ClearSelection(resetAnchor: true);
ApplyAutoSelect();
}
protected void OnPropertyChanged(string propertyName)
@ -521,10 +544,18 @@ namespace Avalonia.Controls
}
public void OnSelectionInvalidatedDueToCollectionChange(
IReadOnlyList<object?>? removedItems)
bool selectionInvalidated,
IReadOnlyList<object>? removedItems)
{
var e = new SelectionModelSelectionChangedEventArgs(null, null, removedItems, null);
SelectionModelSelectionChangedEventArgs? e = null;
if (selectionInvalidated)
{
e = new SelectionModelSelectionChangedEventArgs(null, null, removedItems, null);
}
OnSelectionChanged(e);
ApplyAutoSelect();
}
internal object? ResolvePath(object data, SelectionNode sourceNode)
@ -733,24 +764,52 @@ namespace Avalonia.Controls
});
}
private void BeginOperation() => _rootNode.BeginOperation();
private void BeginOperation()
{
if (_operationCount++ == 0)
{
_rootNode.BeginOperation();
}
}
private void EndOperation()
{
var changes = new List<SelectionNodeOperation>();
_rootNode.EndOperation(changes);
if (_operationCount == 0)
{
throw new AvaloniaInternalException("No selection operation in progress.");
}
SelectionModelSelectionChangedEventArgs? e = null;
if (changes.Count > 0)
if (--_operationCount == 0)
{
var changeSet = new SelectionModelChangeSet(changes);
e = changeSet.CreateEventArgs();
var changes = new List<SelectionNodeOperation>();
_rootNode.EndOperation(changes);
if (changes.Count > 0)
{
var changeSet = new SelectionModelChangeSet(changes);
e = changeSet.CreateEventArgs();
}
}
OnSelectionChanged(e);
}
private void ApplyAutoSelect()
{
if (AutoSelect)
{
_selectedIndicesCached = null;
if (SelectedIndex == default && _rootNode.ItemsSourceView?.Count > 0)
{
using var operation = new Operation(this);
SelectImpl(0, true);
}
}
}
internal class SelectedItemInfo : ISelectedItemInfo
{
public SelectedItemInfo(SelectionNode node, IndexPath path)

3
src/Avalonia.Controls/SelectionNode.cs

@ -587,8 +587,9 @@ namespace Avalonia.Controls
if (selectionInvalidated)
{
OnSelectionChanged();
_manager.OnSelectionInvalidatedDueToCollectionChange(removed);
}
_manager.OnSelectionInvalidatedDueToCollectionChange(selectionInvalidated, removed);
}
private bool OnItemsAdded(int index, int count)

203
tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs

@ -1569,8 +1569,6 @@ namespace Avalonia.Controls.UnitTests
};
data.Reset();
Assert.Equal(1, raised);
}
[Fact]
@ -1642,6 +1640,207 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { new IndexPath(2) }, target.SelectedIndices);
}
[Fact]
public void AutoSelect_Selects_When_Enabled()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.AutoSelect = true;
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_When_Source_Assigned()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.Source = data;
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_When_New_Source_Assigned_And_Old_Source_Has_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Equal(new[] { new IndexPath(0) }, e.DeselectedIndices);
Assert.Equal(new[] { "foo" }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
else
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "newfoo" }, e.SelectedItems);
}
++raised;
};
target.Source = new[] { "newfoo" };
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(2, raised);
}
[Fact]
public void AutoSelect_Selects_When_First_Item_Added()
{
var data = new ObservableCollection<string>();
var target = new SelectionModel { AutoSelect = true , Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
data.Add("foo");
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_When_Selected_Item_Removed()
{
var data = new ObservableCollection<string> { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(2);
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new[] { "baz" }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
else
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
}
++raised;
};
data.RemoveAt(2);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(2, raised);
}
[Fact]
public void AutoSelect_Selects_On_Deselection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(2);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(2) }, e.DeselectedIndices);
Assert.Equal(new[] { "baz" }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.Deselect(2);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_On_ClearSelection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(2);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(2) }, e.DeselectedIndices);
Assert.Equal(new[] { "baz" }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.ClearSelection();
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Overrides_Deselecting_First_Item()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.Select(0);
target.SelectionChanged += (s, e) =>
{
++raised;
};
target.Deselect(0);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(0, raised);
}
private int GetSubscriberCount(AvaloniaList<object> list)
{
return ((INotifyCollectionChangedDebug)list).GetCollectionChangedSubscribers()?.Length ?? 0;

Loading…
Cancel
Save