// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Styling; using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls { /// /// Displays a hierachical tree of data. /// public class TreeView : ItemsControl, ICustomKeyboardNavigation { /// /// Defines the property. /// public static readonly StyledProperty AutoScrollToSelectedItemProperty = SelectingItemsControl.AutoScrollToSelectedItemProperty.AddOwner(); /// /// Defines the property. /// public static readonly DirectProperty SelectedItemProperty = SelectingItemsControl.SelectedItemProperty.AddOwner( o => o.SelectedItem, (o, v) => o.SelectedItem = v); private object _selectedItem; /// /// Initializes static members of the class. /// static TreeView() { // HACK: Needed or SelectedItem property will not be found in Release build. } /// /// Gets the for the tree view. /// public new ITreeItemContainerGenerator ItemContainerGenerator => (ITreeItemContainerGenerator)base.ItemContainerGenerator; /// /// Gets or sets a value indicating whether to automatically scroll to newly selected items. /// public bool AutoScrollToSelectedItem { get { return GetValue(AutoScrollToSelectedItemProperty); } set { SetValue(AutoScrollToSelectedItemProperty, value); } } /// /// Gets or sets the selected item. /// public object SelectedItem { get { return _selectedItem; } set { if (_selectedItem != null) { var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); MarkContainerSelected(container, false); } SetAndRaise(SelectedItemProperty, ref _selectedItem, value); if (_selectedItem != null) { var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); MarkContainerSelected(container, true); if (AutoScrollToSelectedItem && container != null) { container.BringIntoView(); } } } } (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction) { if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) { if (!this.IsVisualAncestorOf(element)) { IControl result = _selectedItem != null ? ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) : ItemContainerGenerator.ContainerFromIndex(0); return (true, result); } else { return (true, null); } } return (false, null); } /// protected override IItemContainerGenerator CreateItemContainerGenerator() { var result = new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, new TreeContainerIndex()); result.Index.Materialized += ContainerMaterialized; return result; } /// protected override void OnGotFocus(GotFocusEventArgs e) { if (e.NavigationMethod == NavigationMethod.Directional) { e.Handled = UpdateSelectionFromEventSource( e.Source, true, (e.InputModifiers & InputModifiers.Shift) != 0); } } /// protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right) { e.Handled = UpdateSelectionFromEventSource( e.Source, true, (e.InputModifiers & InputModifiers.Shift) != 0, (e.InputModifiers & InputModifiers.Control) != 0); } } /// /// Updates the selection for an item based on user interaction. /// /// The container. /// Whether the item should be selected or unselected. /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). protected void UpdateSelectionFromContainer( IControl container, bool select = true, bool rangeModifier = false, bool toggleModifier = false) { var item = ItemContainerGenerator.Index.ItemFromContainer(container); if (item != null) { if (SelectedItem != null) { var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem); MarkContainerSelected(old, false); } SelectedItem = item; if (SelectedItem != null) { MarkContainerSelected(container, true); } } } /// /// Updates the selection based on an event that may have originated in a container that /// belongs to the control. /// /// The control that raised the event. /// Whether the container should be selected or unselected. /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// /// True if the event originated from a container that belongs to the control; otherwise /// false. /// protected bool UpdateSelectionFromEventSource( IInteractive eventSource, bool select = true, bool rangeModifier = false, bool toggleModifier = false) { var container = GetContainerFromEventSource(eventSource); if (container != null) { UpdateSelectionFromContainer(container, select, rangeModifier, toggleModifier); return true; } return false; } /// /// Tries to get the container that was the source of an event. /// /// The control that raised the event. /// The container or null if the event did not originate in a container. protected IControl GetContainerFromEventSource(IInteractive eventSource) { var item = ((IVisual)eventSource).GetSelfAndVisualAncestors() .OfType() .FirstOrDefault(); if (item != null) { if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index) { return item; } } return null; } /// /// Called when a new item container is materialized, to set its selected state. /// /// The event sender. /// The event args. private void ContainerMaterialized(object sender, ItemContainerEventArgs e) { var selectedItem = SelectedItem; if (selectedItem != null) { foreach (var container in e.Containers) { if (container.Item == selectedItem) { ((TreeViewItem)container.ContainerControl).IsSelected = true; if (AutoScrollToSelectedItem) { Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView); } break; } } } } /// /// Sets a container's 'selected' class or . /// /// The container. /// Whether the control is selected private void MarkContainerSelected(IControl container, bool selected) { if (container != null) { var selectable = container as ISelectable; if (selectable != null) { selectable.IsSelected = selected; } else { ((IPseudoClasses)container.Classes).Set(":selected", selected); } } } } }