using System.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; namespace Avalonia.Controls { /// /// An item in a . /// public class TreeViewItem : HeaderedItemsControl, ISelectable { /// /// Defines the property. /// public static readonly DirectProperty IsExpandedProperty = AvaloniaProperty.RegisterDirect( nameof(IsExpanded), o => o.IsExpanded, (o, v) => o.IsExpanded = v); /// /// Defines the property. /// public static readonly StyledProperty IsSelectedProperty = ListBoxItem.IsSelectedProperty.AddOwner(); /// /// Defines the property. /// public static readonly DirectProperty LevelProperty = AvaloniaProperty.RegisterDirect( nameof(Level), o => o.Level); private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel()); private TreeView _treeView; private IControl _header; private bool _isExpanded; private int _level; /// /// Initializes static members of the class. /// static TreeViewItem() { SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); ParentProperty.Changed.AddClassHandler((o, e) => o.OnParentChanged(e)); RequestBringIntoViewEvent.AddClassHandler((x, e) => x.OnRequestBringIntoView(e)); } /// /// Gets or sets a value indicating whether the item is expanded to show its children. /// public bool IsExpanded { get { return _isExpanded; } set { SetAndRaise(IsExpandedProperty, ref _isExpanded, value); } } /// /// Gets or sets the selection state of the item. /// public bool IsSelected { get { return GetValue(IsSelectedProperty); } set { SetValue(IsSelectedProperty, value); } } /// /// Gets the level/indentation of the item. /// public int Level { get { return _level; } private set { SetAndRaise(LevelProperty, ref _level, value); } } /// /// Gets the for the tree view. /// public new ITreeItemContainerGenerator ItemContainerGenerator => (ITreeItemContainerGenerator)base.ItemContainerGenerator; /// protected override IItemContainerGenerator CreateItemContainerGenerator() { return new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty); } /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); _treeView = this.GetLogicalAncestors().OfType().FirstOrDefault(); Level = CalculateDistanceFromLogicalParent(this) - 1; ItemContainerGenerator.UpdateIndex(); if (ItemTemplate == null && _treeView?.ItemTemplate != null) { ItemTemplate = _treeView.ItemTemplate; } } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); ItemContainerGenerator.UpdateIndex(); } protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) { if (e.TargetObject == this && _header != null) { var m = _header.TransformToVisual(this); if (m.HasValue) { var bounds = new Rect(_header.Bounds.Size); var rect = bounds.TransformToAABB(m.Value); e.TargetRect = rect; } } } /// protected override void OnKeyDown(KeyEventArgs e) { if (!e.Handled) { switch (e.Key) { case Key.Right: if (Items != null && Items.Cast().Any()) { IsExpanded = true; } e.Handled = true; break; case Key.Left: IsExpanded = false; e.Handled = true; break; } } // Don't call base.OnKeyDown - let events bubble up to containing TreeView. } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _header = e.NameScope.Find("PART_Header"); } private static int CalculateDistanceFromLogicalParent(ILogical logical, int @default = -1) where T : class { var result = 0; while (logical != null && !(logical is T)) { ++result; logical = logical.LogicalParent; } return logical != null ? result : @default; } private void OnParentChanged(AvaloniaPropertyChangedEventArgs e) { if (!((ILogical)this).IsAttachedToLogicalTree && e.NewValue is null) { // If we're not attached to the logical tree, then OnDetachedFromLogicalTree isn't going to be // called when the item is removed. This results in the item not being removed from the index, // causing #3551. In this case, update the index when Parent is changed to null. ItemContainerGenerator.UpdateIndex(); } } } }