diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml
index b57a9a0a9e..26a62ebca6 100644
--- a/samples/BindingDemo/MainWindow.xaml
+++ b/samples/BindingDemo/MainWindow.xaml
@@ -74,11 +74,11 @@
-
+
-
+
diff --git a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs
index 22d01e0765..a66038ff3e 100644
--- a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs
+++ b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs
@@ -6,6 +6,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
using ReactiveUI;
+using Avalonia.Controls;
namespace BindingDemo.ViewModels
{
@@ -27,7 +28,7 @@ namespace BindingDemo.ViewModels
Detail = "Item " + x + " details",
}));
- SelectedItems = new ObservableCollection();
+ Selection = new SelectionModel();
ShuffleItems = ReactiveCommand.Create(() =>
{
@@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels
}
public ObservableCollection Items { get; }
- public ObservableCollection SelectedItems { get; }
+ public SelectionModel Selection { get; }
public ReactiveCommand ShuffleItems { get; }
public string BooleanString
diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml
index b1b3112e60..47b4ce7151 100644
--- a/samples/ControlCatalog/Pages/ListBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml
@@ -10,7 +10,7 @@
HorizontalAlignment="Center"
Spacing="16">
-
+
diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml
index 3a81e2ed02..c9e3fafb6d 100644
--- a/samples/ControlCatalog/Pages/TreeViewPage.xaml
+++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml
@@ -10,7 +10,7 @@
HorizontalAlignment="Center"
Spacing="16">
-
+
diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs b/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
index 1f35f05f1d..5893796b8b 100644
--- a/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
@@ -28,21 +28,22 @@ namespace ControlCatalog.Pages
{
Node root = new Node();
Items = root.Children;
- SelectedItems = new ObservableCollection();
+ Selection = new SelectionModel();
AddItemCommand = ReactiveCommand.Create(() =>
{
- Node parentItem = SelectedItems.Count > 0 ? SelectedItems[0] : root;
+ Node parentItem = Selection.SelectedItems.Count > 0 ?
+ (Node)Selection.SelectedItems[0] : root;
parentItem.AddNewItem();
});
RemoveItemCommand = ReactiveCommand.Create(() =>
{
- while (SelectedItems.Count > 0)
+ while (Selection.SelectedItems.Count > 0)
{
- Node lastItem = SelectedItems[0];
+ Node lastItem = (Node)Selection.SelectedItems[0];
RecursiveRemove(Items, lastItem);
- SelectedItems.Remove(lastItem);
+ Selection.DeselectAt(Selection.SelectedIndices[0]);
}
bool RecursiveRemove(ObservableCollection items, Node selectedItem)
@@ -67,7 +68,7 @@ namespace ControlCatalog.Pages
public ObservableCollection Items { get; }
- public ObservableCollection SelectedItems { get; }
+ public SelectionModel Selection { get; }
public ReactiveCommand AddItemCommand { get; }
@@ -78,7 +79,7 @@ namespace ControlCatalog.Pages
get => _selectionMode;
set
{
- SelectedItems.Clear();
+ Selection.ClearSelection();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
@@ -109,7 +110,7 @@ namespace ControlCatalog.Pages
public override string ToString() => Header;
- private Node CreateNewNode() => new Node {Header = $"Item {_counter++}"};
+ private Node CreateNewNode() => new Node { Header = $"Item {_counter++}" };
}
}
}
diff --git a/samples/VirtualizationDemo/MainWindow.xaml b/samples/VirtualizationDemo/MainWindow.xaml
index 12137cd03d..4bd657bf93 100644
--- a/samples/VirtualizationDemo/MainWindow.xaml
+++ b/samples/VirtualizationDemo/MainWindow.xaml
@@ -45,7 +45,7 @@
SelectedItems { get; }
- = new AvaloniaList();
+ public SelectionModel Selection { get; } = new SelectionModel();
public AvaloniaList Items
{
@@ -138,9 +137,9 @@ namespace VirtualizationDemo.ViewModels
{
var index = Items.Count;
- if (SelectedItems.Count > 0)
+ if (Selection.SelectedIndices.Count > 0)
{
- index = Items.IndexOf(SelectedItems[0]);
+ index = Selection.SelectedIndex.GetAt(0);
}
Items.Insert(index, new ItemViewModel(_newItemIndex++, NewItemString));
@@ -148,9 +147,9 @@ namespace VirtualizationDemo.ViewModels
private void Remove()
{
- if (SelectedItems.Count > 0)
+ if (Selection.SelectedItems.Count > 0)
{
- Items.RemoveAll(SelectedItems);
+ Items.RemoveAll(Selection.SelectedItems.Cast().ToList());
}
}
@@ -164,8 +163,7 @@ namespace VirtualizationDemo.ViewModels
private void SelectItem(int index)
{
- SelectedItems.Clear();
- SelectedItems.Add(Items[index]);
+ Selection.SelectedIndex = new IndexPath(index);
}
}
}
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index 67ef6cd1e9..50f2067df6 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -306,9 +306,9 @@ namespace Avalonia.Controls
{
var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
- if (container == null && SelectedItems.Count > 0)
+ if (container == null && SelectedIndex != -1)
{
- ScrollIntoView(SelectedItems[0]);
+ ScrollIntoView(Selection.SelectedIndex);
container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
}
diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs
index 1660d6b1ad..f0da370f73 100644
--- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs
+++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs
@@ -94,9 +94,13 @@ namespace Avalonia.Controls.Generators
/// The container, or null of not found.
public IControl ContainerFromItem(object item)
{
- IControl result;
- _itemToContainer.TryGetValue(item, out result);
- return result;
+ if (item != null)
+ {
+ _itemToContainer.TryGetValue(item, out var result);
+ return result;
+ }
+
+ return null;
}
///
@@ -106,9 +110,13 @@ namespace Avalonia.Controls.Generators
/// The item, or null of not found.
public object ItemFromContainer(IControl container)
{
- object result;
- _containerToItem.TryGetValue(container, out result);
- return result;
+ if (container != null)
+ {
+ _containerToItem.TryGetValue(container, out var result);
+ return result;
+ }
+
+ return null;
}
}
}
diff --git a/src/Avalonia.Controls/ISelectionModel.cs b/src/Avalonia.Controls/ISelectionModel.cs
new file mode 100644
index 0000000000..6570921c03
--- /dev/null
+++ b/src/Avalonia.Controls/ISelectionModel.cs
@@ -0,0 +1,249 @@
+// This source file is adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+//
+// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Holds the selected items for a control.
+ ///
+ public interface ISelectionModel : INotifyPropertyChanged
+ {
+ ///
+ /// Gets or sets the anchor index.
+ ///
+ IndexPath AnchorIndex { get; set; }
+
+ ///
+ /// Gets or set the index of the first selected item.
+ ///
+ IndexPath SelectedIndex { get; set; }
+
+ ///
+ /// Gets or set the indexes of the selected items.
+ ///
+ IReadOnlyList SelectedIndices { get; }
+
+ ///
+ /// Gets the first selected item.
+ ///
+ object SelectedItem { get; }
+
+ ///
+ /// Gets the selected items.
+ ///
+ IReadOnlyList
/// The item index.
/// The container that was brought into view.
- private IControl ScrollIntoView(int index)
+ private IControl ScrollIntoViewCore(int index)
{
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
index 26be85beb3..80c9e972d5 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
@@ -128,9 +128,9 @@ namespace Avalonia.Controls.Presenters
_scrollInvalidated?.Invoke(this, e);
}
- public override void ScrollIntoView(object item)
+ public override void ScrollIntoView(int index)
{
- Virtualizer?.ScrollIntoView(item);
+ Virtualizer?.ScrollIntoView(index);
}
///
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
index 3a0e6b67c9..23846bcd2e 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
@@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
}
///
- public virtual void ScrollIntoView(object item)
+ public virtual void ScrollIntoView(int index)
{
}
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index e39cbdc016..8fc1a55e68 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -2,15 +2,15 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
-using Avalonia.Collections;
using Avalonia.Controls.Generators;
+using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
-using Avalonia.Logging;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@@ -23,9 +23,9 @@ namespace Avalonia.Controls.Primitives
/// provides a base class for s
/// that maintain a selection (single or multiple). By default only its
/// and properties are visible; the
- /// current multiple selection together with the
- /// properties are protected, however a derived class can expose
- /// these if it wishes to support multiple selection.
+ /// current multiple and together with the
+ /// and properties are protected, however a derived class can
+ /// expose these if it wishes to support multiple selection.
///
///
/// maintains a selection respecting the current
@@ -74,6 +74,15 @@ namespace Avalonia.Controls.Primitives
o => o.SelectedItems,
(o, v) => o.SelectedItems = v);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty SelectionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Selection),
+ o => o.Selection,
+ (o, v) => o.Selection = v);
+
///
/// Defines the property.
///
@@ -100,17 +109,22 @@ namespace Avalonia.Controls.Primitives
RoutingStrategies.Bubble);
private static readonly IList Empty = Array.Empty