diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml
index 6019d5f91f..789b45e62c 100644
--- a/samples/ControlCatalog/Pages/TreeViewPage.xaml
+++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml
@@ -20,6 +20,7 @@
+
Single
diff --git a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
index d396ef2b3d..5bc23e2fe5 100644
--- a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
@@ -23,12 +23,14 @@ namespace ControlCatalog.ViewModels
AddItemCommand = ReactiveCommand.Create(AddItem);
RemoveItemCommand = ReactiveCommand.Create(RemoveItem);
+ SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem);
}
public ObservableCollection Items { get; }
public SelectionModel Selection { get; }
public ReactiveCommand AddItemCommand { get; }
public ReactiveCommand RemoveItemCommand { get; }
+ public ReactiveCommand SelectRandomItemCommand { get; }
public SelectionMode SelectionMode
{
@@ -74,6 +76,15 @@ namespace ControlCatalog.ViewModels
}
}
+ private void SelectRandomItem()
+ {
+ var random = new Random();
+ var depth = random.Next(4);
+ var indexes = Enumerable.Range(0, 4).Select(x => random.Next(10));
+ var path = new IndexPath(indexes);
+ Selection.SelectedIndex = path;
+ }
+
private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
var selected = string.Join(",", e.SelectedIndices);
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/IndexRange.cs b/src/Avalonia.Controls/IndexRange.cs
index 1dc161c699..e45d013af4 100644
--- a/src/Avalonia.Controls/IndexRange.cs
+++ b/src/Avalonia.Controls/IndexRange.cs
@@ -132,6 +132,53 @@ namespace Avalonia.Controls
return result;
}
+ public static int Intersect(
+ IList ranges,
+ IndexRange range,
+ IList? removed = null)
+ {
+ var result = 0;
+
+ for (var i = 0; i < ranges.Count && range != s_invalid; ++i)
+ {
+ var existing = ranges[i];
+
+ if (existing.End < range.Begin || existing.Begin > range.End)
+ {
+ removed?.Add(existing);
+ ranges.RemoveAt(i--);
+ result += existing.Count;
+ }
+ else
+ {
+ if (existing.Begin < range.Begin)
+ {
+ var except = new IndexRange(existing.Begin, range.Begin - 1);
+ removed?.Add(except);
+ ranges[i] = existing = new IndexRange(range.Begin, existing.End);
+ result += except.Count;
+ }
+
+ if (existing.End > range.End)
+ {
+ var except = new IndexRange(range.End + 1, existing.End);
+ removed?.Add(except);
+ ranges[i] = new IndexRange(existing.Begin, range.End);
+ result += except.Count;
+ }
+ }
+ }
+
+ MergeRanges(ranges);
+
+ if (removed is object)
+ {
+ MergeRanges(removed);
+ }
+
+ return result;
+ }
+
public static int Remove(
IList ranges,
IndexRange range,
diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs
index 93699583e6..ff1c0260bb 100644
--- a/src/Avalonia.Controls/SelectionModel.cs
+++ b/src/Avalonia.Controls/SelectionModel.cs
@@ -46,17 +46,25 @@ namespace Avalonia.Controls
if (_rootNode.Source != null)
{
- if (_rootNode.Source != null)
+ // Temporarily prevent auto-select when switching source.
+ var restoreAutoSelect = _autoSelect;
+ _autoSelect = false;
+
+ try
{
using (var operation = new Operation(this))
{
ClearSelection(resetAnchor: true);
}
}
+ finally
+ {
+ _autoSelect = restoreAutoSelect;
+ }
}
_rootNode.Source = value;
- ApplyAutoSelect();
+ ApplyAutoSelect(true);
RaisePropertyChanged("Source");
@@ -114,7 +122,7 @@ namespace Avalonia.Controls
if (_autoSelect != value)
{
_autoSelect = value;
- ApplyAutoSelect();
+ ApplyAutoSelect(true);
}
}
}
@@ -133,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);
@@ -188,7 +196,6 @@ namespace Avalonia.Controls
using var operation = new Operation(this);
ClearSelection(resetAnchor: true);
SelectWithPathImpl(value, select: true);
- ApplyAutoSelect();
}
}
}
@@ -384,21 +391,18 @@ 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) => _rootNode.IsSelected(index);
@@ -416,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)
{
@@ -451,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)
{
@@ -470,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)
{
@@ -565,7 +569,6 @@ namespace Avalonia.Controls
{
using var operation = new Operation(this);
ClearSelection(resetAnchor: true);
- ApplyAutoSelect();
}
public IDisposable Update() => new Operation(this);
@@ -592,10 +595,13 @@ namespace Avalonia.Controls
}
OnSelectionChanged(e);
- ApplyAutoSelect();
+ ApplyAutoSelect(true);
}
- internal IObservable