Browse Source

Add nullability annotations to SelectionModel etc.

And ran a few of VS' lightbulb suggestions.
pull/3469/head
Steven Kirk 7 years ago
parent
commit
d615cdebcb
  1. 4
      src/Avalonia.Controls/IndexRange.cs
  2. 9
      src/Avalonia.Controls/SelectedItems.cs
  3. 56
      src/Avalonia.Controls/SelectionModel.cs
  4. 44
      src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs
  5. 4
      src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs
  6. 87
      src/Avalonia.Controls/SelectionNode.cs
  7. 10
      src/Avalonia.Controls/Utils/SelectionTreeHelper.cs

4
src/Avalonia.Controls/IndexRange.cs

@ -3,9 +3,7 @@
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace Avalonia.Controls
{

9
src/Avalonia.Controls/SelectedItems.cs

@ -8,13 +8,14 @@ using System.Collections;
using System.Collections.Generic;
using SelectedItemInfo = Avalonia.Controls.SelectionModel.SelectedItemInfo;
#nullable enable
namespace Avalonia.Controls
{
internal class SelectedItems<T> : IReadOnlyList<T>
{
private readonly List<SelectedItemInfo> _infos;
private readonly Func<List<SelectedItemInfo>, int, T> _getAtImpl;
private int _totalCount;
public SelectedItems(
List<SelectedItemInfo> infos,
@ -29,7 +30,7 @@ namespace Avalonia.Controls
if (node != null)
{
_totalCount += node.SelectedCount;
Count += node.SelectedCount;
}
else
{
@ -40,11 +41,11 @@ namespace Avalonia.Controls
public T this[int index] => _getAtImpl(_infos, index);
public int Count => _totalCount;
public int Count { get; }
public IEnumerator<T> GetEnumerator()
{
for (var i = 0; i < _totalCount; ++i)
for (var i = 0; i < Count; ++i)
{
yield return this[i];
}

56
src/Avalonia.Controls/SelectionModel.cs

@ -8,20 +8,22 @@ using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Controls.Utils;
#nullable enable
namespace Avalonia.Controls
{
public class SelectionModel : INotifyPropertyChanged, IDisposable
{
private SelectionNode _rootNode;
private readonly SelectionNode _rootNode;
private bool _singleSelect;
private IReadOnlyList<IndexPath> _selectedIndicesCached;
private IReadOnlyList<object> _selectedItemsCached;
private SelectionModelChildrenRequestedEventArgs _childrenRequestedEventArgs;
private SelectionModelSelectionChangedEventArgs _selectionChangedEventArgs;
private IReadOnlyList<IndexPath>? _selectedIndicesCached;
private IReadOnlyList<object?>? _selectedItemsCached;
private SelectionModelChildrenRequestedEventArgs? _childrenRequestedEventArgs;
private SelectionModelSelectionChangedEventArgs? _selectionChangedEventArgs;
public event EventHandler<SelectionModelChildrenRequestedEventArgs> ChildrenRequested;
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<SelectionModelSelectionChangedEventArgs> SelectionChanged;
public event EventHandler<SelectionModelChildrenRequestedEventArgs>? ChildrenRequested;
public event PropertyChangedEventHandler? PropertyChanged;
public event EventHandler<SelectionModelSelectionChangedEventArgs>? SelectionChanged;
public SelectionModel()
{
@ -29,9 +31,9 @@ namespace Avalonia.Controls
SharedLeafNode = new SelectionNode(this, null);
}
public object Source
public object? Source
{
get => _rootNode.Source;
get => _rootNode?.Source;
set
{
ClearSelection(resetAnchor: true, raiseSelectionChanged: false);
@ -74,10 +76,10 @@ namespace Avalonia.Controls
{
IndexPath anchor = default;
if (_rootNode.AnchorIndex >= 0)
if (_rootNode?.AnchorIndex >= 0)
{
var path = new List<int>();
var current = _rootNode;
SelectionNode? current = _rootNode;
while (current?.AnchorIndex >= 0)
{
@ -136,11 +138,11 @@ namespace Avalonia.Controls
}
}
public object SelectedItem
public object? SelectedItem
{
get
{
object item = null;
object? item = null;
var selectedItems = SelectedItems;
if (selectedItems?.Count > 0)
@ -152,7 +154,7 @@ namespace Avalonia.Controls
}
}
public IReadOnlyList<object> SelectedItems
public IReadOnlyList<object?> SelectedItems
{
get
{
@ -179,12 +181,12 @@ namespace Avalonia.Controls
// the selected item at a particular index. This avoid having to create the storage and copying
// needed in a dumb vector. This also allows us to expose a tree of selected nodes into an
// easier to consume flat vector view of objects.
var selectedItems = new SelectedItems<object> (
var selectedItems = new SelectedItems<object?> (
selectedInfos,
(infos, index) =>
{
var currentIndex = 0;
object item = null;
object? item = null;
foreach (var info in infos)
{
@ -197,7 +199,7 @@ namespace Avalonia.Controls
if (index >= currentIndex && index < currentIndex + currentCount)
{
var targetIndex = node.SelectedIndices[index - currentIndex];
item = node.ItemsSourceView.GetAt(targetIndex);
item = node.ItemsSourceView!.GetAt(targetIndex);
break;
}
@ -289,8 +291,6 @@ namespace Avalonia.Controls
{
ClearSelection(resetAnchor: false, raiseSelectionChanged: false);
_rootNode?.Dispose();
_rootNode = null;
SharedLeafNode = null;
_selectedIndicesCached = null;
_selectedItemsCached = null;
}
@ -349,7 +349,7 @@ namespace Avalonia.Controls
{
var path = index;
var isRealized = true;
var node = _rootNode;
SelectionNode? node = _rootNode;
for (int i = 0; i < path.GetSize() - 1; i++)
{
@ -370,11 +370,11 @@ namespace Avalonia.Controls
var size = path.GetSize();
if (size == 0)
{
isSelected = SelectionNode.ConvertToNullableBool(node.EvaluateIsSelectedBasedOnChildrenNodes());
isSelected = SelectionNode.ConvertToNullableBool(node!.EvaluateIsSelectedBasedOnChildrenNodes());
}
else
{
isSelected = node.IsSelectedWithPartial(path.GetAt(size - 1));
isSelected = node!.IsSelectedWithPartial(path.GetAt(size - 1));
}
}
@ -457,9 +457,9 @@ namespace Avalonia.Controls
OnSelectionChanged();
}
internal object ResolvePath(object data, SelectionNode sourceNode)
internal object? ResolvePath(object data, SelectionNode sourceNode)
{
object resolved = null;
object? resolved = null;
// Raise ChildrenRequested event if there is a handler
if (ChildrenRequested != null)
@ -565,7 +565,7 @@ namespace Avalonia.Controls
}
var childNode = _rootNode.GetAt(groupIndex, realizeChild: true);
var selected = childNode.Select(itemIndex, select);
var selected = childNode!.Select(itemIndex, select);
if (selected)
{
@ -653,7 +653,7 @@ namespace Avalonia.Controls
var selected = false;
for (int groupIdx = startGroupIndex; groupIdx <= endGroupIndex; groupIdx++)
{
var groupNode = _rootNode.GetAt(groupIdx, realizeChild: true);
var groupNode = _rootNode.GetAt(groupIdx, realizeChild: true)!;
int startIndex = groupIdx == startGroupIndex ? startItemIndex : 0;
int endIndex = groupIdx == endGroupIndex ? endItemIndex : groupNode.DataCount - 1;
selected |= groupNode.SelectRange(new IndexRange(startIndex, endIndex), select);
@ -688,7 +688,7 @@ namespace Avalonia.Controls
if (info.Node.DataCount == 0)
{
// Select only leaf nodes
info.ParentNode.Select(info.Path.GetAt(info.Path.GetSize() - 1), select);
info.ParentNode!.Select(info.Path.GetAt(info.Path.GetSize() - 1), select);
}
});

44
src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs

@ -4,27 +4,53 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace Avalonia.Controls
{
public class SelectionModelChildrenRequestedEventArgs : EventArgs
{
private SelectionNode _sourceNode;
private object? _source;
private SelectionNode? _sourceNode;
internal SelectionModelChildrenRequestedEventArgs(object source, SelectionNode sourceNode)
{
Initialize(source, sourceNode);
_source = source;
_sourceNode = sourceNode;
}
public object Children { get; set; }
public object Source { get; private set; }
public IndexPath SourceIndex => _sourceNode.IndexPath;
public object? Children { get; set; }
public object Source
{
get
{
if (_source == null)
{
throw new ObjectDisposedException(nameof(SelectionModelChildrenRequestedEventArgs));
}
return _source;
}
}
public IndexPath SourceIndex
{
get
{
if (_sourceNode == null)
{
throw new ObjectDisposedException(nameof(SelectionModelChildrenRequestedEventArgs));
}
return _sourceNode.IndexPath;
}
}
internal void Initialize(object source, SelectionNode sourceNode)
internal void Initialize(object? source, SelectionNode? sourceNode)
{
Source = source;
_source = source;
_sourceNode = sourceNode;
}
}

4
src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs

@ -4,8 +4,8 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace Avalonia.Controls
{

87
src/Avalonia.Controls/SelectionNode.cs

@ -8,6 +8,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
@ -22,17 +24,14 @@ namespace Avalonia.Controls
internal class SelectionNode : IDisposable
{
private readonly SelectionModel _manager;
private readonly List<SelectionNode> _childrenNodes = new List<SelectionNode>();
private readonly SelectionNode _parent;
private readonly List<SelectionNode?> _childrenNodes = new List<SelectionNode?>();
private readonly SelectionNode? _parent;
private readonly List<IndexRange> _selected = new List<IndexRange>();
private object _source;
private ItemsSourceView _dataSource;
private int _selectedCount;
private List<int> _selectedIndicesCached = new List<int>();
private readonly List<int> _selectedIndicesCached = new List<int>();
private object? _source;
private bool _selectedIndicesCacheIsValid;
private int _realizedChildrenNodeCount;
public SelectionNode(SelectionModel manager, SelectionNode parent)
public SelectionNode(SelectionModel manager, SelectionNode? parent)
{
_manager = manager;
_parent = parent;
@ -40,7 +39,7 @@ namespace Avalonia.Controls
public int AnchorIndex { get; set; } = -1;
public object Source
public object? Source
{
get => _source;
set
@ -60,7 +59,7 @@ namespace Avalonia.Controls
newDataSource = new ItemsSourceView((IEnumerable)value);
}
_dataSource = newDataSource;
ItemsSourceView = newDataSource;
HookupCollectionChangedHandler();
OnSelectionChanged();
@ -68,10 +67,10 @@ namespace Avalonia.Controls
}
}
public ItemsSourceView ItemsSourceView => _dataSource;
public int DataCount => _dataSource?.Count ?? 0;
public ItemsSourceView? ItemsSourceView { get; private set; }
public int DataCount => ItemsSourceView?.Count ?? 0;
public int ChildrenNodeCount => _childrenNodes.Count;
public int RealizedChildrenNodeCount => _realizedChildrenNodeCount;
public int RealizedChildrenNodeCount { get; private set; }
public IndexPath IndexPath
{
@ -101,17 +100,22 @@ namespace Avalonia.Controls
// create a bunch of leaf node instances - instead i use the same instance m_leafNode to avoid
// an explosion of node objects. However, I'm still creating the m_childrenNodes
// collection unfortunately.
public SelectionNode GetAt(int index, bool realizeChild)
public SelectionNode? GetAt(int index, bool realizeChild)
{
SelectionNode child = null;
SelectionNode? child = null;
if (realizeChild)
{
if (ItemsSourceView == null || index < 0 || index >= ItemsSourceView.Count)
{
throw new IndexOutOfRangeException();
}
if (_childrenNodes.Count == 0)
{
if (_dataSource != null)
if (ItemsSourceView != null)
{
for (int i = 0; i < _dataSource.Count; i++)
for (int i = 0; i < ItemsSourceView.Count; i++)
{
_childrenNodes.Add(null);
}
@ -120,7 +124,7 @@ namespace Avalonia.Controls
if (_childrenNodes[index] == null)
{
var childData = _dataSource.GetAt(index);
var childData = ItemsSourceView!.GetAt(index);
if (childData != null)
{
@ -142,7 +146,7 @@ namespace Avalonia.Controls
}
_childrenNodes[index] = child;
_realizedChildrenNodeCount++;
RealizedChildrenNodeCount++;
}
else
{
@ -160,7 +164,7 @@ namespace Avalonia.Controls
return child;
}
public int SelectedCount => _selectedCount;
public int SelectedCount { get; private set; }
public bool IsSelected(int index)
{
@ -205,7 +209,7 @@ namespace Avalonia.Controls
// Null -> Some descendents are selected and some are not
public bool? IsSelectedWithPartial(int index)
{
var selectionState = SelectionState.NotSelected;
SelectionState selectionState;
if (_childrenNodes.Count == 0 || // no nodes realized
_childrenNodes.Count <= index || // target node is not realized
@ -221,7 +225,7 @@ namespace Avalonia.Controls
// targetNode is a non-leaf node, containing one or many children nodes. Evaluate
// based on children of targetNode.
var targetNode = _childrenNodes[index];
selectionState = targetNode.EvaluateIsSelectedBasedOnChildrenNodes();
selectionState = targetNode!.EvaluateIsSelectedBasedOnChildrenNodes();
}
return ConvertToNullableBool(selectionState);
@ -274,7 +278,7 @@ namespace Avalonia.Controls
public void Dispose()
{
_dataSource?.Dispose();
ItemsSourceView?.Dispose();
UnhookCollectionChangedHandler();
}
@ -290,9 +294,9 @@ namespace Avalonia.Controls
public void SelectAll()
{
if (_dataSource != null)
if (ItemsSourceView != null)
{
var size = _dataSource.Count;
var size = ItemsSourceView.Count;
if (size > 0)
{
@ -324,17 +328,17 @@ namespace Avalonia.Controls
private void HookupCollectionChangedHandler()
{
if (_dataSource != null)
if (ItemsSourceView != null)
{
_dataSource.CollectionChanged += OnSourceListChanged;
ItemsSourceView.CollectionChanged += OnSourceListChanged;
}
}
private void UnhookCollectionChangedHandler()
{
if (_dataSource != null)
if (ItemsSourceView != null)
{
_dataSource.CollectionChanged -= OnSourceListChanged;
ItemsSourceView.CollectionChanged -= OnSourceListChanged;
}
}
@ -353,11 +357,11 @@ namespace Avalonia.Controls
{
if (!IsSelected(i))
{
_selectedCount++;
SelectedCount++;
}
}
if (oldCount != _selectedCount)
if (oldCount != SelectedCount)
{
_selected.Add(addRange);
@ -370,18 +374,18 @@ namespace Avalonia.Controls
private void RemoveRange(IndexRange removeRange, bool raiseOnSelectionChanged)
{
int oldCount = _selectedCount;
int oldCount = SelectedCount;
// TODO: Prevent overlap of Ranges in _selected (Task 14107720)
for (int i = removeRange.Begin; i <= removeRange.End; i++)
{
if (IsSelected(i))
{
_selectedCount--;
SelectedCount--;
}
}
if (oldCount != _selectedCount)
if (oldCount != SelectedCount)
{
// Build up a both a list of Ranges to remove and ranges to add
var toRemove = new List<IndexRange>();
@ -448,7 +452,7 @@ namespace Avalonia.Controls
OnSelectionChanged();
}
_selectedCount = 0;
SelectedCount = 0;
AnchorIndex = -1;
// This will throw away all the children SelectionNodes
@ -576,7 +580,7 @@ namespace Avalonia.Controls
// Adjust the anchor
if (AnchorIndex >= index)
{
AnchorIndex = AnchorIndex + count;
AnchorIndex += count;
}
// Check if adding a node invalidated an ancestors
@ -610,7 +614,7 @@ namespace Avalonia.Controls
bool selectionInvalidated = false;
// Remove the items from the selection for leaf
if (ItemsSourceView.Count > 0)
if (ItemsSourceView!.Count > 0)
{
bool isSelected = false;
@ -650,7 +654,7 @@ namespace Avalonia.Controls
{
if (_childrenNodes[index] != null)
{
_realizedChildrenNodeCount--;
RealizedChildrenNodeCount--;
}
_childrenNodes.RemoveAt(index);
}
@ -659,14 +663,14 @@ namespace Avalonia.Controls
//Adjust the anchor
if (AnchorIndex >= index)
{
AnchorIndex = AnchorIndex - count;
AnchorIndex -= count;
}
}
else
{
// No more items in the list, clear
ClearSelection();
_realizedChildrenNodeCount = 0;
RealizedChildrenNodeCount = 0;
selectionInvalidated = true;
}
@ -719,7 +723,7 @@ namespace Avalonia.Controls
public SelectionState EvaluateIsSelectedBasedOnChildrenNodes()
{
SelectionState selectionState = SelectionState.NotSelected;
var selectionState = SelectionState.NotSelected;
int realizedChildrenNodeCount = RealizedChildrenNodeCount;
int selectedCount = SelectedCount;
@ -739,7 +743,6 @@ namespace Avalonia.Controls
{
// There are child nodes, walk them individually and evaluate based on each child
// being selected/not selected or partially selected.
bool isSelected = false;
selectedCount = 0;
int notSelectedCount = 0;
for (int i = 0; i < ChildrenNodeCount; i++)

10
src/Avalonia.Controls/Utils/SelectionTreeHelper.cs

@ -7,6 +7,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace Avalonia.Controls.Utils
{
internal static class SelectionTreeHelper
@ -26,7 +28,7 @@ namespace Avalonia.Controls.Utils
if (depth < path.GetSize() - 1)
{
node = node.GetAt(childIndex, realizeChildren);
node = node.GetAt(childIndex, realizeChildren)!;
}
}
}
@ -48,7 +50,7 @@ namespace Avalonia.Controls.Utils
int count = realizeChildren ? nextNode.Node.DataCount : nextNode.Node.ChildrenNodeCount;
for (int i = count - 1; i >= 0; i--)
{
SelectionNode child = nextNode.Node.GetAt(i, realizeChildren);
var child = nextNode.Node.GetAt(i, realizeChildren);
var childPath = nextNode.Path.CloneWithChildIndex(i);
if (child != null)
{
@ -154,7 +156,7 @@ namespace Avalonia.Controls.Utils
public struct TreeWalkNodeInfo
{
public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath, SelectionNode parent)
public TreeWalkNodeInfo(SelectionNode node, IndexPath indexPath, SelectionNode? parent)
{
node = node ?? throw new ArgumentNullException(nameof(node));
@ -174,7 +176,7 @@ namespace Avalonia.Controls.Utils
public SelectionNode Node { get; }
public IndexPath Path { get; }
public SelectionNode ParentNode { get; }
public SelectionNode? ParentNode { get; }
};
}

Loading…
Cancel
Save