Browse Source

Use SelectionModel in TreeView.

fixes/tree-selectionmodel
Steven Kirk 7 years ago
parent
commit
c4afd15d90
  1. 2
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  2. 17
      samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
  3. 663
      src/Avalonia.Controls/TreeView.cs
  4. 17
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

2
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView Items="{Binding Items}" Selection="{Binding Selection}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>

17
samples/ControlCatalog/Pages/TreeViewPage.xaml.cs

@ -28,21 +28,22 @@ namespace ControlCatalog.Pages
{
Node root = new Node();
Items = root.Children;
SelectedItems = new ObservableCollection<Node>();
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<Node> items, Node selectedItem)
@ -67,7 +68,7 @@ namespace ControlCatalog.Pages
public ObservableCollection<Node> Items { get; }
public ObservableCollection<Node> SelectedItems { get; }
public SelectionModel Selection { get; }
public ReactiveCommand<Unit, Unit> 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++}" };
}
}
}

663
src/Avalonia.Controls/TreeView.cs

@ -5,11 +5,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
@ -45,15 +46,29 @@ namespace Avalonia.Controls
o => o.SelectedItems,
(o, v) => o.SelectedItems = v);
/// <summary>
/// Defines the <see cref="Selection"/> property.
/// </summary>
public static readonly DirectProperty<TreeView, ISelectionModel> SelectionProperty =
SelectingItemsControl.SelectionProperty.AddOwner<TreeView>(
o => o.Selection,
(o, v) => o.Selection = v);
/// <summary>
/// Defines the <see cref="SelectionMode"/> property.
/// </summary>
public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
ListBox.SelectionModeProperty.AddOwner<TreeView>();
private static readonly IList Empty = Array.Empty<object>();
/// <summary>
/// Defines the <see cref="SelectionChanged"/> property.
/// </summary>
public static RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
SelectingItemsControl.SelectionChangedEvent;
private object _selectedItem;
private IList _selectedItems;
private ISelectionModel _selection;
private readonly SelectedItemsSync _selectedItems;
/// <summary>
/// Initializes static members of the <see cref="TreeView"/> class.
@ -63,6 +78,13 @@ namespace Avalonia.Controls
// HACK: Needed or SelectedItem property will not be found in Release build.
}
public TreeView()
{
// Setting Selection to null causes a default SelectionModel to be created.
Selection = null;
_selectedItems = new SelectedItemsSync(Selection);
}
/// <summary>
/// Occurs when the control's selection changes.
/// </summary>
@ -87,8 +109,6 @@ namespace Avalonia.Controls
set => SetValue(AutoScrollToSelectedItemProperty, value);
}
private bool _syncingSelectedItems;
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
@ -98,61 +118,102 @@ namespace Avalonia.Controls
set => SetValue(SelectionModeProperty, value);
}
/// <summary>
/// Gets or sets the selected item.
/// </summary>
/// <summary>
/// Gets or sets the selected item.
/// </summary>
public object SelectedItem
{
get => _selectedItem;
set
{
var selectedItems = SelectedItems;
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
get => Selection.SelectedItem;
set => Selection.SelectedIndex = IndexFromItem(value);
}
if (value != null)
{
if (selectedItems.Count != 1 || selectedItems[0] != value)
{
_syncingSelectedItems = true;
SelectSingleItem(value);
_syncingSelectedItems = false;
}
}
else if (SelectedItems.Count > 0)
{
SelectedItems.Clear();
}
}
/// <summary>
/// Gets or sets the selected items.
/// </summary>
protected IList SelectedItems
{
get => _selectedItems.GetOrCreateItems();
set => _selectedItems.SetItems(value);
}
/// <summary>
/// Gets the selected items.
/// Gets or sets a model holding the current selection.
/// </summary>
public IList SelectedItems
public ISelectionModel Selection
{
get
get => _selection;
set
{
if (_selectedItems == null)
value ??= new SelectionModel
{
_selectedItems = new AvaloniaList<object>();
SubscribeToSelectedItems();
}
return _selectedItems;
}
SingleSelect = !SelectionMode.HasFlagCustom(SelectionMode.Multiple),
AutoSelect = SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected),
RetainSelectionOnReset = true,
};
set
{
if (value?.IsFixedSize == true || value?.IsReadOnly == true)
if (_selection != value)
{
throw new NotSupportedException(
"Cannot use a fixed size or read-only collection as SelectedItems.");
}
if (value == null)
{
throw new ArgumentNullException(nameof(value), "Cannot set Selection to null.");
}
else if (value.Source != null && value.Source != Items)
{
throw new ArgumentException("Selection has invalid Source.");
}
List<object> oldSelection = null;
if (_selection != null)
{
oldSelection = Selection.SelectedItems.ToList();
_selection.PropertyChanged -= OnSelectionModelPropertyChanged;
_selection.SelectionChanged -= OnSelectionModelSelectionChanged;
_selection.ChildrenRequested -= OnSelectionModelChildrenRequested;
MarkContainersUnselected();
}
_selection = value;
if (_selection != null)
{
_selection.Source = Items;
_selection.PropertyChanged += OnSelectionModelPropertyChanged;
_selection.SelectionChanged += OnSelectionModelSelectionChanged;
_selection.ChildrenRequested += OnSelectionModelChildrenRequested;
UnsubscribeFromSelectedItems();
_selectedItems = value ?? new AvaloniaList<object>();
SubscribeToSelectedItems();
if (_selection.SingleSelect)
{
SelectionMode &= ~SelectionMode.Multiple;
}
else
{
SelectionMode |= SelectionMode.Multiple;
}
if (_selection.AutoSelect)
{
SelectionMode |= SelectionMode.AlwaysSelected;
}
else
{
SelectionMode &= ~SelectionMode.AlwaysSelected;
}
UpdateContainerSelection();
var selectedItem = SelectedItem;
if (_selectedItem != selectedItem)
{
RaisePropertyChanged(SelectedItemProperty, _selectedItem, selectedItem);
_selectedItem = selectedItem;
}
}
}
}
}
@ -185,186 +246,12 @@ namespace Avalonia.Controls
/// Note that this method only selects nodes currently visible due to their parent nodes
/// being expanded: it does not expand nodes.
/// </remarks>
public void SelectAll()
{
SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Items);
}
public void SelectAll() => Selection.SelectAll();
/// <summary>
/// Deselects all items in the <see cref="TreeView"/>.
/// </summary>
public void UnselectAll()
{
SelectedItems.Clear();
}
/// <summary>
/// Subscribes to the <see cref="SelectedItems"/> CollectionChanged event, if any.
/// </summary>
private void SubscribeToSelectedItems()
{
if (_selectedItems is INotifyCollectionChanged incc)
{
incc.CollectionChanged += SelectedItemsCollectionChanged;
}
SelectedItemsCollectionChanged(
_selectedItems,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void SelectSingleItem(object item)
{
SelectedItems.Clear();
SelectedItems.Add(item);
}
/// <summary>
/// Called when the <see cref="SelectedItems"/> CollectionChanged event is raised.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
IList added = null;
IList removed = null;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SelectedItemsAdded(e.NewItems.Cast<object>().ToArray());
if (AutoScrollToSelectedItem)
{
var container = (TreeViewItem)ItemContainerGenerator.Index.ContainerFromItem(e.NewItems[0]);
container?.BringIntoView();
}
added = e.NewItems;
break;
case NotifyCollectionChangedAction.Remove:
if (!_syncingSelectedItems)
{
if (SelectedItems.Count == 0)
{
SelectedItem = null;
}
else
{
var selectedIndex = SelectedItems.IndexOf(_selectedItem);
if (selectedIndex == -1)
{
var old = _selectedItem;
_selectedItem = SelectedItems[0];
RaisePropertyChanged(SelectedItemProperty, old, _selectedItem);
}
}
}
foreach (var item in e.OldItems)
{
MarkItemSelected(item, false);
}
removed = e.OldItems;
break;
case NotifyCollectionChangedAction.Reset:
foreach (IControl container in ItemContainerGenerator.Index.Containers)
{
MarkContainerSelected(container, false);
}
if (SelectedItems.Count > 0)
{
SelectedItemsAdded(SelectedItems);
added = SelectedItems;
}
else if (!_syncingSelectedItems)
{
SelectedItem = null;
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems)
{
MarkItemSelected(item, false);
}
foreach (var item in e.NewItems)
{
MarkItemSelected(item, true);
}
if (SelectedItem != SelectedItems[0] && !_syncingSelectedItems)
{
var oldItem = SelectedItem;
var item = SelectedItems[0];
_selectedItem = item;
RaisePropertyChanged(SelectedItemProperty, oldItem, item);
}
added = e.NewItems;
removed = e.OldItems;
break;
}
if (added?.Count > 0 || removed?.Count > 0)
{
var changed = new SelectionChangedEventArgs(
SelectingItemsControl.SelectionChangedEvent,
removed ?? Empty,
added ?? Empty);
RaiseEvent(changed);
}
}
private void MarkItemSelected(object item, bool selected)
{
var container = ItemContainerGenerator.Index.ContainerFromItem(item);
MarkContainerSelected(container, selected);
}
private void SelectedItemsAdded(IList items)
{
if (items.Count == 0)
{
return;
}
foreach (object item in items)
{
MarkItemSelected(item, true);
}
if (SelectedItem == null && !_syncingSelectedItems)
{
SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]);
}
}
/// <summary>
/// Unsubscribes from the <see cref="SelectedItems"/> CollectionChanged event, if any.
/// </summary>
private void UnsubscribeFromSelectedItems()
{
if (_selectedItems is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= SelectedItemsCollectionChanged;
}
}
public void UnselectAll() => Selection.ClearSelection();
(bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element,
NavigationDirection direction)
@ -448,6 +335,72 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when <see cref="SelectionModel.PropertyChanged"/> is raised.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void OnSelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectionModel.AnchorIndex) && AutoScrollToSelectedItem)
{
var container = ContainerFromIndex(Selection.AnchorIndex);
if (container != null)
{
container.BringIntoView();
}
}
}
/// <summary>
/// Called when <see cref="SelectionModel.SelectionChanged"/> is raised.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void OnSelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
void Mark(IndexPath index, bool selected)
{
var container = ContainerFromIndex(index);
if (container != null)
{
MarkContainerSelected(container, selected);
}
}
foreach (var i in e.SelectedIndices)
{
Mark(i, true);
}
foreach (var i in e.DeselectedIndices)
{
Mark(i, false);
}
var newSelectedItem = SelectedItem;
if (newSelectedItem != _selectedItem)
{
RaisePropertyChanged(SelectedItemProperty, _selectedItem, newSelectedItem);
_selectedItem = newSelectedItem;
}
var ev = new SelectionChangedEventArgs(
SelectionChangedEvent,
e.DeselectedItems.ToList(),
e.SelectedItems.ToList());
RaiseEvent(ev);
}
private void OnSelectionModelChildrenRequested(object sender, SelectionModelChildrenRequestedEventArgs e)
{
var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as ItemsControl;
e.Children = container?.Items as IEnumerable;
}
private TreeViewItem GetContainerInDirection(
TreeViewItem from,
NavigationDirection direction,
@ -501,6 +454,12 @@ namespace Avalonia.Controls
return result;
}
protected override void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
{
Selection.Source = Items;
base.ItemsChanged(e);
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
@ -522,6 +481,18 @@ namespace Avalonia.Controls
}
}
protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == SelectionModeProperty)
{
var mode = newValue.GetValueOrDefault<SelectionMode>();
Selection.SingleSelect = !mode.HasFlagCustom(SelectionMode.Multiple);
Selection.AutoSelect = mode.HasFlagCustom(SelectionMode.AlwaysSelected);
}
}
/// <summary>
/// Updates the selection for an item based on user interaction.
/// </summary>
@ -537,9 +508,9 @@ namespace Avalonia.Controls
bool toggleModifier = false,
bool rightButton = false)
{
var item = ItemContainerGenerator.Index.ItemFromContainer(container);
var index = IndexFromContainer((TreeViewItem)container);
if (item == null)
if (index.GetSize() == 0)
{
return;
}
@ -556,41 +527,48 @@ namespace Avalonia.Controls
var multi = (mode & SelectionMode.Multiple) != 0;
var range = multi && selectedContainer != null && rangeModifier;
if (rightButton)
if (!select)
{
Selection.DeselectAt(index);
}
else if (rightButton)
{
if (!SelectedItems.Contains(item))
if (!Selection.IsSelectedAt(index))
{
SelectSingleItem(item);
Selection.SelectedIndex = index;
}
}
else if (!toggle && !range)
{
SelectSingleItem(item);
Selection.SelectedIndex = index;
}
else if (multi && range)
{
SynchronizeItems(
SelectedItems,
GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem));
using var operation = Selection.Update();
var anchor = Selection.AnchorIndex;
if (anchor.GetSize() == 0)
{
anchor = new IndexPath(0);
}
Selection.ClearSelection();
Selection.AnchorIndex = anchor;
Selection.SelectRangeFromAnchorTo(index);
}
else
{
var i = SelectedItems.IndexOf(item);
if (i != -1)
if (Selection.IsSelectedAt(index))
{
SelectedItems.Remove(item);
Selection.DeselectAt(index);
}
else if (multi)
{
Selection.SelectAt(index);
}
else
{
if (multi)
{
SelectedItems.Add(item);
}
else
{
SelectedItem = item;
}
Selection.SelectedIndex = index;
}
}
}
@ -613,117 +591,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Find which node is first in hierarchy.
/// </summary>
/// <param name="treeView">Search root.</param>
/// <param name="nodeA">Nodes to find.</param>
/// <param name="nodeB">Node to find.</param>
/// <returns>Found first node.</returns>
private static TreeViewItem FindFirstNode(TreeView treeView, TreeViewItem nodeA, TreeViewItem nodeB)
{
return FindInContainers(treeView.ItemContainerGenerator, nodeA, nodeB);
}
private static TreeViewItem FindInContainers(ITreeItemContainerGenerator containerGenerator,
TreeViewItem nodeA,
TreeViewItem nodeB)
{
IEnumerable<ItemContainerInfo> containers = containerGenerator.Containers;
foreach (ItemContainerInfo container in containers)
{
TreeViewItem node = FindFirstNode(container.ContainerControl as TreeViewItem, nodeA, nodeB);
if (node != null)
{
return node;
}
}
return null;
}
private static TreeViewItem FindFirstNode(TreeViewItem node, TreeViewItem nodeA, TreeViewItem nodeB)
{
if (node == null)
{
return null;
}
TreeViewItem match = node == nodeA ? nodeA : node == nodeB ? nodeB : null;
if (match != null)
{
return match;
}
return FindInContainers(node.ItemContainerGenerator, nodeA, nodeB);
}
/// <summary>
/// Returns all items that belong to containers between <paramref name="from"/> and <paramref name="to"/>.
/// The range is inclusive.
/// </summary>
/// <param name="from">From container.</param>
/// <param name="to">To container.</param>
private List<object> GetItemsInRange(TreeViewItem from, TreeViewItem to)
{
var items = new List<object>();
if (from == null || to == null)
{
return items;
}
TreeViewItem firstItem = FindFirstNode(this, from, to);
if (firstItem == null)
{
return items;
}
bool wasReversed = false;
if (firstItem == to)
{
var temp = from;
from = to;
to = temp;
wasReversed = true;
}
TreeViewItem node = from;
while (node != to)
{
var item = ItemContainerGenerator.Index.ItemFromContainer(node);
if (item != null)
{
items.Add(item);
}
node = GetContainerInDirection(node, NavigationDirection.Down, true);
}
var toItem = ItemContainerGenerator.Index.ItemFromContainer(to);
if (toItem != null)
{
items.Add(toItem);
}
if (wasReversed)
{
items.Reverse();
}
return items;
}
/// <summary>
/// Updates the selection based on an event that may have originated in a container that
/// belongs to the control.
@ -829,26 +696,90 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Makes a list of objects equal another (though doesn't preserve order).
/// </summary>
/// <param name="items">The items collection.</param>
/// <param name="desired">The desired items.</param>
private static void SynchronizeItems(IList items, IEnumerable<object> desired)
private void MarkContainersUnselected()
{
var list = items.Cast<object>().ToList();
var toRemove = list.Except(desired).ToList();
var toAdd = desired.Except(list).ToList();
foreach (var container in ItemContainerGenerator.Index.Containers)
{
MarkContainerSelected(container, false);
}
}
private void UpdateContainerSelection()
{
var index = ItemContainerGenerator.Index;
foreach (var container in index.Containers)
{
var i = IndexFromContainer((TreeViewItem)container);
MarkContainerSelected(
container,
Selection.IsSelectedAt(i) != false);
}
}
private static IndexPath IndexFromContainer(TreeViewItem container)
{
var result = new List<int>();
while (true)
{
if (container.Level == 0)
{
var treeView = container.FindAncestorOfType<TreeView>();
if (treeView == null)
{
return default;
}
result.Add(treeView.ItemContainerGenerator.IndexFromContainer(container));
result.Reverse();
return new IndexPath(result);
}
else
{
var parent = container.FindAncestorOfType<TreeViewItem>();
if (parent == null)
{
return default;
}
result.Add(parent.ItemContainerGenerator.IndexFromContainer(container));
container = parent;
}
}
}
private IndexPath IndexFromItem(object item)
{
var container = ItemContainerGenerator.Index.ContainerFromItem(item) as TreeViewItem;
foreach (var i in toRemove)
if (container != null)
{
items.Remove(i);
return IndexFromContainer(container);
}
foreach (var i in toAdd)
return default;
}
private TreeViewItem ContainerFromIndex(IndexPath index)
{
TreeViewItem treeViewItem = null;
for (var i = 0; i < index.GetSize(); ++i)
{
items.Add(i);
var generator = treeViewItem?.ItemContainerGenerator ?? ItemContainerGenerator;
treeViewItem = generator.ContainerFromIndex(index.GetAt(i)) as TreeViewItem;
if (treeViewItem == null)
{
return null;
}
}
return treeViewItem;
}
}
}

17
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -10,7 +10,6 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
@ -244,12 +243,12 @@ namespace Avalonia.Controls.UnitTests
ClickContainer(item2Container, InputModifiers.Control);
Assert.True(item2Container.IsSelected);
Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType<Node>());
Assert.Equal(new[] {item1, item2}, target.Selection.SelectedItems.OfType<Node>());
ClickContainer(item1Container, InputModifiers.Control);
Assert.False(item1Container.IsSelected);
Assert.DoesNotContain(item1, target.SelectedItems.OfType<Node>());
Assert.DoesNotContain(item1, target.Selection.SelectedItems.OfType<Node>());
}
[Fact]
@ -749,11 +748,11 @@ namespace Avalonia.Controls.UnitTests
target.SelectAll();
AssertChildrenSelected(target, tree[0]);
Assert.Equal(5, target.SelectedItems.Count);
Assert.Equal(5, target.Selection.SelectedItems.Count);
_mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right);
Assert.Equal(5, target.SelectedItems.Count);
Assert.Equal(5, target.Selection.SelectedItems.Count);
}
[Fact]
@ -785,11 +784,11 @@ namespace Avalonia.Controls.UnitTests
ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(toContainer, InputModifiers.Shift);
Assert.Equal(2, target.SelectedItems.Count);
Assert.Equal(2, target.Selection.SelectedItems.Count);
_mouse.Click(thenContainer, MouseButton.Right);
Assert.Equal(1, target.SelectedItems.Count);
Assert.Equal(1, target.Selection.SelectedItems.Count);
}
[Fact]
@ -819,7 +818,7 @@ namespace Avalonia.Controls.UnitTests
_mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: InputModifiers.Shift);
Assert.Equal(1, target.SelectedItems.Count);
Assert.Equal(1, target.Selection.SelectedItems.Count);
}
[Fact]
@ -849,7 +848,7 @@ namespace Avalonia.Controls.UnitTests
_mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: InputModifiers.Control);
Assert.Equal(1, target.SelectedItems.Count);
Assert.Equal(1, target.Selection.SelectedItems.Count);
}
[Fact]

Loading…
Cancel
Save