|
|
@@ -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;
|
|
|
}
|
|
|
}
|
|
|
}
|