Browse Source

Refactor of SelectionModel change notifications.

To address issues found.
pull/3470/head
Steven Kirk 7 years ago
parent
commit
859aba1043
  1. 53
      src/Avalonia.Controls/SelectionModel.cs
  2. 140
      src/Avalonia.Controls/SelectionModelChangeSet.cs
  3. 80
      src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs
  4. 60
      src/Avalonia.Controls/SelectionNode.cs
  5. 80
      src/Avalonia.Controls/SelectionNodeOperation.cs

53
src/Avalonia.Controls/SelectionModel.cs

@ -490,13 +490,9 @@ namespace Avalonia.Controls
}
public void OnSelectionInvalidatedDueToCollectionChange(
IEnumerable<object>? removedItems)
IReadOnlyList<object>? removedItems)
{
var e = new SelectionModelSelectionChangedEventArgs(
Enumerable.Empty<IndexPath>(),
Enumerable.Empty<IndexPath>(),
removedItems ?? Enumerable.Empty<object>(),
Enumerable.Empty<object>());
var e = new SelectionModelSelectionChangedEventArgs(null, null, removedItems, null);
OnSelectionChanged(e);
}
@ -706,50 +702,19 @@ namespace Avalonia.Controls
});
}
private void BeginOperation()
{
if (SelectionChanged != null)
{
_rootNode.BeginOperation();
}
}
private void BeginOperation() => _rootNode.BeginOperation();
private void EndOperation()
{
static IEnumerable<T>? Concat<T>(IEnumerable<T>? a, IEnumerable<T> b)
{
return a == null ? b : a.Concat(b);
}
var changes = new List<SelectionNodeOperation>();
_rootNode.EndOperation(changes);
SelectionModelSelectionChangedEventArgs? e = null;
if (SelectionChanged != null)
if (changes.Count > 0)
{
IEnumerable<IndexPath>? selectedIndices = null;
IEnumerable<IndexPath>? deselectedIndices = null;
IEnumerable<object>? selectedItems = null;
IEnumerable<object>? deselectedItems = null;
foreach (var changes in _rootNode.EndOperation())
{
if (changes.HasChanges)
{
selectedIndices = Concat(selectedIndices, changes.SelectedIndices);
deselectedIndices = Concat(deselectedIndices, changes.DeselectedIndices);
selectedItems = Concat(selectedItems, changes.SelectedItems);
deselectedItems = Concat(deselectedItems, changes.DeselectedItems);
}
}
if (selectedIndices != null || deselectedIndices != null ||
selectedItems != null || deselectedItems != null)
{
e = new SelectionModelSelectionChangedEventArgs(
deselectedIndices ?? Enumerable.Empty<IndexPath>(),
selectedIndices ?? Enumerable.Empty<IndexPath>(),
deselectedItems ?? Enumerable.Empty<object>(),
selectedItems ?? Enumerable.Empty<object>());
}
var changeSet = new SelectionModelChangeSet(changes);
e = changeSet.CreateEventArgs();
}
OnSelectionChanged(e);

140
src/Avalonia.Controls/SelectionModelChangeSet.cs

@ -1,144 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace Avalonia.Controls
{
internal class SelectionModelChangeSet
{
private SelectionNode _owner;
private List<IndexRange>? _selected;
private List<IndexRange>? _deselected;
public SelectionModelChangeSet(SelectionNode owner) => _owner = owner;
public bool IsTracking { get; private set; }
public bool HasChanges => _selected?.Count > 0 || _deselected?.Count > 0;
public IEnumerable<IndexPath> SelectedIndices => EnumerateIndices(_selected);
public IEnumerable<IndexPath> DeselectedIndices => EnumerateIndices(_deselected);
public IEnumerable<object> SelectedItems => EnumerateItems(_selected);
public IEnumerable<object> DeselectedItems => EnumerateItems(_deselected);
private List<SelectionNodeOperation> _changes;
public void BeginOperation()
public SelectionModelChangeSet(List<SelectionNodeOperation> changes)
{
if (IsTracking)
{
throw new AvaloniaInternalException("SelectionModel change operation already in progress.");
}
IsTracking = true;
_selected?.Clear();
_deselected?.Clear();
_changes = changes;
}
public void EndOperation() => IsTracking = false;
public void Selected(IndexRange range)
public SelectionModelSelectionChangedEventArgs CreateEventArgs()
{
if (!IsTracking)
{
return;
}
Add(range, ref _selected, _deselected);
return new SelectionModelSelectionChangedEventArgs(
CreateIndices(x => x.DeselectedRanges),
CreateIndices(x => x.SelectedRanges),
CreateItems(x => x.DeselectedRanges),
CreateItems(x => x.SelectedRanges));
}
public void Selected(IEnumerable<IndexRange> ranges)
private IReadOnlyList<IndexPath> CreateIndices(Func<SelectionNodeOperation, List<IndexRange>?> selector)
{
if (!IsTracking)
if (_changes == null)
{
return;
return Array.Empty<IndexPath>();
}
foreach (var range in ranges)
{
Selected(range);
}
}
public void Deselected(IndexRange range)
{
if (!IsTracking)
{
return;
}
Add(range, ref _deselected, _selected);
}
var result = new List<IndexPath>();
public void Deselected(IEnumerable<IndexRange> ranges)
{
if (!IsTracking)
foreach (var i in _changes)
{
return;
}
var ranges = selector(i);
foreach (var range in ranges)
{
Deselected(range);
}
}
private static void Add(
IndexRange range,
ref List<IndexRange>? add,
List<IndexRange>? remove)
{
if (remove != null)
{
var removed = new List<IndexRange>();
IndexRange.Remove(remove, range, removed);
var selected = IndexRange.Subtract(range, removed);
if (selected.Any())
if (ranges != null)
{
add ??= new List<IndexRange>();
foreach (var r in selected)
foreach (var j in ranges)
{
IndexRange.Add(add, r);
for (var k = j.Begin; k <= j.End; ++k)
{
result.Add(i.Path.CloneWithChildIndex(k));
}
}
}
}
else
{
add ??= new List<IndexRange>();
IndexRange.Add(add, range);
}
return result;
}
private IEnumerable<IndexPath> EnumerateIndices(IEnumerable<IndexRange>? ranges)
private IReadOnlyList<object> CreateItems(Func<SelectionNodeOperation, List<IndexRange>?> selector)
{
var path = _owner.IndexPath;
if (ranges != null)
if (_changes == null)
{
foreach (var range in ranges)
{
for (var i = range.Begin; i <= range.End; ++i)
{
yield return path.CloneWithChildIndex(i);
}
}
return Array.Empty<object>();
}
}
private IEnumerable<object> EnumerateItems(IEnumerable<IndexRange>? ranges)
{
var items = _owner.ItemsSourceView;
var result = new List<object>();
if (ranges != null && items != null)
foreach (var i in _changes)
{
foreach (var range in ranges)
var ranges = selector(i);
if (ranges != null && i.Items != null)
{
for (var i = range.Begin; i <= range.End; ++i)
foreach (var j in ranges)
{
yield return items.GetAt(i);
for (var k = j.Begin; k <= j.End; ++k)
{
result.Add(i.Items.GetAt(k));
}
}
}
}
return result;
}
}
}

80
src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs

@ -12,49 +12,73 @@ namespace Avalonia.Controls
{
public class SelectionModelSelectionChangedEventArgs : EventArgs
{
private readonly IEnumerable<IndexPath> _selectedIndicesSource;
private readonly IEnumerable<IndexPath> _deselectedIndicesSource;
private readonly IEnumerable<object> _selectedItemsSource;
private readonly IEnumerable<object> _deselectedItemsSource;
private List<IndexPath>? _selectedIndices;
private List<IndexPath>? _deselectedIndices;
private List<object>? _selectedItems;
private List<object>? _deselectedItems;
private readonly IEnumerable<IndexPath>? _deselectedIndicesSource;
private readonly IEnumerable<IndexPath>? _selectedIndicesSource;
private readonly IEnumerable<object>? _deselectedItemsSource;
private readonly IEnumerable<object>? _selectedItemsSource;
private IReadOnlyList<IndexPath>? _deselectedIndices;
private IReadOnlyList<IndexPath>? _selectedIndices;
private IReadOnlyList<object>? _deselectedItems;
private IReadOnlyList<object>? _selectedItems;
public SelectionModelSelectionChangedEventArgs(
IEnumerable<IndexPath> deselectedIndices,
IEnumerable<IndexPath> selectedIndices,
IEnumerable<object> deselectedItems,
IEnumerable<object> selectedItems)
IReadOnlyList<IndexPath>? deselectedIndices,
IReadOnlyList<IndexPath>? selectedIndices,
IReadOnlyList<object>? deselectedItems,
IReadOnlyList<object>? selectedItems)
{
_selectedIndicesSource = selectedIndices;
_deselectedIndicesSource = deselectedIndices;
_selectedItemsSource = selectedItems;
_deselectedItemsSource = deselectedItems;
_deselectedIndices = deselectedIndices ?? Array.Empty<IndexPath>();
_selectedIndices = selectedIndices ?? Array.Empty<IndexPath>();
_deselectedItems = deselectedItems ?? Array.Empty<object>();
_selectedItems= selectedItems ?? Array.Empty<object>();
}
/// <summary>
/// Gets the indices of the items that were added to the selection.
/// </summary>
public IReadOnlyList<IndexPath> SelectedIndices =>
_selectedIndices ?? (_selectedIndices = new List<IndexPath>(_selectedIndicesSource));
public SelectionModelSelectionChangedEventArgs(
IEnumerable<IndexPath>? deselectedIndices,
IEnumerable<IndexPath>? selectedIndices,
IEnumerable<object>? deselectedItems,
IEnumerable<object>? selectedItems)
{
static void Set<T>(IEnumerable<T>? source, ref IEnumerable<T>? sourceField, ref IReadOnlyList<T>? field)
{
if (source != null)
{
sourceField = source;
}
else
{
field = Array.Empty<T>();
}
}
Set(deselectedIndices, ref _deselectedIndicesSource, ref _deselectedIndices);
Set(selectedIndices, ref _selectedIndicesSource, ref _selectedIndices);
Set(deselectedItems, ref _deselectedItemsSource, ref _deselectedItems);
Set(selectedItems, ref _selectedItemsSource, ref _selectedItems);
}
/// <summary>
/// Gets the indices of the items that were removed from the selection.
/// </summary>
public IReadOnlyList<IndexPath> DeselectedIndices =>
_deselectedIndices ?? (_deselectedIndices = new List<IndexPath>(_deselectedIndicesSource));
public IReadOnlyList<IndexPath> DeselectedIndices
=> _deselectedIndices ??= new List<IndexPath>(_deselectedIndicesSource);
/// <summary>
/// Gets the items that were added to the selection.
/// Gets the indices of the items that were added to the selection.
/// </summary>
public IReadOnlyList<object> SelectedItems =>
_selectedItems ?? (_selectedItems = new List<object>(_selectedItemsSource));
public IReadOnlyList<IndexPath> SelectedIndices
=> _selectedIndices ??= new List<IndexPath>(_selectedIndicesSource);
/// <summary>
/// Gets the items that were removed from the selection.
/// </summary>
public IReadOnlyList<object> DeselectedItems =>
_deselectedItems ?? (_deselectedItems = new List<object>(_deselectedItemsSource));
public IReadOnlyList<object> DeselectedItems
=> _deselectedItems ??= new List<object>(_deselectedItemsSource);
/// <summary>
/// Gets the items that were added to the selection.
/// </summary>
public IReadOnlyList<object> SelectedItems
=> _selectedItems ??= new List<object>(_selectedItemsSource);
}
}

60
src/Avalonia.Controls/SelectionNode.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls
private readonly SelectionNode? _parent;
private readonly List<IndexRange> _selected = new List<IndexRange>();
private readonly List<int> _selectedIndicesCached = new List<int>();
private SelectionModelChangeSet? _changes;
private SelectionNodeOperation? _operation;
private object? _source;
private bool _selectedIndicesCacheIsValid;
@ -137,7 +137,7 @@ namespace Avalonia.Controls
child = new SelectionNode(_manager, parent: this);
child.Source = resolvedChild;
if (_changes?.IsTracking == true)
if (_operation != null)
{
child.BeginOperation();
}
@ -296,33 +296,45 @@ namespace Avalonia.Controls
public void BeginOperation()
{
_changes ??= new SelectionModelChangeSet(this);
_changes.BeginOperation();
if (_operation != null)
{
throw new AvaloniaInternalException("Selection operation already in progress.");
}
_operation = new SelectionNodeOperation(this);
for (var i = 0; i < _childrenNodes.Count; ++i)
{
_childrenNodes[i]?.BeginOperation();
var child = _childrenNodes[i];
if (child != null && child != _manager.SharedLeafNode)
{
child.BeginOperation();
}
}
}
public IEnumerable<SelectionModelChangeSet> EndOperation()
public void EndOperation(List<SelectionNodeOperation> changes)
{
if (_changes != null)
if (_operation == null)
{
_changes.EndOperation();
yield return _changes;
throw new AvaloniaInternalException("No selection operation in progress.");
}
for (var i = 0; i < _childrenNodes.Count; ++i)
{
var child = _childrenNodes[i];
if (_operation.HasChanges)
{
changes.Add(_operation);
}
if (child != null)
{
foreach (var changes in child.EndOperation())
{
yield return changes;
}
}
_operation = null;
for (var i = 0; i < _childrenNodes.Count; ++i)
{
var child = _childrenNodes[i];
if (child != null && child != _manager.SharedLeafNode)
{
child.EndOperation(changes);
}
}
}
@ -400,7 +412,7 @@ namespace Avalonia.Controls
if (selected.Count > 0)
{
_changes?.Selected(selected);
_operation?.Selected(selected);
if (raiseOnSelectionChanged)
{
@ -417,7 +429,7 @@ namespace Avalonia.Controls
if (removed.Count > 0)
{
_changes?.Deselected(removed);
_operation?.Deselected(removed);
if (raiseOnSelectionChanged)
{
@ -431,7 +443,7 @@ namespace Avalonia.Controls
// Deselect all items
if (_selected.Count > 0)
{
_changes?.Deselected(_selected);
_operation?.Deselected(_selected);
_selected.Clear();
OnSelectionChanged();
}
@ -480,7 +492,7 @@ namespace Avalonia.Controls
private void OnSourceListChanged(object dataSource, NotifyCollectionChangedEventArgs args)
{
bool selectionInvalidated = false;
IList<object>? removed = null;
List<object>? removed = null;
switch (args.Action)
{
@ -594,7 +606,7 @@ namespace Avalonia.Controls
return selectionInvalidated;
}
private (bool, IList<object>) OnItemsRemoved(int index, IList items)
private (bool, List<object>) OnItemsRemoved(int index, IList items)
{
var selectionInvalidated = false;
var removed = new List<object>();

80
src/Avalonia.Controls/SelectionNodeOperation.cs

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace Avalonia.Controls
{
internal class SelectionNodeOperation
{
private readonly SelectionNode _owner;
private List<IndexRange>? _selected;
private List<IndexRange>? _deselected;
public SelectionNodeOperation(SelectionNode owner)
{
_owner = owner;
}
public bool HasChanges => _selected?.Count > 0 || _deselected?.Count > 0;
public List<IndexRange>? SelectedRanges => _selected;
public List<IndexRange>? DeselectedRanges => _deselected;
public IndexPath Path => _owner.IndexPath;
public ItemsSourceView? Items => _owner.ItemsSourceView;
public void Selected(IndexRange range)
{
Add(range, ref _selected, _deselected);
}
public void Selected(IEnumerable<IndexRange> ranges)
{
foreach (var range in ranges)
{
Selected(range);
}
}
public void Deselected(IndexRange range)
{
Add(range, ref _deselected, _selected);
}
public void Deselected(IEnumerable<IndexRange> ranges)
{
foreach (var range in ranges)
{
Deselected(range);
}
}
private static void Add(
IndexRange range,
ref List<IndexRange>? add,
List<IndexRange>? remove)
{
if (remove != null)
{
var removed = new List<IndexRange>();
IndexRange.Remove(remove, range, removed);
var selected = IndexRange.Subtract(range, removed);
if (selected.Any())
{
add ??= new List<IndexRange>();
foreach (var r in selected)
{
IndexRange.Add(add, r);
}
}
}
else
{
add ??= new List<IndexRange>();
IndexRange.Add(add, range);
}
}
}
}
Loading…
Cancel
Save