diff --git a/src/Avalonia.Controls/IndexRange.cs b/src/Avalonia.Controls/IndexRange.cs index f3820c087a..b1a112ab39 100644 --- a/src/Avalonia.Controls/IndexRange.cs +++ b/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 { diff --git a/src/Avalonia.Controls/SelectedItems.cs b/src/Avalonia.Controls/SelectedItems.cs index 036788e7b2..af43742670 100644 --- a/src/Avalonia.Controls/SelectedItems.cs +++ b/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 : IReadOnlyList { private readonly List _infos; private readonly Func, int, T> _getAtImpl; - private int _totalCount; public SelectedItems( List 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 GetEnumerator() { - for (var i = 0; i < _totalCount; ++i) + for (var i = 0; i < Count; ++i) { yield return this[i]; } diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs index ea3a09d4e7..34d5f78434 100644 --- a/src/Avalonia.Controls/SelectionModel.cs +++ b/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 _selectedIndicesCached; - private IReadOnlyList _selectedItemsCached; - private SelectionModelChildrenRequestedEventArgs _childrenRequestedEventArgs; - private SelectionModelSelectionChangedEventArgs _selectionChangedEventArgs; + private IReadOnlyList? _selectedIndicesCached; + private IReadOnlyList? _selectedItemsCached; + private SelectionModelChildrenRequestedEventArgs? _childrenRequestedEventArgs; + private SelectionModelSelectionChangedEventArgs? _selectionChangedEventArgs; - public event EventHandler ChildrenRequested; - public event PropertyChangedEventHandler PropertyChanged; - public event EventHandler SelectionChanged; + public event EventHandler? ChildrenRequested; + public event PropertyChangedEventHandler? PropertyChanged; + public event EventHandler? 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(); - 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 SelectedItems + public IReadOnlyList 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 ( + var selectedItems = new SelectedItems ( 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); } }); diff --git a/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs b/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs index c5571b9f74..aa5a9b5cad 100644 --- a/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs +++ b/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; } } diff --git a/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs b/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs index 8c9e0343de..c8edc1f8ae 100644 --- a/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs +++ b/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 { diff --git a/src/Avalonia.Controls/SelectionNode.cs b/src/Avalonia.Controls/SelectionNode.cs index f5d93681c4..363eb35b94 100644 --- a/src/Avalonia.Controls/SelectionNode.cs +++ b/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 { /// @@ -22,17 +24,14 @@ namespace Avalonia.Controls internal class SelectionNode : IDisposable { private readonly SelectionModel _manager; - private readonly List _childrenNodes = new List(); - private readonly SelectionNode _parent; + private readonly List _childrenNodes = new List(); + private readonly SelectionNode? _parent; private readonly List _selected = new List(); - private object _source; - private ItemsSourceView _dataSource; - private int _selectedCount; - private List _selectedIndicesCached = new List(); + private readonly List _selectedIndicesCached = new List(); + 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(); @@ -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++) diff --git a/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs b/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs index 38b1dde5d7..93102a7b5b 100644 --- a/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs +++ b/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; } }; }