diff --git a/src/Avalonia.Controls/IndexPath.cs b/src/Avalonia.Controls/IndexPath.cs index 6c5aaf7ad1..73b75bc23d 100644 --- a/src/Avalonia.Controls/IndexPath.cs +++ b/src/Avalonia.Controls/IndexPath.cs @@ -123,6 +123,26 @@ namespace Avalonia.Controls } } + public bool IsAncestorOf(in IndexPath other) + { + if (other.GetSize() <= GetSize()) + { + return false; + } + + var size = GetSize(); + + for (int i = 0; i < size; i++) + { + if (GetAt(i) != other.GetAt(i)) + { + return false; + } + } + + return true; + } + public override string ToString() { if (_path != null) diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs index 314c36d28d..ff1c0260bb 100644 --- a/src/Avalonia.Controls/SelectionModel.cs +++ b/src/Avalonia.Controls/SelectionModel.cs @@ -141,7 +141,7 @@ namespace Avalonia.Controls while (current?.AnchorIndex >= 0) { path.Add(current.AnchorIndex); - current = current.GetAt(current.AnchorIndex, false); + current = current.GetAt(current.AnchorIndex, false, default); } anchor = new IndexPath(path); @@ -420,7 +420,7 @@ namespace Avalonia.Controls for (int i = 0; i < path.GetSize() - 1; i++) { var childIndex = path.GetAt(i); - node = node.GetAt(childIndex, realizeChild: false); + node = node.GetAt(childIndex, false, default); if (node == null) { @@ -455,7 +455,7 @@ namespace Avalonia.Controls } var isSelected = (bool?)false; - var childNode = _rootNode.GetAt(groupIndex, realizeChild: false); + var childNode = _rootNode.GetAt(groupIndex, false, default); if (childNode != null) { @@ -474,7 +474,7 @@ namespace Avalonia.Controls for (int i = 0; i < path.GetSize() - 1; i++) { var childIndex = path.GetAt(i); - node = node.GetAt(childIndex, realizeChild: false); + node = node.GetAt(childIndex, false, default); if (node == null) { @@ -598,7 +598,10 @@ namespace Avalonia.Controls ApplyAutoSelect(true); } - internal IObservable? ResolvePath(object data, IndexPath dataIndexPath) + internal IObservable? ResolvePath( + object data, + IndexPath dataIndexPath, + IndexPath finalIndexPath) { IObservable? resolved = null; @@ -607,18 +610,22 @@ namespace Avalonia.Controls { if (_childrenRequestedEventArgs == null) { - _childrenRequestedEventArgs = new SelectionModelChildrenRequestedEventArgs(data, dataIndexPath, false); + _childrenRequestedEventArgs = new SelectionModelChildrenRequestedEventArgs( + data, + dataIndexPath, + finalIndexPath, + false); } else { - _childrenRequestedEventArgs.Initialize(data, dataIndexPath, false); + _childrenRequestedEventArgs.Initialize(data, dataIndexPath, finalIndexPath, false); } ChildrenRequested(this, _childrenRequestedEventArgs); resolved = _childrenRequestedEventArgs.Children; // Clear out the values in the args so that it cannot be used after the event handler call. - _childrenRequestedEventArgs.Initialize(null, default, true); + _childrenRequestedEventArgs.Initialize(null, default, default, true); } return resolved; @@ -683,7 +690,7 @@ namespace Avalonia.Controls ClearSelection(resetAnchor: true); } - var childNode = _rootNode.GetAt(groupIndex, realizeChild: true); + var childNode = _rootNode.GetAt(groupIndex, true, new IndexPath(groupIndex, itemIndex)); var selected = childNode!.Select(itemIndex, select); if (selected) @@ -764,7 +771,7 @@ namespace Avalonia.Controls for (int groupIdx = startGroupIndex; groupIdx <= endGroupIndex; groupIdx++) { - var groupNode = _rootNode.GetAt(groupIdx, realizeChild: true)!; + var groupNode = _rootNode.GetAt(groupIdx, true, new IndexPath(endGroupIndex, endItemIndex))!; int startIndex = groupIdx == startGroupIndex ? startItemIndex : 0; int endIndex = groupIdx == endGroupIndex ? endItemIndex : groupNode.DataCount - 1; groupNode.SelectRange(new IndexRange(startIndex, endIndex), select); diff --git a/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs b/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs index 974da0cf71..b1f3e0b2c4 100644 --- a/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs +++ b/src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs @@ -16,15 +16,17 @@ namespace Avalonia.Controls { private object? _source; private IndexPath _sourceIndexPath; + private IndexPath _finalIndexPath; private bool _throwOnAccess; internal SelectionModelChildrenRequestedEventArgs( object source, IndexPath sourceIndexPath, + IndexPath finalIndexPath, bool throwOnAccess) { source = source ?? throw new ArgumentNullException(nameof(source)); - Initialize(source, sourceIndexPath, throwOnAccess); + Initialize(source, sourceIndexPath, finalIndexPath, throwOnAccess); } /// @@ -65,9 +67,26 @@ namespace Avalonia.Controls } } + /// + /// Gets the index of the final object which is being attempted to be retrieved. + /// + public IndexPath FinalIndex + { + get + { + if (_throwOnAccess) + { + throw new ObjectDisposedException(nameof(SelectionModelChildrenRequestedEventArgs)); + } + + return _finalIndexPath; + } + } + internal void Initialize( object? source, IndexPath sourceIndexPath, + IndexPath finalIndexPath, bool throwOnAccess) { if (!throwOnAccess && source == null) @@ -77,6 +96,7 @@ namespace Avalonia.Controls _source = source; _sourceIndexPath = sourceIndexPath; + _finalIndexPath = finalIndexPath; _throwOnAccess = throwOnAccess; } } diff --git a/src/Avalonia.Controls/SelectionNode.cs b/src/Avalonia.Controls/SelectionNode.cs index 1a3bde1765..d99606673e 100644 --- a/src/Avalonia.Controls/SelectionNode.cs +++ b/src/Avalonia.Controls/SelectionNode.cs @@ -162,7 +162,7 @@ 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, IndexPath finalIndexPath) { SelectionNode? child = null; @@ -192,7 +192,7 @@ namespace Avalonia.Controls if (childData != null) { var childDataIndexPath = IndexPath.CloneWithChildIndex(index); - resolver = _manager.ResolvePath(childData, childDataIndexPath); + resolver = _manager.ResolvePath(childData, childDataIndexPath, finalIndexPath); } if (resolver != null) @@ -864,7 +864,7 @@ namespace Avalonia.Controls int notSelectedCount = 0; for (int i = 0; i < ChildrenNodeCount; i++) { - var child = GetAt(i, realizeChild: false); + var child = GetAt(i, false, default); if (child != null) { diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 95e7437838..3e6ade39fc 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -395,10 +395,17 @@ namespace Avalonia.Controls private void OnSelectionModelChildrenRequested(object sender, SelectionModelChildrenRequestedEventArgs e) { - var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as ItemsControl; + var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as TreeViewItem; if (container is object) { + if (e.SourceIndex.IsAncestorOf(e.FinalIndex)) + { + container.IsExpanded = true; + container.ApplyTemplate(); + container.Presenter?.ApplyTemplate(); + } + e.Children = Observable.CombineLatest( container.GetObservable(TreeViewItem.IsExpandedProperty), container.GetObservable(ItemsProperty), diff --git a/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs b/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs index 430ecabbb8..5adf5bdeea 100644 --- a/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs +++ b/src/Avalonia.Controls/Utils/SelectionTreeHelper.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Utils if (depth < path.GetSize() - 1) { - node = node.GetAt(childIndex, realizeChildren)!; + node = node.GetAt(childIndex, realizeChildren, path)!; } } } @@ -50,7 +50,7 @@ namespace Avalonia.Controls.Utils int count = realizeChildren ? nextNode.Node.DataCount : nextNode.Node.ChildrenNodeCount; for (int i = count - 1; i >= 0; i--) { - var child = nextNode.Node.GetAt(i, realizeChildren); + var child = nextNode.Node.GetAt(i, realizeChildren, nextNode.Path); var childPath = nextNode.Path.CloneWithChildIndex(i); if (child != null) { @@ -90,7 +90,7 @@ namespace Avalonia.Controls.Utils for (int i = endIndex; i >= startIndex; i--) { - var child = node.GetAt(i, realizeChild: true); + var child = node.GetAt(i, true, end); if (child != null) { var childPath = currentPath.CloneWithChildIndex(i); @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Utils int endIndex = depth < end.GetSize() && isEndPath ? end.GetAt(depth) : info.Node.DataCount - 1; for (int i = endIndex; i >= startIndex; i--) { - var child = info.Node.GetAt(i, realizeChild: true); + var child = info.Node.GetAt(i, true, end); if (child != null) { var childPath = info.Path.CloneWithChildIndex(i);