diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 41c17bd3e1..e6af255c29 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -158,15 +158,6 @@ namespace Avalonia.Controls set { SetValue(VerticalContentAlignmentProperty, value); } } - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new ItemContainerGenerator( - this, - ComboBoxItem.ContentProperty, - ComboBoxItem.ContentTemplateProperty); - } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -449,14 +440,15 @@ namespace Avalonia.Controls private void SelectFocusedItem() { - foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers) - { - if (dropdownItem.ContainerControl.IsFocused) - { - SelectedIndex = dropdownItem.Index; - break; - } - } + throw new NotImplementedException(); + ////foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers) + ////{ + //// if (dropdownItem.ContainerControl.IsFocused) + //// { + //// SelectedIndex = dropdownItem.Index; + //// break; + //// } + ////} } private void SelectNext() diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 020a6de539..c0b1297b6d 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -315,11 +315,6 @@ namespace Avalonia.Controls remove => _popupHostChangedHandler -= value; } - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new MenuItemContainerGenerator(this); - } - private void Open(Control control, Control placementTarget, bool requestedByPointer) { if (IsOpen) diff --git a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs index 278e498a67..7aca21b42e 100644 --- a/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs +++ b/src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs @@ -35,11 +35,6 @@ namespace Avalonia.Controls throw new NotSupportedException("Use MenuFlyout.ShowAt(Control) instead"); } - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new MenuItemContainerGenerator(this); - } - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs deleted file mode 100644 index 8f0e3c88c1..0000000000 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.Styling; - -namespace Avalonia.Controls.Generators -{ - /// - /// Creates containers for items and maintains a list of created containers. - /// - public interface IItemContainerGenerator - { - /// - /// Gets the currently realized containers. - /// - IEnumerable Containers { get; } - - /// - /// Gets or sets the theme to be applied to the items in the control. - /// - ControlTheme? ItemContainerTheme { get; set; } - - /// - /// Gets or sets the data template used to display the items in the control. - /// - IDataTemplate? ItemTemplate { get; set; } - - /// - /// Gets or sets the binding to use to bind to the member of an item used for displaying - /// - IBinding? DisplayMemberBinding { get; set; } - - /// - /// Gets the ContainerType, or null if its an untyped ContainerGenerator. - /// - Type? ContainerType { get; } - - /// - /// Signaled whenever new containers are materialized. - /// - event EventHandler? Materialized; - - /// - /// Event raised whenever containers are dematerialized. - /// - event EventHandler? Dematerialized; - - /// - /// Event raised whenever containers are recycled. - /// - event EventHandler? Recycled; - - /// - /// Creates a container control for an item. - /// - /// - /// The index of the item of data in the control's items. - /// - /// The item. - /// The created controls. - ItemContainerInfo Materialize(int index, object item); - - /// - /// Removes a set of created containers. - /// - /// - /// The index of the first item in the control's items. - /// - /// The the number of items to remove. - /// The removed containers. - IEnumerable Dematerialize(int startingIndex, int count); - - /// - /// Inserts space for newly inserted containers in the index. - /// - /// The index at which space should be inserted. - /// The number of blank spaces to create. - void InsertSpace(int index, int count); - - /// - /// Removes a set of created containers and updates the index of later containers to fill - /// the gap. - /// - /// - /// The index of the first item in the control's items. - /// - /// The the number of items to remove. - /// The removed containers. - IEnumerable RemoveRange(int startingIndex, int count); - - bool TryRecycle(int oldIndex, int newIndex, object item); - - /// - /// Clears all created containers and returns the removed controls. - /// - /// The removed controls. - IEnumerable Clear(); - - /// - /// Gets the container control representing the item with the specified index. - /// - /// The index. - /// The container, or null if no container created. - Control? ContainerFromIndex(int index); - - /// - /// Gets the index of the specified container control. - /// - /// The container. - /// The index of the container, or -1 if not found. - int IndexFromContainer(Control? container); - } -} diff --git a/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs deleted file mode 100644 index f15987fe79..0000000000 --- a/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Avalonia.Controls.Generators -{ - /// - /// Creates containers for tree items and maintains a list of created containers. - /// - public interface ITreeItemContainerGenerator : IItemContainerGenerator - { - /// - /// Gets the container index for the tree. - /// - TreeContainerIndex? Index { get; } - - /// - /// Updates the index based on the parent . - /// - void UpdateIndex(); - } -} diff --git a/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs b/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs deleted file mode 100644 index f65018531e..0000000000 --- a/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Avalonia.Controls.Generators -{ - /// - /// Provides details for the - /// and events. - /// - public class ItemContainerEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The container. - public ItemContainerEventArgs(ItemContainerInfo container) - { - StartingIndex = container.Index; - Containers = new[] { container }; - } - - /// - /// Initializes a new instance of the class. - /// - /// The index of the first container in the source items. - /// The containers. - /// - /// TODO: Do we really need to pass in StartingIndex here? The ItemContainerInfo objects - /// have an index, and what happens if the contains passed in aren't sequential? - /// - public ItemContainerEventArgs( - int startingIndex, - IList containers) - { - StartingIndex = startingIndex; - Containers = containers; - } - - /// - /// Gets the containers. - /// - public IList Containers { get; } - - /// - /// Gets the index of the first container in the source items. - /// - public int StartingIndex { get; } - } -} diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index d87422e57e..0598bb1a63 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -1,262 +1,68 @@ using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.Styling; namespace Avalonia.Controls.Generators { /// - /// Creates containers for items and maintains a list of created containers. + /// Generates containers for an . /// - public class ItemContainerGenerator : IItemContainerGenerator + /// + /// Although this class is similar to that found in WPF/UWP, in Avalonia this class only + /// concerns itself with generating and clearing item containers; it does not maintain a + /// record of the currently realized containers, that responsibility is delegated to the + /// items panel. + /// + public class ItemContainerGenerator { - private SortedDictionary _containers = new SortedDictionary(); + private ItemsControl _owner; - /// - /// Initializes a new instance of the class. - /// - /// The owner control. - public ItemContainerGenerator(Control owner) - { - Owner = owner ?? throw new ArgumentNullException(nameof(owner)); - } - - /// - public IEnumerable Containers => _containers.Values; - - /// - public event EventHandler? Materialized; - - /// - public event EventHandler? Dematerialized; - - /// - public event EventHandler? Recycled; + internal ItemContainerGenerator(ItemsControl owner) => _owner = owner; /// - /// Gets or sets the theme to be applied to the items in the control. + /// Creates a new container control. /// - public ControlTheme? ItemContainerTheme { get; set; } + /// The newly created container control. + /// + /// Before calling this method, should be + /// called to determine whether the item itself should be used as a container. After + /// calling this method, should + /// be called to prepare the container to display the specified item. + /// + public Control CreateContainer() => _owner.CreateContainerOverride(); /// - /// Gets or sets the data template used to display the items in the control. + /// Determines whether the specified item is (or is eligible to be) its own container. /// - public IDataTemplate? ItemTemplate { get; set; } - - /// - public IBinding? DisplayMemberBinding { get; set; } + /// The item. + /// true if the item is its own container, otherwise false. + /// + /// Whereas in WPF/UWP, non-control items can be their own container, in Avalonia only + /// control items may be; the caller is responsible for checking if each item is a control + /// and calling this method before creating a new container. + /// + public bool IsItemItsOwnContainer(Control container) => _owner.IsItemItsOwnContainerOverride(container); /// - /// Gets the owner control. + /// Prepares the specified element as the container for the corresponding item. /// - public Control Owner { get; } - - /// - public virtual Type? ContainerType => null; - - /// - public ItemContainerInfo Materialize(int index, object item) - { - var container = new ItemContainerInfo(CreateContainer(item)!, item, index); - - _containers.Add(container.Index, container); - Materialized?.Invoke(this, new ItemContainerEventArgs(container)); - - return container; - } - - /// - public virtual IEnumerable Dematerialize(int startingIndex, int count) - { - var result = new List(); - - for (int i = startingIndex; i < startingIndex + count; ++i) - { - result.Add(_containers[i]); - _containers.Remove(i); - } - - Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); - - return result; - } - - /// - public virtual void InsertSpace(int index, int count) - { - if (count > 0) - { - var toMove = _containers.Where(x => x.Key >= index) - .OrderByDescending(x => x.Key) - .ToArray(); - - foreach (var i in toMove) - { - _containers.Remove(i.Key); - i.Value.Index += count; - _containers.Add(i.Value.Index, i.Value); - } - } - } - - /// - public virtual IEnumerable RemoveRange(int startingIndex, int count) - { - var result = new List(); - - if (count > 0) - { - for (var i = startingIndex; i < startingIndex + count; ++i) - { - if (_containers.TryGetValue(i, out var found)) - { - result.Add(found); - } - - _containers.Remove(i); - } - - var toMove = _containers.Where(x => x.Key >= startingIndex) - .OrderBy(x => x.Key).ToArray(); - - foreach (var i in toMove) - { - _containers.Remove(i.Key); - i.Value.Index -= count; - _containers.Add(i.Value.Index, i.Value); - } - - Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); - - if (toMove.Length > 0) - { - var containers = toMove.Select(x => x.Value).ToArray(); - Recycled?.Invoke(this, new ItemContainerEventArgs(containers[0].Index, containers)); - } - } - - return result; - } - - /// - public virtual bool TryRecycle(int oldIndex, int newIndex, object item) => false; - - /// - public virtual IEnumerable Clear() - { - var result = Containers.ToArray(); - _containers.Clear(); - - if (result.Length > 0) - { - Dematerialized?.Invoke(this, new ItemContainerEventArgs(0, result)); - } - - return result; - } - - /// - public Control? ContainerFromIndex(int index) - { - ItemContainerInfo? result; - _containers.TryGetValue(index, out result); - return result?.ContainerControl; - } - - /// - public int IndexFromContainer(Control? container) - { - foreach (var i in _containers) - { - if (i.Value.ContainerControl == container) - { - return i.Key; - } - } - - return -1; - } + /// The element that's used to display the specified item. + /// The item to display. + /// The index of the item to display. + /// + /// If is true for an item, then this method + /// only needs to be called a single time, otherwise this method should be called after the + /// container is created, and each subsequent time the container is recycled to display a + /// new item. + /// + public void PrepareItemContainer(Control container, object? item, int index) => + _owner.PrepareItemContainer(container, item, index); /// - /// Creates the container for an item. + /// Undoes the effects of the method. /// - /// The item. - /// The created container control. - protected virtual Control? CreateContainer(object item) - { - var result = item as Control; - - if (result == null) - { - result = new ContentPresenter(); - if (DisplayMemberBinding is not null) - { - result.SetValue(StyledElement.DataContextProperty, item, BindingPriority.Style); - result.Bind(ContentPresenter.ContentProperty, DisplayMemberBinding, BindingPriority.Style); - } - else - { - result.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Style); - } - - if (ItemTemplate != null) - { - result.SetValue( - ContentPresenter.ContentTemplateProperty, - ItemTemplate, - BindingPriority.Style); - } - } + /// The element that's used to display the specified item. + public void ClearItemContainer(Control container) => _owner.ClearContainerForItemOverride(container); - if (ItemContainerTheme != null) - { - result.SetValue( - StyledElement.ThemeProperty, - ItemContainerTheme, - BindingPriority.Template); - } - - return result; - } - - /// - /// Moves a container. - /// - /// The old index. - /// The new index. - /// The new item. - /// The container info. - protected ItemContainerInfo MoveContainer(int oldIndex, int newIndex, object item) - { - var container = _containers[oldIndex]; - container.Index = newIndex; - container.Item = item; - _containers.Remove(oldIndex); - _containers.Add(newIndex, container); - return container; - } - - /// - /// Gets all containers with an index that fall within a range. - /// - /// The first index. - /// The number of elements in the range. - /// The containers. - protected IEnumerable GetContainerRange(int index, int count) - { - return _containers.Where(x => x.Key >= index && x.Key < index + count).Select(x => x.Value); - } - - /// - /// Raises the event. - /// - /// The event args. - protected void RaiseRecycled(ItemContainerEventArgs e) - { - Recycled?.Invoke(this, e); - } + public Control? ContainerFromIndex(int index) => _owner.ContainerFromIndex(index); + public int IndexFromContainer(Control container) => _owner.IndexFromContainer(container); } } diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs deleted file mode 100644 index ad5bf0219c..0000000000 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using Avalonia.Data; - -namespace Avalonia.Controls.Generators -{ - /// - /// Creates containers for items and maintains a list of created containers. - /// - /// The type of the container. - public class ItemContainerGenerator : ItemContainerGenerator where T : Control, new() - { - /// - /// Initializes a new instance of the class. - /// - /// The owner control. - /// The container's Content property. - /// The container's ContentTemplate property. - public ItemContainerGenerator( - Control owner, - AvaloniaProperty contentProperty, - AvaloniaProperty? contentTemplateProperty) - : base(owner) - { - ContentProperty = contentProperty ?? throw new ArgumentNullException(nameof(contentProperty)); - ContentTemplateProperty = contentTemplateProperty; - } - - /// - public override Type ContainerType => typeof(T); - - /// - /// Gets the container's Content property. - /// - protected AvaloniaProperty ContentProperty { get; } - - /// - /// Gets the container's ContentTemplate property. - /// - protected AvaloniaProperty? ContentTemplateProperty { get; } - - /// - protected override Control? CreateContainer(object item) - { - var container = item as T; - - if (container is null) - { - container = new T(); - - if (ContentTemplateProperty != null) - { - container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); - } - - if (DisplayMemberBinding is not null) - { - container.SetValue(StyledElement.DataContextProperty, item, BindingPriority.Style); - container.Bind(ContentProperty, DisplayMemberBinding, BindingPriority.Style); - } - else - { - container.SetValue(ContentProperty, item, BindingPriority.Style); - } - - if (!(item is Control)) - { - container.DataContext = item; - } - } - - if (ItemContainerTheme != null) - { - container.SetValue(StyledElement.ThemeProperty, ItemContainerTheme, BindingPriority.Style); - } - - return container; - } - - /// - public override bool TryRecycle(int oldIndex, int newIndex, object item) - { - var container = ContainerFromIndex(oldIndex); - - if (container == null) - { - throw new IndexOutOfRangeException("Could not recycle container: not materialized."); - } - - container.SetValue(ContentProperty, item); - - if (!(item is Control)) - { - container.DataContext = item; - } - - var info = MoveContainer(oldIndex, newIndex, item); - RaiseRecycled(new ItemContainerEventArgs(info)); - - return true; - } - } -} diff --git a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs deleted file mode 100644 index 047e457e7c..0000000000 --- a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Avalonia.Controls.Generators -{ - /// - /// Holds information about an item container generated by an - /// . - /// - public class ItemContainerInfo - { - /// - /// Initializes a new instance of the class. - /// - /// The container control. - /// The item that the container represents. - /// - /// The index of the item in the collection. - /// - public ItemContainerInfo(Control container, object item, int index) - { - ContainerControl = container; - Item = item; - Index = index; - } - - /// - /// Gets the container control. - /// - /// - /// This will be null if is null. - /// - public Control ContainerControl { get; } - - /// - /// Gets the item that the container represents. - /// - public object Item { get; internal set; } - - /// - /// Gets the index of the item in the collection. - /// - public int Index { get; set; } - } -} diff --git a/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs deleted file mode 100644 index 0dcb6c05e1..0000000000 --- a/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Avalonia.Controls.Generators -{ - public class MenuItemContainerGenerator : ItemContainerGenerator - { - /// - /// Initializes a new instance of the class. - /// - /// The owner control. - public MenuItemContainerGenerator(Control owner) - : base(owner, MenuItem.HeaderProperty, null) - { - } - - /// - protected override Control? CreateContainer(object item) - { - var separator = item as Separator; - return separator != null ? separator : base.CreateContainer(item); - } - } -} diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs deleted file mode 100644 index b26b113fbb..0000000000 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.LogicalTree; -using Avalonia.Reactive; -using Avalonia.VisualTree; - -namespace Avalonia.Controls.Generators -{ - public class TabItemContainerGenerator : ItemContainerGenerator - { - public TabItemContainerGenerator(TabControl owner) - : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty) - { - Owner = owner; - } - - public new TabControl Owner { get; } - - protected override Control CreateContainer(object item) - { - var tabItem = (TabItem)base.CreateContainer(item)!; - - tabItem.Bind(TabItem.TabStripPlacementProperty, new OwnerBinding( - tabItem, - TabControl.TabStripPlacementProperty)); - - if (tabItem.HeaderTemplate == null) - { - tabItem.Bind(TabItem.HeaderTemplateProperty, new OwnerBinding( - tabItem, - TabControl.ItemTemplateProperty)); - } - - if (Owner.HeaderDisplayMemberBinding is not null) - { - tabItem.Bind(HeaderedContentControl.HeaderProperty, Owner.HeaderDisplayMemberBinding, - BindingPriority.Style); - } - - if (tabItem.Header == null) - { - if (item is IHeadered headered) - { - tabItem.Header = headered.Header; - } - else - { - if (!(tabItem.DataContext is Control)) - { - tabItem.Header = tabItem.DataContext; - } - } - } - - if (!(tabItem.Content is Control)) - { - tabItem.Bind(TabItem.ContentTemplateProperty, new OwnerBinding( - tabItem, - TabControl.ContentTemplateProperty)); - } - - return tabItem; - } - - private class OwnerBinding : SingleSubscriberObservableBase - { - private readonly TabItem _item; - private readonly StyledProperty _ownerProperty; - private IDisposable? _ownerSubscription; - private IDisposable? _propertySubscription; - - public OwnerBinding(TabItem item, StyledProperty ownerProperty) - { - _item = item; - _ownerProperty = ownerProperty; - } - - protected override void Subscribed() - { - _ownerSubscription = ControlLocator.Track(_item, 0, typeof(TabControl)).Subscribe(OwnerChanged); - } - - protected override void Unsubscribed() - { - _ownerSubscription?.Dispose(); - _ownerSubscription = null; - } - - private void OwnerChanged(ILogical? c) - { - _propertySubscription?.Dispose(); - _propertySubscription = null; - - if (c is TabControl tabControl) - { - _propertySubscription = tabControl.GetObservable(_ownerProperty) - .Subscribe(x => PublishNext(x)); - } - } - } - } -} diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs deleted file mode 100644 index 02b8e7003e..0000000000 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Avalonia.Controls.Generators -{ - /// - /// Maintains an index of all item containers currently materialized by a . - /// - /// - /// Each has its own - /// that maintains the list of its direct children, but they also share an instance of this - /// class in their property which tracks - /// the containers materialized for the entire tree. - /// - public class TreeContainerIndex - { - private readonly Dictionary> _itemToContainerSet = new Dictionary>(); - private readonly Dictionary _itemToContainer = new Dictionary(); - private readonly Dictionary _containerToItem = new Dictionary(); - - /// - /// Signaled whenever new containers are materialized. - /// - public event EventHandler? Materialized; - - /// - /// Event raised whenever containers are dematerialized. - /// - public event EventHandler? Dematerialized; - - /// - /// Gets the currently materialized containers. - /// - public IEnumerable Containers => _containerToItem.Keys; - - /// - /// Gets the items of currently materialized containers. - /// - public IEnumerable Items => _containerToItem.Values; - - /// - /// Adds an entry to the index. - /// - /// The item. - /// The item container. - public void Add(object item, Control container) - { - _itemToContainer[item] = container; - if (_itemToContainerSet.TryGetValue(item, out var set)) - { - set.Add(container); - } - else - { - _itemToContainerSet.Add(item, new HashSet { container }); - } - - _containerToItem.Add(container, item); - - Materialized?.Invoke( - this, - new ItemContainerEventArgs(new ItemContainerInfo(container, item, 0))); - } - - /// - /// Removes a container from private collections. - /// - /// The item container. - /// The DataContext object - private void RemoveContainer(Control container, object item) - { - if (_itemToContainerSet.TryGetValue(item, out var set)) - { - set.Remove(container); - if (set.Count == 0) - { - _itemToContainerSet.Remove(item); - _itemToContainer.Remove(item); - } - else - { - _itemToContainer[item] = set.First(); - } - } - } - - /// - /// Removes a container from the index. - /// - /// The item container. - public void Remove(Control container) - { - var item = _containerToItem[container]; - _containerToItem.Remove(container); - RemoveContainer(container, item); - - Dematerialized?.Invoke( - this, - new ItemContainerEventArgs(new ItemContainerInfo(container, item, 0))); - } - - /// - /// Removes a set of containers from the index. - /// - /// The index of the first item. - /// The item containers. - public void Remove(int startingIndex, IEnumerable containers) - { - foreach (var container in containers) - { - var item = _containerToItem[container.ContainerControl]; - _containerToItem.Remove(container.ContainerControl); - RemoveContainer(container.ContainerControl, item); - } - - Dematerialized?.Invoke( - this, - new ItemContainerEventArgs(startingIndex, containers.ToList())); - } - - /// - /// Gets the container for an item. - /// - /// The item. - /// The container, or null of not found. - public Control? ContainerFromItem(object item) - { - if (item != null) - { - _itemToContainer.TryGetValue(item, out var result); - if (result == null) - { - _itemToContainerSet.TryGetValue(item, out var set); - if (set?.Count > 0) - { - return set.FirstOrDefault(); - } - } - return result; - } - - return null; - } - - /// - /// Gets the item for a container. - /// - /// The container. - /// The item, or null of not found. - public object? ItemFromContainer(Control? container) - { - if (container != null) - { - _containerToItem.TryGetValue(container, out var result); - if (result != null) - { - _itemToContainer[result] = container; - } - return result; - } - - return null; - } - } -} diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs deleted file mode 100644 index b7111c0ef2..0000000000 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.LogicalTree; - -namespace Avalonia.Controls.Generators -{ - /// - /// Creates containers for tree items and maintains a list of created containers. - /// - /// The type of the container. - public class TreeItemContainerGenerator : ItemContainerGenerator, ITreeItemContainerGenerator - where T : Control, new() - { - private TreeView? _treeView; - - /// - /// Initializes a new instance of the class. - /// - /// The owner control. - /// The container's Content property. - /// The container's ContentTemplate property. - /// The container's Items property. - /// The container's IsExpanded property. - public TreeItemContainerGenerator( - Control owner, - AvaloniaProperty contentProperty, - AvaloniaProperty contentTemplateProperty, - AvaloniaProperty itemsProperty, - AvaloniaProperty isExpandedProperty) - : base(owner, contentProperty, contentTemplateProperty) - { - ItemsProperty = itemsProperty ?? throw new ArgumentNullException(nameof(itemsProperty)); - IsExpandedProperty = isExpandedProperty ?? throw new ArgumentNullException(nameof(isExpandedProperty)); - UpdateIndex(); - } - - /// - /// Gets the container index for the tree. - /// - public TreeContainerIndex? Index { get; private set; } - - /// - /// Gets the item container's Items property. - /// - protected AvaloniaProperty ItemsProperty { get; } - - /// - /// Gets the item container's IsExpanded property. - /// - protected AvaloniaProperty IsExpandedProperty { get; } - - /// - protected override Control? CreateContainer(object? item) - { - var container = item as T; - - if (item == null) - { - return null; - } - else if (container != null) - { - Index?.Add(item, container); - return container; - } - else - { - var template = GetTreeDataTemplate(item, ItemTemplate); - var result = new T(); - - if (ItemContainerTheme != null) - { - result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style); - } - - if (DisplayMemberBinding is not null) - { - result.SetValue(StyledElement.DataContextProperty, item, BindingPriority.Style); - result.Bind(ContentProperty, DisplayMemberBinding, BindingPriority.Style); - } - else - { - result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style); - } - - var itemsSelector = template.ItemsSelector(item); - - if (itemsSelector != null) - { - BindingOperations.Apply(result, ItemsProperty, itemsSelector, null); - } - - if (!(item is Control)) - { - result.DataContext = item; - } - - Index?.Add(item, result); - - return result; - } - } - - public override IEnumerable Clear() - { - var items = base.Clear(); - Index?.Remove(0, items); - return items; - } - - public override IEnumerable Dematerialize(int startingIndex, int count) - { - Index?.Remove(startingIndex, GetContainerRange(startingIndex, count)); - return base.Dematerialize(startingIndex, count); - } - - public override IEnumerable RemoveRange(int startingIndex, int count) - { - Index?.Remove(startingIndex, GetContainerRange(startingIndex, count)); - return base.RemoveRange(startingIndex, count); - } - - public override bool TryRecycle(int oldIndex, int newIndex, object item) => false; - - public void UpdateIndex() - { - if (Owner is TreeView treeViewOwner && Index == null) - { - Index = new TreeContainerIndex(); - _treeView = treeViewOwner; - } - else - { - var treeView = Owner.GetSelfAndLogicalAncestors().OfType().FirstOrDefault(); - - if (treeView != _treeView) - { - Clear(); - Index = treeView?.ItemContainerGenerator?.Index; - _treeView = treeView; - } - } - } - - class WrapperTreeDataTemplate : ITreeDataTemplate - { - private readonly IDataTemplate _inner; - public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner; - public Control? Build(object? param) => _inner.Build(param); - public bool Match(object? data) => _inner.Match(data); - public InstancedBinding? ItemsSelector(object item) => null; - } - - private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate? primary) - { - var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default; - var treeTemplate = template as ITreeDataTemplate ?? new WrapperTreeDataTemplate(template); - return treeTemplate; - } - } -} diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index e51296cdec..c4356e2967 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -3,8 +3,8 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using Avalonia.Collections; using Avalonia.Automation.Peers; +using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; @@ -15,7 +15,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; -using Avalonia.VisualTree; using Avalonia.Styling; namespace Avalonia.Controls @@ -81,7 +80,7 @@ namespace Avalonia.Controls private IEnumerable? _items = new AvaloniaList(); private int _itemCount; - private IItemContainerGenerator? _itemContainerGenerator; + private ItemContainerGenerator? _itemContainerGenerator; private EventHandler? _childIndexChanged; /// @@ -103,27 +102,9 @@ namespace Avalonia.Controls } /// - /// Gets the for the control. + /// Gets the for the control. /// - public IItemContainerGenerator ItemContainerGenerator - { - get - { - if (_itemContainerGenerator == null) - { - _itemContainerGenerator = CreateItemContainerGenerator(); - - _itemContainerGenerator.ItemContainerTheme = ItemContainerTheme; - _itemContainerGenerator.ItemTemplate = ItemTemplate; - _itemContainerGenerator.DisplayMemberBinding = DisplayMemberBinding; - _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); - _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); - _itemContainerGenerator.Recycled += (_, e) => OnContainersRecycled(e); - } - - return _itemContainerGenerator; - } - } + public ItemContainerGenerator ItemContainerGenerator => _itemContainerGenerator ??= new(this); /// /// Gets or sets the items to display. @@ -174,11 +155,7 @@ namespace Avalonia.Controls /// /// Gets the items presenter control. /// - public ItemsPresenter? Presenter - { - get; - protected set; - } + public ItemsPresenter? Presenter { get; private set; } private protected bool WrapFocus { get; set; } @@ -188,6 +165,52 @@ namespace Avalonia.Controls remove => _childIndexChanged -= value; } + /// + /// Returns the container for the item at the specified index. + /// + /// The index of the item to retrieve. + /// + /// The container for the item at the specified index within the item collection, if the + /// item has a container; otherwise, null. + /// + public Control? ContainerFromIndex(int index) => Presenter?.ContainerFromIndex(index); + + /// + /// Returns the container corresponding to the specified item. + /// + /// The item to retrieve the container for. + /// + /// A container that corresponds to the specified item, if the item has a container and + /// exists in the collection; otherwise, null. + /// + public Control? ContainerFromItem(object item) + { + throw new NotImplementedException(); + } + + /// + /// Returns the index to the item that has the specified, generated container. + /// + /// The generated container to retrieve the item index for. + /// + /// The index to the item that corresponds to the specified generated container, or -1 if + /// is not found. + /// + public int IndexFromContainer(Control container) => Presenter?.IndexFromContainer(container) ?? -1; + + /// + /// Returns the item that corresponds to the specified, generated container. + /// + /// The control that corresponds to the item to be returned. + /// + /// The contained item, or the container if it does not contain an item. + /// + public object? ItemFromContainer(Control container) + { + // TODO: Should this throw or return null of container isn't a container? + throw new NotImplementedException(); + } + /// void IItemsPresenterHost.RegisterItemsPresenter(ItemsPresenter presenter) { @@ -197,7 +220,7 @@ namespace Avalonia.Controls } Presenter = presenter; - ItemContainerGenerator?.Clear(); + ////ItemContainerGenerator?.Clear(); if (Presenter is IChildIndexProvider innerProvider) { @@ -237,6 +260,47 @@ namespace Avalonia.Controls } } + /// + /// Creates or a container that can be used to display an item. + /// + protected internal virtual Control CreateContainerOverride() => new ContentPresenter(); + + /// + /// Prepares the specified element to display the specified item. + /// + /// The element that's used to display the specified item. + /// The item to display. + /// The index of the item to display. + protected internal virtual void PrepareContainerForItemOverride(Control container, object? item, int index) + { + if (container == item) + return; + + if (container is ContentControl cc) + { + cc.SetValue(ContentControl.ContentProperty, item, BindingPriority.Template); + if (ItemTemplate is { } it) + cc.SetValue(ContentControl.ContentTemplateProperty, it, BindingPriority.Template); + } + else if (container is ContentPresenter p) + { + p.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Template); + if (ItemTemplate is { } it) + p.SetValue(ContentPresenter.ContentTemplateProperty, it, BindingPriority.Template); + } + + if (ItemContainerTheme is not null) + container.SetValue(ThemeProperty, ItemContainerTheme, BindingPriority.Template); + } + + /// + /// Undoes the effects of the method. + /// + /// The container element. + protected internal virtual void ClearContainerForItemOverride(Control container) + { + } + /// /// Gets the index of an item in a collection. /// @@ -273,60 +337,11 @@ namespace Avalonia.Controls } /// - /// Creates the for the control. + /// Determines whether the specified item is (or is eligible to be) its own container. /// - /// - /// An . - /// - protected virtual IItemContainerGenerator CreateItemContainerGenerator() - { - return new ItemContainerGenerator(this); - } - - /// - /// Called when new containers are materialized for the by its - /// . - /// - /// The details of the containers. - protected virtual void OnContainersMaterialized(ItemContainerEventArgs e) - { - foreach (var container in e.Containers) - { - // If the item is its own container, then it will be added to the logical tree when - // it was added to the Items collection. - if (container.ContainerControl != null && container.ContainerControl != container.Item) - { - LogicalChildren.Add(container.ContainerControl); - } - } - } - - /// - /// Called when containers are dematerialized for the by its - /// . - /// - /// The details of the containers. - protected virtual void OnContainersDematerialized(ItemContainerEventArgs e) - { - foreach (var container in e.Containers) - { - // If the item is its own container, then it will be removed from the logical tree - // when it is removed from the Items collection. - if (container.ContainerControl != container.Item) - { - LogicalChildren.Remove(container.ContainerControl); - } - } - } - - /// - /// Called when containers are recycled for the by its - /// . - /// - /// The details of the containers. - protected virtual void OnContainersRecycled(ItemContainerEventArgs e) - { - } + /// The item to check. + /// true if the item is (or is eligible to be) its own container; otherwise, false. + protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true; /// /// Handles directional navigation within the . @@ -387,7 +402,8 @@ namespace Avalonia.Controls } else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null) { - _itemContainerGenerator.ItemContainerTheme = change.GetNewValue(); + throw new NotImplementedException(); + ////_itemContainerGenerator.ItemContainerTheme = change.GetNewValue(); } } @@ -433,6 +449,27 @@ namespace Avalonia.Controls } } + internal void AddLogicalChild(Control c) => LogicalChildren.Add(c); + internal void RemoveLogicalChild(Control c) => LogicalChildren.Remove(c); + internal void ClearLogicalChildren() => LogicalChildren.Clear(); + + internal void PrepareItemContainer(Control container, object? item, int index) + { + var itemContainerTheme = ItemContainerTheme; + + if (itemContainerTheme is not null && + !container.IsSet(ThemeProperty) && + ((IStyleable)container).StyleKey == itemContainerTheme.TargetType) + { + container.Theme = itemContainerTheme; + } + + if (item is not Control) + container.DataContext = item; + + PrepareContainerForItemOverride(container, item, index); + } + /// /// Given a collection of items, adds those that are controls to the logical children. /// @@ -501,7 +538,7 @@ namespace Avalonia.Controls { if (_itemContainerGenerator != null) { - _itemContainerGenerator.ItemTemplate = (IDataTemplate?)e.NewValue; + ////_itemContainerGenerator.ItemTemplate = (IDataTemplate?)e.NewValue; // TODO: Rebuild the item containers. } } diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index a8a926691a..78a2d5d990 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -103,14 +103,7 @@ namespace Avalonia.Controls /// public void UnselectAll() => Selection.Clear(); - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new ItemContainerGenerator( - this, - ListBoxItem.ContentProperty, - ListBoxItem.ContentTemplateProperty); - } + protected internal override Control CreateContainerOverride() => new ListBoxItem(); /// protected override void OnGotFocus(GotFocusEventArgs e) diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs index bd7a2749c5..3b211d4cc7 100644 --- a/src/Avalonia.Controls/MenuBase.cs +++ b/src/Avalonia.Controls/MenuBase.cs @@ -99,9 +99,10 @@ namespace Avalonia.Controls { get { - return ItemContainerGenerator.Containers - .Select(x => x.ContainerControl) - .OfType(); + throw new NotImplementedException(); + ////return ItemContainerGenerator.Containers + //// .Select(x => x.ContainerControl) + //// .OfType(); } } @@ -141,12 +142,6 @@ namespace Avalonia.Controls /// bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new ItemContainerGenerator(this, MenuItem.HeaderProperty, null); - } - /// protected override void OnKeyDown(KeyEventArgs e) { diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 3e00cea430..d134704b26 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -332,9 +332,10 @@ namespace Avalonia.Controls { get { - return ItemContainerGenerator.Containers - .Select(x => x.ContainerControl) - .OfType(); + throw new NotImplementedException(); + ////return ItemContainerGenerator.Containers + //// .Select(x => x.ContainerControl) + //// .OfType(); } } @@ -357,12 +358,6 @@ namespace Avalonia.Controls /// void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent)); - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new MenuItemContainerGenerator(this); - } - protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index b731f48d9c..5f90c264d7 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; namespace Avalonia.Controls.Presenters @@ -13,7 +14,7 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty> ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); - private ItemsPresenterContainerGenerator? _generator; + private PanelContainerGenerator? _generator; /// /// Gets or sets a template which creates the used to display the items. @@ -52,7 +53,10 @@ namespace Avalonia.Controls.Presenters internal void ScrollIntoView(int index) { - + if (Panel is VirtualizingPanel v) + v.ScrollIntoView(index); + else if (index >= 0 && index < Panel?.Children.Count) + Panel.Children[index].BringIntoView(); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) @@ -97,5 +101,20 @@ namespace Avalonia.Controls.Presenters _generator?.Dispose(); _generator = new(this); } + + internal Control? ContainerFromIndex(int index) + { + if (Panel is VirtualizingPanel v) + return v.ContainerFromIndex(index); + return index >= 0 && index < Panel?.Children.Count ? Panel.Children[index] : null; + } + + internal int IndexFromContainer(Control container) + { + if (Panel is VirtualizingPanel v) + return v.IndexFromContainer(container); + return Panel?.Children.IndexOf(container) ?? -1; + } + } } diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterContainerGenerator.cs b/src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs similarity index 66% rename from src/Avalonia.Controls/Presenters/ItemsPresenterContainerGenerator.cs rename to src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs index 233635a414..f1765ba986 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterContainerGenerator.cs +++ b/src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs @@ -9,11 +9,11 @@ namespace Avalonia.Controls.Presenters /// /// Generates containers for s that have non-virtualizing panels. /// - internal class ItemsPresenterContainerGenerator : IDisposable + internal class PanelContainerGenerator : IDisposable { private readonly ItemsPresenter _presenter; - public ItemsPresenterContainerGenerator(ItemsPresenter presenter) + public PanelContainerGenerator(ItemsPresenter presenter) { Debug.Assert(presenter.ItemsControl is not null); Debug.Assert(presenter.Panel is not null or VirtualizingPanel); @@ -29,12 +29,17 @@ namespace Avalonia.Controls.Presenters public void Dispose() { - _presenter.ItemsControl!.PropertyChanged -= OnItemsControlPropertyChanged; + if (_presenter.ItemsControl is { } itemsControl) + { + itemsControl.PropertyChanged -= OnItemsControlPropertyChanged; - if (_presenter.ItemsControl.Items is INotifyCollectionChanged incc) - incc.CollectionChanged -= OnItemsChanged; + if (itemsControl.Items is INotifyCollectionChanged incc) + incc.CollectionChanged -= OnItemsChanged; - _presenter.Panel!.Children.Clear(); + itemsControl.ClearLogicalChildren(); + } + + _presenter.Panel?.Children.Clear(); } private void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) @@ -51,58 +56,68 @@ namespace Avalonia.Controls.Presenters private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (_presenter.ItemsControl?.Items is null || _presenter.Panel is null) + if (_presenter.Panel is null || _presenter.ItemsControl is null) return; - var generator = _presenter.ItemsControl.ItemContainerGenerator; + var itemsControl = _presenter.ItemsControl; var panel = _presenter.Panel; void Add(int index, IEnumerable items) { var i = index; - foreach (var item in items) - { - var c = generator.Materialize(i, item); - panel.Children.Insert(i++, c.ContainerControl); - } + panel.Children.Insert(i++, CreateContainer(itemsControl, item, i)); } void Remove(int index, int count) { for (var i = 0; i < count; ++i) + { + itemsControl.RemoveLogicalChild(panel.Children[i + index]); panel.Children.RemoveAt(i + index); + } } switch (e.Action) { case NotifyCollectionChangedAction.Add: - generator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); Add(e.NewStartingIndex, e.NewItems!); break; case NotifyCollectionChangedAction.Remove: - generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); Remove(e.OldStartingIndex, e.OldItems!.Count); break; case NotifyCollectionChangedAction.Replace: - generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); Remove(e.OldStartingIndex, e.OldItems!.Count); - generator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); Add(e.NewStartingIndex, e.NewItems!); break; case NotifyCollectionChangedAction.Move: - generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); Remove(e.OldStartingIndex, e.OldItems!.Count); - generator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); Add(e.NewStartingIndex, e.NewItems!); break; case NotifyCollectionChangedAction.Reset: - generator.Clear(); + itemsControl.ClearLogicalChildren(); panel.Children.Clear(); - if (_presenter.ItemsControl.Items is { } items) + if (_presenter.ItemsControl?.Items is { } items) Add(0, items); break; } } + + private static Control CreateContainer(ItemsControl itemsControl, object? item, int index) + { + var generator = itemsControl.ItemContainerGenerator; + + if (item is Control c && generator.IsItemItsOwnContainer(c)) + { + return c; + } + else + { + c = generator.CreateContainer(); + itemsControl.AddLogicalChild(c); + generator.PrepareItemContainer(c, item, index); + return c; + } + } } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 44fa78ac21..1503da1322 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using System.Xml.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; using Avalonia.Data; @@ -391,7 +392,7 @@ namespace Avalonia.Controls.Primitives for (var current = eventSource as Visual; current != null; current = current.VisualParent) { if (current is Control control && control.Parent == this && - ItemContainerGenerator?.IndexFromContainer(control) != -1) + IndexFromContainer(control) != -1) { return control; } @@ -432,54 +433,34 @@ namespace Avalonia.Controls.Primitives } } - /// - protected override void OnContainersMaterialized(ItemContainerEventArgs e) + protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) { - base.OnContainersMaterialized(e); + base.PrepareContainerForItemOverride(element, item, index); - foreach (var container in e.Containers) + if ((element as ISelectable)?.IsSelected == true) { - if ((container.ContainerControl as ISelectable)?.IsSelected == true) - { - Selection.Select(container.Index); - MarkContainerSelected(container.ContainerControl, true); - } - else - { - var selected = Selection.IsSelected(container.Index); - MarkContainerSelected(container.ContainerControl, selected); - } + Selection.Select(index); + MarkContainerSelected(element, true); } - } - - /// - protected override void OnContainersDematerialized(ItemContainerEventArgs e) - { - base.OnContainersDematerialized(e); - - if (Presenter?.Panel is InputElement panel) + else { - foreach (var container in e.Containers) - { - if (KeyboardNavigation.GetTabOnceActiveElement(panel) == container.ContainerControl) - { - KeyboardNavigation.SetTabOnceActiveElement(panel, null); - break; - } - } + var selected = Selection.IsSelected(index); + MarkContainerSelected(element, selected); } } - protected override void OnContainersRecycled(ItemContainerEventArgs e) + protected internal override void ClearContainerForItemOverride(Control element) { - foreach (var i in e.Containers) + base.ClearContainerForItemOverride(element); + + if (Presenter?.Panel is InputElement panel && + KeyboardNavigation.GetTabOnceActiveElement(panel) == element) { - if (i.ContainerControl != null && i.Item != null) - { - bool selected = Selection.IsSelected(i.Index); - MarkContainerSelected(i.ContainerControl, selected); - } + KeyboardNavigation.SetTabOnceActiveElement(panel, null); } + + if (element is ISelectable selectable) + MarkContainerSelected(element, false); } /// @@ -526,44 +507,45 @@ namespace Avalonia.Controls.Primitives protected override void OnTextInput(TextInputEventArgs e) { - if (!e.Handled) - { - if (!IsTextSearchEnabled) - return; - - StopTextSearchTimer(); - - _textSearchTerm += e.Text; - - bool Match(ItemContainerInfo info) - { - if (info.ContainerControl is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty)) - { - var searchText = ao.GetValue(TextSearch.TextProperty); - - if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true) - { - return true; - } - } - - return info.ContainerControl is IContentControl control && - control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; - } + throw new NotImplementedException(); + ////if (!e.Handled) + ////{ + //// if (!IsTextSearchEnabled) + //// return; + + //// StopTextSearchTimer(); + + //// _textSearchTerm += e.Text; + + //// bool Match(ItemContainerInfo info) + //// { + //// if (info.ContainerControl is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty)) + //// { + //// var searchText = ao.GetValue(TextSearch.TextProperty); + + //// if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true) + //// { + //// return true; + //// } + //// } + + //// return info.ContainerControl is IContentControl control && + //// control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; + //// } - var info = ItemContainerGenerator?.Containers.FirstOrDefault(Match); + //// var info = ItemContainerGenerator?.Containers.FirstOrDefault(Match); - if (info != null) - { - SelectedIndex = info.Index; - } + //// if (info != null) + //// { + //// SelectedIndex = info.Index; + //// } - StartTextSearchTimer(); + //// StartTextSearchTimer(); - e.Handled = true; - } + //// e.Handled = true; + ////} - base.OnTextInput(e); + ////base.OnTextInput(e); } protected override void OnKeyDown(KeyEventArgs e) @@ -634,7 +616,7 @@ namespace Avalonia.Controls.Primitives /// True if the selection was moved; otherwise false. protected bool MoveSelection(NavigationDirection direction, bool wrap) { - var from = SelectedIndex != -1 ? ItemContainerGenerator?.ContainerFromIndex(SelectedIndex) : null; + var from = SelectedIndex != -1 ? ContainerFromIndex(SelectedIndex) : null; return MoveSelection(from, direction, wrap); } @@ -650,7 +632,7 @@ namespace Avalonia.Controls.Primitives if (Presenter?.Panel is INavigableContainer container && GetNextControl(container, direction, from, wrap) is Control next) { - var index = ItemContainerGenerator?.IndexFromContainer(next) ?? -1; + var index = IndexFromContainer(next); if (index != -1) { @@ -733,7 +715,7 @@ namespace Avalonia.Controls.Primitives if (Presenter?.Panel != null) { - var container = ItemContainerGenerator?.ContainerFromIndex(index); + var container = ContainerFromIndex(index); KeyboardNavigation.SetTabOnceActiveElement( (InputElement)Presenter.Panel, container); @@ -757,7 +739,7 @@ namespace Avalonia.Controls.Primitives bool rightButton = false, bool fromFocus = false) { - var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1; + var index = IndexFromContainer(container); if (index != -1) { @@ -842,7 +824,7 @@ namespace Avalonia.Controls.Primitives { void Mark(int index, bool selected) { - var container = ItemContainerGenerator?.ContainerFromIndex(index); + var container = ContainerFromIndex(index); if (container != null) { @@ -909,7 +891,7 @@ namespace Avalonia.Controls.Primitives e.Source is Control control && e.Source is ISelectable selectable && control.Parent == this && - ItemContainerGenerator?.IndexFromContainer(control) != -1) + IndexFromContainer(control) != -1) { UpdateSelection(control, selectable.IsSelected); } @@ -961,7 +943,7 @@ namespace Avalonia.Controls.Primitives { MarkContainerSelected( container, - Selection.IsSelected(ItemContainerGenerator.IndexFromContainer(container))); + Selection.IsSelected(IndexFromContainer(container))); } } } diff --git a/src/Avalonia.Controls/Primitives/TabStrip.cs b/src/Avalonia.Controls/Primitives/TabStrip.cs index d27b1faaed..a6a6f4cec8 100644 --- a/src/Avalonia.Controls/Primitives/TabStrip.cs +++ b/src/Avalonia.Controls/Primitives/TabStrip.cs @@ -18,14 +18,6 @@ namespace Avalonia.Controls.Primitives ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); } - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new ItemContainerGenerator( - this, - ContentControl.ContentProperty, - ContentControl.ContentTemplateProperty); - } - /// protected override void OnGotFocus(GotFocusEventArgs e) { diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 2e7059b838..8ce1befde5 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -165,15 +165,15 @@ namespace Avalonia.Controls return RegisterContentPresenter(presenter); } - protected override void OnContainersMaterialized(ItemContainerEventArgs e) + protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) { - base.OnContainersMaterialized(e); + base.PrepareContainerForItemOverride(element, item, index); UpdateSelectedContent(); } - protected override void OnContainersRecycled(ItemContainerEventArgs e) + protected internal override void ClearContainerForItemOverride(Control element) { - base.OnContainersRecycled(e); + base.ClearContainerForItemOverride(element); UpdateSelectedContent(); } @@ -207,11 +207,6 @@ namespace Avalonia.Controls return false; } - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new TabItemContainerGenerator(this); - } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter"); diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 2fa4a02fa2..6336219fb6 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -75,12 +75,6 @@ namespace Avalonia.Controls remove => RemoveHandler(SelectingItemsControl.SelectionChangedEvent, value); } - /// - /// 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. /// @@ -188,7 +182,8 @@ namespace Avalonia.Controls /// public void SelectAll() { - SynchronizeItems(SelectedItems, ItemContainerGenerator.Index!.Items); + throw new NotImplementedException(); + ////SynchronizeItems(SelectedItems, ItemContainerGenerator.Index!.Items); } /// @@ -242,7 +237,7 @@ namespace Avalonia.Controls if (AutoScrollToSelectedItem) { - var container = ItemContainerGenerator.Index!.ContainerFromItem(e.NewItems![0]!); + var container = ContainerFromItem(e.NewItems![0]!); container?.BringIntoView(); } @@ -282,9 +277,10 @@ namespace Avalonia.Controls break; case NotifyCollectionChangedAction.Reset: - foreach (Control container in ItemContainerGenerator.Index!.Containers) + foreach (var child in LogicalChildren) { - MarkContainerSelected(container, false); + if (child is Control container && IndexFromContainer(container) != -1) + MarkContainerSelected(container, false); } if (SelectedItems.Count > 0) @@ -337,7 +333,7 @@ namespace Avalonia.Controls private void MarkItemSelected(object item, bool selected) { - var container = ItemContainerGenerator.Index!.ContainerFromItem(item)!; + var container = ContainerFromItem(item)!; MarkContainerSelected(container, selected); } @@ -378,8 +374,8 @@ namespace Avalonia.Controls if (!this.IsVisualAncestorOf((Visual)element)) { var result = _selectedItem != null ? - ItemContainerGenerator.Index!.ContainerFromItem(_selectedItem) : - ItemContainerGenerator.ContainerFromIndex(0); + ContainerFromItem(_selectedItem) : + ContainerFromIndex(0); return (result != null, result); // SelectedItem may not be in the treeview. } @@ -390,27 +386,6 @@ namespace Avalonia.Controls return (false, null); } - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - var result = CreateTreeItemContainerGenerator(); - result.Index!.Materialized += ContainerMaterialized; - return result; - } - - protected virtual ITreeItemContainerGenerator CreateTreeItemContainerGenerator() => - CreateTreeItemContainerGenerator(); - - protected ITreeItemContainerGenerator CreateTreeItemContainerGenerator() where TVItem: TreeViewItem, new() - { - return new TreeItemContainerGenerator( - this, - TreeViewItem.HeaderProperty, - TreeViewItem.ItemTemplateProperty, - TreeViewItem.ItemsProperty, - TreeViewItem.IsExpandedProperty); - } - /// protected override void OnGotFocus(GotFocusEventArgs e) { @@ -466,53 +441,54 @@ namespace Avalonia.Controls NavigationDirection direction, bool intoChildren) { - IItemContainerGenerator? parentGenerator = GetParentContainerGenerator(from); - - if (parentGenerator == null) - { - return null; - } - - var index = from is not null ? parentGenerator.IndexFromContainer(from) : -1; - var parent = from?.Parent as ItemsControl; - TreeViewItem? result = null; - - switch (direction) - { - case NavigationDirection.Up: - if (index > 0) - { - var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1)!; - result = previous.IsExpanded && previous.ItemCount > 0 ? - (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1)! : - previous; - } - else - { - result = from?.Parent as TreeViewItem; - } - - break; - - case NavigationDirection.Down: - case NavigationDirection.Right: - if (from?.IsExpanded == true && intoChildren && from.ItemCount > 0) - { - result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0)!; - } - else if (index < parent?.ItemCount - 1) - { - result = (TreeViewItem)parentGenerator.ContainerFromIndex(index + 1)!; - } - else if (parent is TreeViewItem parentItem) - { - return GetContainerInDirection(parentItem, direction, false); - } - - break; - } - - return result; + throw new NotImplementedException(); + ////IItemContainerGenerator? parentGenerator = GetParentContainerGenerator(from); + + ////if (parentGenerator == null) + ////{ + //// return null; + ////} + + ////var index = from is not null ? parentGenerator.IndexFromContainer(from) : -1; + ////var parent = from?.Parent as ItemsControl; + ////TreeViewItem? result = null; + + ////switch (direction) + ////{ + //// case NavigationDirection.Up: + //// if (index > 0) + //// { + //// var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1)!; + //// result = previous.IsExpanded && previous.ItemCount > 0 ? + //// (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1)! : + //// previous; + //// } + //// else + //// { + //// result = from?.Parent as TreeViewItem; + //// } + + //// break; + + //// case NavigationDirection.Down: + //// case NavigationDirection.Right: + //// if (from?.IsExpanded == true && intoChildren && from.ItemCount > 0) + //// { + //// result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0)!; + //// } + //// else if (index < parent?.ItemCount - 1) + //// { + //// result = (TreeViewItem)parentGenerator.ContainerFromIndex(index + 1)!; + //// } + //// else if (parent is TreeViewItem parentItem) + //// { + //// return GetContainerInDirection(parentItem, direction, false); + //// } + + //// break; + ////} + + ////return result; } /// @@ -551,80 +527,63 @@ namespace Avalonia.Controls bool toggleModifier = false, bool rightButton = false) { - var item = ItemContainerGenerator.Index!.ItemFromContainer(container); - - if (item == null) - { - return; - } - - Control? selectedContainer = null; - - if (SelectedItem != null) - { - selectedContainer = ItemContainerGenerator.Index!.ContainerFromItem(SelectedItem); - } - - var mode = SelectionMode; - var toggle = toggleModifier || mode.HasAllFlags(SelectionMode.Toggle); - var multi = mode.HasAllFlags(SelectionMode.Multiple); - var range = multi && rangeModifier && selectedContainer != null; - - if (rightButton) - { - if (!SelectedItems.Contains(item)) - { - SelectSingleItem(item); - } - } - else if (!toggle && !range) - { - SelectSingleItem(item); - } - else if (multi && range) - { - SynchronizeItems( - SelectedItems, - GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem)); - } - else - { - var i = SelectedItems.IndexOf(item); - - if (i != -1) - { - SelectedItems.Remove(item); - } - else - { - if (multi) - { - SelectedItems.Add(item); - } - else - { - SelectedItem = item; - } - } - } - } - - private static IItemContainerGenerator? GetParentContainerGenerator(TreeViewItem? item) - { - if (item == null) - { - return null; - } - - switch (item.Parent) - { - case TreeView treeView: - return treeView.ItemContainerGenerator; - case TreeViewItem treeViewItem: - return treeViewItem.ItemContainerGenerator; - default: - return null; - } + throw new NotImplementedException(); + ////var item = ItemContainerGenerator.Index!.ItemFromContainer(container); + + ////if (item == null) + ////{ + //// return; + ////} + + ////Control? selectedContainer = null; + + ////if (SelectedItem != null) + ////{ + //// selectedContainer = ItemContainerGenerator.Index!.ContainerFromItem(SelectedItem); + ////} + + ////var mode = SelectionMode; + ////var toggle = toggleModifier || mode.HasAllFlags(SelectionMode.Toggle); + ////var multi = mode.HasAllFlags(SelectionMode.Multiple); + ////var range = multi && rangeModifier && selectedContainer != null; + + ////if (rightButton) + ////{ + //// if (!SelectedItems.Contains(item)) + //// { + //// SelectSingleItem(item); + //// } + ////} + ////else if (!toggle && !range) + ////{ + //// SelectSingleItem(item); + ////} + ////else if (multi && range) + ////{ + //// SynchronizeItems( + //// SelectedItems, + //// GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem)); + ////} + ////else + ////{ + //// var i = SelectedItems.IndexOf(item); + + //// if (i != -1) + //// { + //// SelectedItems.Remove(item); + //// } + //// else + //// { + //// if (multi) + //// { + //// SelectedItems.Add(item); + //// } + //// else + //// { + //// SelectedItem = item; + //// } + //// } + ////} } /// @@ -639,23 +598,24 @@ namespace Avalonia.Controls return FindInContainers(treeView.ItemContainerGenerator, nodeA, nodeB); } - private static TreeViewItem? FindInContainers(ITreeItemContainerGenerator containerGenerator, + private static TreeViewItem? FindInContainers(ItemContainerGenerator containerGenerator, TreeViewItem nodeA, TreeViewItem nodeB) { - IEnumerable containers = containerGenerator.Containers; + throw new NotImplementedException(); + ////IEnumerable containers = containerGenerator.Containers; - foreach (ItemContainerInfo container in containers) - { - TreeViewItem? node = FindFirstNode(container.ContainerControl as TreeViewItem, nodeA, nodeB); + ////foreach (ItemContainerInfo container in containers) + ////{ + //// TreeViewItem? node = FindFirstNode(container.ContainerControl as TreeViewItem, nodeA, nodeB); - if (node != null) - { - return node; - } - } + //// if (node != null) + //// { + //// return node; + //// } + ////} - return null; + ////return null; } private static TreeViewItem? FindFirstNode(TreeViewItem? node, TreeViewItem nodeA, TreeViewItem nodeB) @@ -683,59 +643,60 @@ namespace Avalonia.Controls /// To container. private List GetItemsInRange(TreeViewItem? from, TreeViewItem? to) { - var items = new List(); + throw new NotImplementedException(); + ////var items = new List(); - if (from == null || to == null) - { - return items; - } + ////if (from == null || to == null) + ////{ + //// return items; + ////} - TreeViewItem? firstItem = FindFirstNode(this, from, to); + ////TreeViewItem? firstItem = FindFirstNode(this, from, to); - if (firstItem == null) - { - return items; - } + ////if (firstItem == null) + ////{ + //// return items; + ////} - bool wasReversed = false; + ////bool wasReversed = false; - if (firstItem == to) - { - var temp = from; + ////if (firstItem == to) + ////{ + //// var temp = from; - from = to; - to = temp; + //// from = to; + //// to = temp; - wasReversed = true; - } + //// wasReversed = true; + ////} - TreeViewItem? node = from; + ////TreeViewItem? node = from; - while (node != to) - { - var item = ItemContainerGenerator.Index!.ItemFromContainer(node); + ////while (node != to) + ////{ + //// var item = ItemContainerGenerator.Index!.ItemFromContainer(node); - if (item != null) - { - items.Add(item); - } + //// if (item != null) + //// { + //// items.Add(item); + //// } - node = GetContainerInDirection(node, NavigationDirection.Down, true); - } + //// node = GetContainerInDirection(node, NavigationDirection.Down, true); + ////} - var toItem = ItemContainerGenerator.Index!.ItemFromContainer(to); + ////var toItem = ItemContainerGenerator.Index!.ItemFromContainer(to); - if (toItem != null) - { - items.Add(toItem); - } + ////if (toItem != null) + ////{ + //// items.Add(toItem); + ////} - if (wasReversed) - { - items.Reverse(); - } + ////if (wasReversed) + ////{ + //// items.Reverse(); + ////} - return items; + ////return items; } /// @@ -776,19 +737,20 @@ namespace Avalonia.Controls /// The container or null if the event did not originate in a container. protected TreeViewItem? GetContainerFromEventSource(object eventSource) { - var item = ((Visual)eventSource).GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(); + throw new NotImplementedException(); + ////var item = ((Visual)eventSource).GetSelfAndVisualAncestors() + //// .OfType() + //// .FirstOrDefault(); - if (item != null) - { - if (item.ItemContainerGenerator.Index == ItemContainerGenerator.Index) - { - return item; - } - } + ////if (item != null) + ////{ + //// if (item.ItemContainerGenerator.Index == ItemContainerGenerator.Index) + //// { + //// return item; + //// } + ////} - return null; + ////return null; } /// @@ -796,30 +758,30 @@ namespace Avalonia.Controls /// /// The event sender. /// The event args. - private void ContainerMaterialized(object? sender, ItemContainerEventArgs e) - { - var selectedItem = SelectedItem; - - if (selectedItem == null) - { - return; - } - - foreach (var container in e.Containers) - { - if (container.Item == selectedItem) - { - ((TreeViewItem)container.ContainerControl).IsSelected = true; - - if (AutoScrollToSelectedItem) - { - Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView); - } - - break; - } - } - } + ////private void ContainerMaterialized(object? sender, ItemContainerEventArgs e) + ////{ + //// var selectedItem = SelectedItem; + + //// if (selectedItem == null) + //// { + //// return; + //// } + + //// foreach (var container in e.Containers) + //// { + //// if (container.Item == selectedItem) + //// { + //// ((TreeViewItem)container.ContainerControl).IsSelected = true; + + //// if (AutoScrollToSelectedItem) + //// { + //// Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView); + //// } + + //// break; + //// } + //// } + ////} /// /// Sets a container's 'selected' class or . diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 9bfcf5adfa..097fe54f42 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -58,7 +58,6 @@ namespace Avalonia.Controls PressedMixin.Attach(); FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); - ParentProperty.Changed.AddClassHandler((o, e) => o.OnParentChanged(e)); RequestBringIntoViewEvent.AddClassHandler((x, e) => x.OnRequestBringIntoView(e)); } @@ -89,27 +88,6 @@ namespace Avalonia.Controls private set { SetAndRaise(LevelProperty, ref _level, value); } } - /// - /// Gets the for the tree view. - /// - public new ITreeItemContainerGenerator ItemContainerGenerator => - (ITreeItemContainerGenerator)base.ItemContainerGenerator; - - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() => CreateTreeItemContainerGenerator(); - - /// - protected ITreeItemContainerGenerator CreateTreeItemContainerGenerator() - where TVItem: TreeViewItem, new() - { - return new TreeItemContainerGenerator( - this, - TreeViewItem.HeaderProperty, - TreeViewItem.ItemTemplateProperty, - TreeViewItem.ItemsProperty, - TreeViewItem.IsExpandedProperty); - } - /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -118,7 +96,6 @@ namespace Avalonia.Controls _treeView = this.GetLogicalAncestors().OfType().FirstOrDefault(); Level = CalculateDistanceFromLogicalParent(this) - 1; - ItemContainerGenerator.UpdateIndex(); if (ItemTemplate == null && _treeView?.ItemTemplate != null) { @@ -134,7 +111,6 @@ namespace Avalonia.Controls protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); - ItemContainerGenerator.UpdateIndex(); } protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) @@ -219,16 +195,5 @@ namespace Avalonia.Controls 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(); - } - } } } diff --git a/src/Avalonia.Controls/VirtualizingPanel.cs b/src/Avalonia.Controls/VirtualizingPanel.cs index 19b1c4002c..dd40f868d5 100644 --- a/src/Avalonia.Controls/VirtualizingPanel.cs +++ b/src/Avalonia.Controls/VirtualizingPanel.cs @@ -1,5 +1,7 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Utils; namespace Avalonia.Controls @@ -25,6 +27,32 @@ namespace Avalonia.Controls } } + /// + /// Scrolls the specified item into view. + /// + /// The index of the item. + protected internal abstract void ScrollIntoView(int index); + + /// + /// Returns the container for the item at the specified index. + /// + /// The index of the item to retrieve. + /// + /// The container for the item at the specified index within the item collection, if the + /// item is realized; otherwise, null. + /// + protected internal abstract Control? ContainerFromIndex(int index); + + /// + /// Returns the index to the item that has the specified realized container. + /// + /// The generated container to retrieve the item index for. + /// + /// The index to the item that corresponds to the specified realized container, or -1 if + /// is not found. + /// + protected internal abstract int IndexFromContainer(Control container); + /// /// Called when the that owns the panel changes. /// @@ -51,8 +79,59 @@ namespace Avalonia.Controls { } + /// + /// Adds the specified to the collection + /// of a element. + /// + /// The control to add to the collection. + protected void AddInternalChild(Control control) + { + var itemsControl = EnsureItemsControl(); + itemsControl.AddLogicalChild(control); + Children.Add(control); + } + + /// + /// Adds the specified to the collection + /// of a element at the specified index position. + /// + /// + /// The index position within the collection at which the child element is inserted. + /// + /// The control to add to the collection. + protected void InsertInternalChild(int index, Control control) + { + var itemsControl = EnsureItemsControl(); + itemsControl.AddLogicalChild(control); + Children.Insert(index, control); + } + + /// + /// Removes child elements from the collection. + /// + /// + /// The beginning index position within the collection at which the first child element is + /// removed. + /// + /// The number of child elements to remove. + protected void RemoveInternalChildRange(int index, int count) + { + var itemsControl = EnsureItemsControl(); + + for (var i = 0; i < count; ++i) + { + var c = Children[i]; + itemsControl.RemoveLogicalChild(c); + } + + Children.RemoveRange(index, count); + } + internal void Attach(ItemsControl itemsControl) { + if (ItemsControl is not null) + throw new InvalidOperationException("The VirtualizingPanel is already attached to an ItemsControl"); + ItemsControl = itemsControl; ItemsControl.PropertyChanged += OnItemsControlPropertyChanged; @@ -62,18 +141,24 @@ namespace Avalonia.Controls internal void Detach() { - if (ItemsControl is null) - return; + var itemsControl = EnsureItemsControl(); - ItemsControl.PropertyChanged -= OnItemsControlPropertyChanged; + itemsControl.PropertyChanged -= OnItemsControlPropertyChanged; - if (ItemsControl.Items is INotifyCollectionChanged incc) + if (itemsControl.Items is INotifyCollectionChanged incc) incc.CollectionChanged -= OnItemsControlItemsChanged; ItemsControl = null; Children.Clear(); } + private ItemsControl EnsureItemsControl() + { + if (ItemsControl is null) + ThrowNotAttached(); + return ItemsControl; + } + private protected virtual void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (e.Property == ItemsControl.ItemsProperty) @@ -91,5 +176,11 @@ namespace Avalonia.Controls if (_itemsControl?.Items is IList items) OnItemsChanged(items, e); } + + [DoesNotReturn] + private static void ThrowNotAttached() + { + throw new InvalidOperationException("The VirtualizingPanel does not belong to an ItemsControl."); + } } } diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 5d9b3b56b1..a4e287dc08 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Xml.Linq; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Utilities; @@ -18,16 +19,21 @@ namespace Avalonia.Controls public static readonly StyledProperty OrientationProperty = StackLayout.OrientationProperty.AddOwner(); + private static readonly AttachedProperty ItemIsOwnContainerProperty = + AvaloniaProperty.RegisterAttached("ItemIsOwnContainer"); + private static readonly Rect s_invalidViewport = new(double.PositiveInfinity, double.PositiveInfinity, 0, 0); private readonly Action _recycleElement; private readonly Action _updateElementIndex; private int _anchorIndex = -1; private Control? _anchorElement; + private bool _isInLayout; private bool _isWaitingForViewportUpdate; private double _lastEstimatedElementSizeU = 25; private RealizedElementList? _measureElements; private RealizedElementList? _realizedElements; private Rect _viewport = s_invalidViewport; + private Stack? _recyclePool; public VirtualizingStackPanel() { @@ -54,73 +60,92 @@ namespace Avalonia.Controls if (!IsEffectivelyVisible) return default; - var items = ItemsControl?.Items as IList; + _isInLayout = true; - if (items is null || items.Count == 0) + try { - Children.Clear(); - return default; - } + var items = ItemsControl?.Items as IList; + + if (items is null || items.Count == 0) + { + RemoveInternalChildRange(0, Children.Count); + return default; + } - var orientation = Orientation; + var orientation = Orientation; - _realizedElements ??= new(); - _measureElements ??= new(); + _realizedElements ??= new(); + _measureElements ??= new(); - // If we're bringing an item into view, ignore any layout passes until we receive a new - // effective viewport. - if (_isWaitingForViewportUpdate) - { - var sizeV = orientation == Orientation.Horizontal ? DesiredSize.Height : DesiredSize.Width; - return CalculateDesiredSize(orientation, items, sizeV); - } + // If we're bringing an item into view, ignore any layout passes until we receive a new + // effective viewport. + if (_isWaitingForViewportUpdate) + { + var sizeV = orientation == Orientation.Horizontal ? DesiredSize.Height : DesiredSize.Width; + return CalculateDesiredSize(orientation, items, sizeV); + } - // We handle horizontal and vertical layouts here so X and Y are abstracted to: - // - Horizontal layouts: U = horizontal, V = vertical - // - Vertical layouts: U = vertical, V = horizontal - var viewport = CalculateMeasureViewport(items); + // We handle horizontal and vertical layouts here so X and Y are abstracted to: + // - Horizontal layouts: U = horizontal, V = vertical + // - Vertical layouts: U = vertical, V = horizontal + var viewport = CalculateMeasureViewport(items); - // Recycle elements outside of the expected range. - _realizedElements.RecycleElementsBefore(viewport.firstIndex, _recycleElement); - _realizedElements.RecycleElementsAfter(viewport.estimatedLastIndex, _recycleElement); + // Recycle elements outside of the expected range. + _realizedElements.RecycleElementsBefore(viewport.firstIndex, _recycleElement); + _realizedElements.RecycleElementsAfter(viewport.estimatedLastIndex, _recycleElement); - // Do the measure, creating/recycling elements as necessary to fill the viewport. Don't - // write to _realizedElements yet, only _measureElements. - GenerateElements(availableSize, ref viewport); + // Do the measure, creating/recycling elements as necessary to fill the viewport. Don't + // write to _realizedElements yet, only _measureElements. + GenerateElements(availableSize, ref viewport); - // Now we know what definitely fits, recycle anything left over. - _realizedElements.RecycleElementsAfter(_measureElements.LastModelIndex, _recycleElement); + // Now we know what definitely fits, recycle anything left over. + _realizedElements.RecycleElementsAfter(_measureElements.LastModelIndex, _recycleElement); - // And swap the measureElements and realizedElements collection. - (_measureElements, _realizedElements) = (_realizedElements, _measureElements); - _measureElements.ResetForReuse(); + // And swap the measureElements and realizedElements collection. + (_measureElements, _realizedElements) = (_realizedElements, _measureElements); + _measureElements.ResetForReuse(); - return CalculateDesiredSize(orientation, items, viewport.measuredV); + return CalculateDesiredSize(orientation, items, viewport.measuredV); + } + finally + { + _isInLayout = false; + } } protected override Size ArrangeOverride(Size finalSize) { - Debug.Assert(_realizedElements is not null); + if (_realizedElements is null) + return default; - var orientation = Orientation; - var u = _realizedElements!.StartU; + _isInLayout = true; - for (var i = 0; i < _realizedElements.Count; ++i) + try { - var e = _realizedElements.Elements[i]; + var orientation = Orientation; + var u = _realizedElements!.StartU; - if (e is object) + for (var i = 0; i < _realizedElements.Count; ++i) { - var sizeU = _realizedElements.SizeU[i]; - var rect = orientation == Orientation.Horizontal ? - new Rect(u, 0, sizeU, finalSize.Height) : - new Rect(0, u, finalSize.Width, sizeU); - e.Arrange(rect); - u += orientation == Orientation.Horizontal ? rect.Width : rect.Height; + var e = _realizedElements.Elements[i]; + + if (e is object) + { + var sizeU = _realizedElements.SizeU[i]; + var rect = orientation == Orientation.Horizontal ? + new Rect(u, 0, sizeU, finalSize.Height) : + new Rect(0, u, finalSize.Width, sizeU); + e.Arrange(rect); + u += orientation == Orientation.Horizontal ? rect.Width : rect.Height; + } } - } - return finalSize; + return finalSize; + } + finally + { + _isInLayout = false; + } } protected override void OnItemsChanged(IList items, NotifyCollectionChangedEventArgs e) @@ -132,20 +157,70 @@ namespace Avalonia.Controls { case NotifyCollectionChangedAction.Add: _realizedElements.ItemsInserted(e.NewStartingIndex, e.NewItems!.Count, _updateElementIndex); - ItemsControl!.ItemContainerGenerator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); break; case NotifyCollectionChangedAction.Remove: _realizedElements.ItemsRemoved(e.OldStartingIndex, e.OldItems!.Count, _updateElementIndex, _recycleElement); - ItemsControl!.ItemContainerGenerator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); break; case NotifyCollectionChangedAction.Reset: - ////RecycleAllElements(); + _realizedElements.RecycleAllElements(_recycleElement); break; } InvalidateMeasure(); } + protected internal override Control? ContainerFromIndex(int index) => _realizedElements?.GetElement(index); + protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1; + + protected internal override void ScrollIntoView(int index) + { + var items = ItemsControl?.Items as IList; + + if (_isInLayout || items is null || index < 0 || index >= items.Count) + return; + + if (GetRealizedElement(index) is Control element) + { + element.BringIntoView(); + } + else if (this.GetVisualRoot() is ILayoutRoot root) + { + // Create and measure the element to be brought into view. Store it in a field so that + // it can be re-used in the layout pass. + _anchorElement = GetOrCreateElement(items, index); + _anchorElement.Measure(Size.Infinity); + _anchorIndex = index; + + // Get the expected position of the elment and put it in place. + var anchorU = GetOrEstimateElementPosition(index); + var rect = Orientation == Orientation.Horizontal ? + new Rect(anchorU, 0, _anchorElement.DesiredSize.Width, _anchorElement.DesiredSize.Height) : + new Rect(0, anchorU, _anchorElement.DesiredSize.Width, _anchorElement.DesiredSize.Height); + _anchorElement.Arrange(rect); + + // If the item being brought into view was added since the last layout pass then + // our bounds won't be updated, so any containing scroll viewers will not have an + // updated extent. Do a layout pass to ensure that the containing scroll viewers + // will be able to scroll the new item into view. + if (!Bounds.Contains(rect) && !_viewport.Contains(rect)) + { + _isWaitingForViewportUpdate = true; + root.LayoutManager.ExecuteLayoutPass(); + _isWaitingForViewportUpdate = false; + } + + // Try to bring the item into view and do a layout pass. + _anchorElement.BringIntoView(); + + _isWaitingForViewportUpdate = !_viewport.Contains(rect); + root.LayoutManager.ExecuteLayoutPass(); + _isWaitingForViewportUpdate = false; + + _anchorElement = null; + _anchorIndex = -1; + } + } + internal IReadOnlyList GetRealizedElements() { return _realizedElements?.Elements ?? Array.Empty(); @@ -206,7 +281,10 @@ namespace Avalonia.Controls private double EstimateElementSizeU() { - var count = _realizedElements!.Count; + if (_realizedElements is null) + return _lastEstimatedElementSizeU; + + var count = _realizedElements.Count; var divisor = 0.0; var total = 0.0; @@ -280,36 +358,89 @@ namespace Avalonia.Controls private Control GetOrCreateElement(IList items, int index) { - var e = GetRealizedElement(index) ?? GetRecycledOrCreateElement(items, index); + var e = GetRealizedElement(index) ?? + GetRecycledElement(items, index) ?? + CreateElement(items, index); InvalidateHack(e); return e; } private Control? GetRealizedElement(int index) { - Debug.Assert(_realizedElements is not null); - if (_anchorIndex == index) return _anchorElement; - return _realizedElements!.GetElement(index); + return _realizedElements?.GetElement(index); } - private Control GetRecycledOrCreateElement(IList items, int index) + private Control? GetRecycledElement(IList items, int index) { Debug.Assert(ItemsControl is not null); - var c = ItemsControl!.ItemContainerGenerator.Materialize(index, items[index]!).ContainerControl; - Children.Add(c); - return c; + var generator = ItemsControl!.ItemContainerGenerator; + var item = items[index]; + + if (item is Control controlItem) + { + if (controlItem.IsSet(ItemIsOwnContainerProperty)) + { + controlItem.IsVisible = true; + return controlItem; + } + else if (generator.IsItemItsOwnContainer(controlItem)) + { + generator.PrepareItemContainer(controlItem, item, index); + controlItem.SetValue(ItemIsOwnContainerProperty, true); + AddInternalChild(controlItem); + return controlItem; + } + } + + if (_recyclePool?.Count > 0) + { + var recycled = _recyclePool.Pop(); + recycled.IsVisible = true; + generator.PrepareItemContainer(recycled, item, index); + return recycled; + } + + return null; } - private void RecycleElement(Control element) + private Control CreateElement(IList items, int index) { Debug.Assert(ItemsControl is not null); - var index = ItemsControl.ItemContainerGenerator!.IndexFromContainer(element); - ItemsControl!.ItemContainerGenerator.Dematerialize(index, 1); - Children.Remove(element); + var generator = ItemsControl!.ItemContainerGenerator; + var item = items[index]; + var container = generator.CreateContainer(); + + AddInternalChild(container); + generator.PrepareItemContainer(container, item, index); + + return container; + } + + private double GetOrEstimateElementPosition(int index) + { + var estimatedElementSize = EstimateElementSizeU(); + return index * estimatedElementSize; + } + + private void RecycleElement(Control element) + { + Debug.Assert(ItemsControl is not null); + + if (element.IsSet(ItemIsOwnContainerProperty)) + { + element.IsVisible = false; + } + else + { + ItemsControl!.ItemContainerGenerator.ClearItemContainer(element); + _recyclePool ??= new(); + _recyclePool.Push(element); + element.IsVisible = false; + } } private void UpdateElementIndex(Control element, int index) @@ -478,6 +609,43 @@ namespace Avalonia.Controls return (-1, 0); } + /// + /// Gets the position of an element on the primary axis, if realized. + /// + /// The index in the source collection of the element. + /// + /// When the method exits, contains the element's position on the primary axis, if + /// the element is realized. + /// + /// + /// True if the requested element was found, otherwise false. + /// + public bool TryGetElementU(int modelIndex, out double position) + { + if (_sizes is null || modelIndex < FirstModelIndex || modelIndex > LastModelIndex) + { + position = double.NaN; + return false; + } + + var index = modelIndex - FirstModelIndex; + position = StartU; + + for (var i = 0; i < index; ++i) + { + position += _sizes[i]; + } + + return true; + } + + /// + /// Gets the model index of the specified element. + /// + /// The element. + /// The model index or -1 if the element is not present in the collection. + public int GetIndex(Control element) => _elements?.IndexOf(element) is int index && index >= 0 ? index : -1; + /// /// Updates the elements in response to items being inserted into the source collection. /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 587898ac6e..d8feb579f0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -21,7 +21,7 @@ namespace Avalonia.Diagnostics.Views { InitializeComponent(); _tree = this.GetControl("tree"); - _tree.ItemContainerGenerator.Index!.Materialized += TreeViewItemMaterialized; + ////_tree.ItemContainerGenerator.Index!.Materialized += TreeViewItemMaterialized; _adorner = new Panel { @@ -103,11 +103,11 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void TreeViewItemMaterialized(object? sender, ItemContainerEventArgs e) - { - var item = (TreeViewItem)e.Containers[0].ContainerControl; - item.TemplateApplied += TreeViewItemTemplateApplied; - } + ////private void TreeViewItemMaterialized(object? sender, ItemContainerEventArgs e) + ////{ + //// var item = (TreeViewItem)e.Containers[0].ContainerControl; + //// item.TemplateApplied += TreeViewItemTemplateApplied; + ////} private void TreeViewItemTemplateApplied(object? sender, TemplateAppliedEventArgs e) { diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index 1d9e5df74a..ddf471e513 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs @@ -1,341 +1,341 @@ -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive.Subjects; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.LogicalTree; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Xunit; - -namespace Avalonia.Controls.UnitTests -{ - public class CarouselTests - { - [Fact] - public void First_Item_Should_Be_Selected_By_Default() - { - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = new[] - { - "Foo", - "Bar" - } - }; - - target.ApplyTemplate(); - - Assert.Equal(0, target.SelectedIndex); - Assert.Equal("Foo", target.SelectedItem); - } - - [Fact] - public void LogicalChild_Should_Be_Selected_Item() - { - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = new[] - { - "Foo", - "Bar" - } - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - Assert.Single(target.GetLogicalChildren()); - - var child = GetContainerTextBlock(target.GetLogicalChildren().Single()); - - Assert.Equal("Foo", child.Text); - } - - [Fact] - public void Should_Remove_NonCurrent_Page_When_IsVirtualized_True() - { - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = new[] { "foo", "bar" }, - IsVirtualized = true, - SelectedIndex = 0, - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - Assert.Single(target.ItemContainerGenerator.Containers); - target.SelectedIndex = 1; - Assert.Single(target.ItemContainerGenerator.Containers); - } - - [Fact] - public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes() - { - var items = new ObservableCollection - { - "Foo", - "Bar", - "FooBar" - }; - - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = false - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - Assert.Equal(3, target.GetLogicalChildren().Count()); - - var child = GetContainerTextBlock(target.GetLogicalChildren().First()); - - Assert.Equal("Foo", child.Text); - - var newItems = items.ToList(); - newItems.RemoveAt(0); - - target.Items = newItems; - - child = GetContainerTextBlock(target.GetLogicalChildren().First()); - - Assert.Equal("Bar", child.Text); - } - - [Fact] - public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes_And_Virtualized() - { - var items = new ObservableCollection - { - "Foo", - "Bar", - "FooBar" - }; - - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = true, - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - Assert.Single(target.GetLogicalChildren()); - - var child = GetContainerTextBlock(target.GetLogicalChildren().Single()); - - Assert.Equal("Foo", child.Text); - - var newItems = items.ToList(); - newItems.RemoveAt(0); - - target.Items = newItems; - - child = GetContainerTextBlock(target.GetLogicalChildren().Single()); - - Assert.Equal("Bar", child.Text); - } - - [Fact] - public void Selected_Item_Changes_To_First_Item_When_Item_Added() - { - var items = new ObservableCollection(); - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = false - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); +////using System.Collections.ObjectModel; +////using System.Linq; +////using System.Reactive.Subjects; +////using Avalonia.Controls.Presenters; +////using Avalonia.Controls.Templates; +////using Avalonia.Data; +////using Avalonia.LogicalTree; +////using Avalonia.UnitTests; +////using Avalonia.VisualTree; +////using Xunit; + +////namespace Avalonia.Controls.UnitTests +////{ +//// public class CarouselTests +//// { +//// [Fact] +//// public void First_Item_Should_Be_Selected_By_Default() +//// { +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = new[] +//// { +//// "Foo", +//// "Bar" +//// } +//// }; + +//// target.ApplyTemplate(); + +//// Assert.Equal(0, target.SelectedIndex); +//// Assert.Equal("Foo", target.SelectedItem); +//// } + +//// [Fact] +//// public void LogicalChild_Should_Be_Selected_Item() +//// { +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = new[] +//// { +//// "Foo", +//// "Bar" +//// } +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// Assert.Single(target.GetLogicalChildren()); + +//// var child = GetContainerTextBlock(target.GetLogicalChildren().Single()); + +//// Assert.Equal("Foo", child.Text); +//// } + +//// [Fact] +//// public void Should_Remove_NonCurrent_Page_When_IsVirtualized_True() +//// { +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = new[] { "foo", "bar" }, +//// IsVirtualized = true, +//// SelectedIndex = 0, +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// Assert.Single(target.ItemContainerGenerator.Containers); +//// target.SelectedIndex = 1; +//// Assert.Single(target.ItemContainerGenerator.Containers); +//// } + +//// [Fact] +//// public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes() +//// { +//// var items = new ObservableCollection +//// { +//// "Foo", +//// "Bar", +//// "FooBar" +//// }; + +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = false +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// Assert.Equal(3, target.GetLogicalChildren().Count()); + +//// var child = GetContainerTextBlock(target.GetLogicalChildren().First()); + +//// Assert.Equal("Foo", child.Text); + +//// var newItems = items.ToList(); +//// newItems.RemoveAt(0); + +//// target.Items = newItems; + +//// child = GetContainerTextBlock(target.GetLogicalChildren().First()); + +//// Assert.Equal("Bar", child.Text); +//// } + +//// [Fact] +//// public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes_And_Virtualized() +//// { +//// var items = new ObservableCollection +//// { +//// "Foo", +//// "Bar", +//// "FooBar" +//// }; + +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = true, +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// Assert.Single(target.GetLogicalChildren()); + +//// var child = GetContainerTextBlock(target.GetLogicalChildren().Single()); + +//// Assert.Equal("Foo", child.Text); + +//// var newItems = items.ToList(); +//// newItems.RemoveAt(0); + +//// target.Items = newItems; + +//// child = GetContainerTextBlock(target.GetLogicalChildren().Single()); + +//// Assert.Equal("Bar", child.Text); +//// } + +//// [Fact] +//// public void Selected_Item_Changes_To_First_Item_When_Item_Added() +//// { +//// var items = new ObservableCollection(); +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = false +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); - Assert.Equal(-1, target.SelectedIndex); - Assert.Empty(target.GetLogicalChildren()); +//// Assert.Equal(-1, target.SelectedIndex); +//// Assert.Empty(target.GetLogicalChildren()); - items.Add("Foo"); +//// items.Add("Foo"); - Assert.Equal(0, target.SelectedIndex); - Assert.Single(target.GetLogicalChildren()); - } +//// Assert.Equal(0, target.SelectedIndex); +//// Assert.Single(target.GetLogicalChildren()); +//// } - [Fact] - public void Selected_Index_Changes_To_None_When_Items_Assigned_Null() - { - var items = new ObservableCollection - { - "Foo", - "Bar", - "FooBar" - }; +//// [Fact] +//// public void Selected_Index_Changes_To_None_When_Items_Assigned_Null() +//// { +//// var items = new ObservableCollection +//// { +//// "Foo", +//// "Bar", +//// "FooBar" +//// }; - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = false - }; +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = false +//// }; - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); - Assert.Equal(3, target.GetLogicalChildren().Count()); +//// Assert.Equal(3, target.GetLogicalChildren().Count()); - var child = GetContainerTextBlock(target.GetLogicalChildren().First()); +//// var child = GetContainerTextBlock(target.GetLogicalChildren().First()); - Assert.Equal("Foo", child.Text); +//// Assert.Equal("Foo", child.Text); - target.Items = null; +//// target.Items = null; - var numChildren = target.GetLogicalChildren().Count(); +//// var numChildren = target.GetLogicalChildren().Count(); - Assert.Equal(0, numChildren); - Assert.Equal(-1, target.SelectedIndex); - } - - [Fact] - public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex() - { - var items = new ObservableCollection - { - "Foo", - "Bar", - "FooBar" - }; - - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = false, - SelectedIndex = 2 - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - Assert.Equal("FooBar", target.SelectedItem); - - var child = GetContainerTextBlock(target.GetVisualDescendants().LastOrDefault()); - - Assert.IsType(child); - Assert.Equal("FooBar", ((TextBlock)child).Text); - } - - [Fact] - public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List() - { - var items = new ObservableCollection - { - "Foo", - "Bar", - "FooBar" - }; - - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = false - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - Assert.Equal(3, target.GetLogicalChildren().Count()); - - var child = GetContainerTextBlock(target.GetLogicalChildren().First()); - - Assert.Equal("Foo", child.Text); - - items.RemoveAt(0); - - child = GetContainerTextBlock(target.GetLogicalChildren().First()); - - Assert.IsType(child); - Assert.Equal("Bar", ((TextBlock)child).Text); - } - - [Fact] - public void Selected_Item_Changes_To_First_Item_If_SelectedItem_Is_Removed_From_Middle() - { - var items = new ObservableCollection - { - "Foo", - "Bar", - "FooBar" - }; - - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - Items = items, - IsVirtualized = false - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - target.SelectedIndex = 1; - - items.RemoveAt(1); - - Assert.Equal(0, target.SelectedIndex); - Assert.Equal("Foo", target.SelectedItem); - } - - private Control CreateTemplate(Carousel control, INameScope scope) - { - return new CarouselPresenter - { - Name = "PART_ItemsPresenter", - [~CarouselPresenter.IsVirtualizedProperty] = control[~Carousel.IsVirtualizedProperty], - [~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty], - [~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty], - [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty], - }.RegisterInNameScope(scope); - } - - private static TextBlock GetContainerTextBlock(object control) - { - var contentPresenter = Assert.IsType(control); - contentPresenter.UpdateChild(); - return Assert.IsType(contentPresenter.Child); - } - - [Fact] - public void SelectedItem_Validation() - { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) - { - var target = new Carousel - { - Template = new FuncControlTemplate(CreateTemplate), - IsVirtualized = false - }; - - target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); - - var exception = new System.InvalidCastException("failed validation"); - var textObservable = - new BehaviorSubject(new BindingNotification(exception, - BindingErrorType.DataValidationError)); - target.Bind(ComboBox.SelectedItemProperty, textObservable); - - Assert.True(DataValidationErrors.GetHasErrors(target)); - Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); - } - } - } -} +//// Assert.Equal(0, numChildren); +//// Assert.Equal(-1, target.SelectedIndex); +//// } + +//// [Fact] +//// public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex() +//// { +//// var items = new ObservableCollection +//// { +//// "Foo", +//// "Bar", +//// "FooBar" +//// }; + +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = false, +//// SelectedIndex = 2 +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// Assert.Equal("FooBar", target.SelectedItem); + +//// var child = GetContainerTextBlock(target.GetVisualDescendants().LastOrDefault()); + +//// Assert.IsType(child); +//// Assert.Equal("FooBar", ((TextBlock)child).Text); +//// } + +//// [Fact] +//// public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List() +//// { +//// var items = new ObservableCollection +//// { +//// "Foo", +//// "Bar", +//// "FooBar" +//// }; + +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = false +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// Assert.Equal(3, target.GetLogicalChildren().Count()); + +//// var child = GetContainerTextBlock(target.GetLogicalChildren().First()); + +//// Assert.Equal("Foo", child.Text); + +//// items.RemoveAt(0); + +//// child = GetContainerTextBlock(target.GetLogicalChildren().First()); + +//// Assert.IsType(child); +//// Assert.Equal("Bar", ((TextBlock)child).Text); +//// } + +//// [Fact] +//// public void Selected_Item_Changes_To_First_Item_If_SelectedItem_Is_Removed_From_Middle() +//// { +//// var items = new ObservableCollection +//// { +//// "Foo", +//// "Bar", +//// "FooBar" +//// }; + +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// Items = items, +//// IsVirtualized = false +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// target.SelectedIndex = 1; + +//// items.RemoveAt(1); + +//// Assert.Equal(0, target.SelectedIndex); +//// Assert.Equal("Foo", target.SelectedItem); +//// } + +//// private Control CreateTemplate(Carousel control, INameScope scope) +//// { +//// return new CarouselPresenter +//// { +//// Name = "PART_ItemsPresenter", +//// [~CarouselPresenter.IsVirtualizedProperty] = control[~Carousel.IsVirtualizedProperty], +//// [~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty], +//// [~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty], +//// [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty], +//// }.RegisterInNameScope(scope); +//// } + +//// private static TextBlock GetContainerTextBlock(object control) +//// { +//// var contentPresenter = Assert.IsType(control); +//// contentPresenter.UpdateChild(); +//// return Assert.IsType(contentPresenter.Child); +//// } + +//// [Fact] +//// public void SelectedItem_Validation() +//// { +//// using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) +//// { +//// var target = new Carousel +//// { +//// Template = new FuncControlTemplate(CreateTemplate), +//// IsVirtualized = false +//// }; + +//// target.ApplyTemplate(); +//// ((Control)target.Presenter).ApplyTemplate(); + +//// var exception = new System.InvalidCastException("failed validation"); +//// var textObservable = +//// new BehaviorSubject(new BindingNotification(exception, +//// BindingErrorType.DataValidationError)); +//// target.Bind(ComboBox.SelectedItemProperty, textObservable); + +//// Assert.True(DataValidationErrors.GetHasErrors(target)); +//// Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); +//// } +//// } +//// } +////} diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs deleted file mode 100644 index 8a053c2a91..0000000000 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using Avalonia.Controls.Generators; -using Avalonia.Controls.Presenters; -using Avalonia.Data; -using Xunit; - -namespace Avalonia.Controls.UnitTests.Generators -{ - public class ItemContainerGeneratorTests - { - [Fact] - public void Materialize_Should_Create_Containers() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - var result = containers - .Select(x => x.ContainerControl) - .OfType() - .Select(x => x.Content) - .ToList(); - - Assert.Equal(items, result); - } - - [Fact] - public void ContainerFromIndex_Should_Return_Materialized_Containers() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - - Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); - Assert.Equal(containers[1].ContainerControl, target.ContainerFromIndex(1)); - Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); - } - - [Fact] - public void IndexFromContainer_Should_Return_Index() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - - Assert.Equal(0, target.IndexFromContainer(containers[0].ContainerControl)); - Assert.Equal(1, target.IndexFromContainer(containers[1].ContainerControl)); - Assert.Equal(2, target.IndexFromContainer(containers[2].ContainerControl)); - } - - [Fact] - public void Dematerialize_Should_Remove_Container() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - - target.Dematerialize(1, 1); - - Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); - Assert.Null(target.ContainerFromIndex(1)); - Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); - } - - [Fact] - public void Dematerialize_Should_Return_Removed_Containers() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - var expected = target.Containers.Take(2).ToList(); - var result = target.Dematerialize(0, 2); - - Assert.Equal(expected, result); - } - - [Fact] - public void InsertSpace_Should_Alter_Successive_Container_Indexes() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - - target.InsertSpace(1, 3); - - Assert.Equal(3, target.Containers.Count()); - Assert.Equal(new[] { 0, 4, 5 }, target.Containers.Select(x => x.Index)); - } - - [Fact] - public void RemoveRange_Should_Alter_Successive_Container_Indexes() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var containers = Materialize(target, 0, items); - - var removed = target.RemoveRange(1, 1).Single(); - - Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); - Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(1)); - Assert.Equal(containers[1], removed); - Assert.Equal(new[] { 0, 1 }, target.Containers.Select(x => x.Index)); - } - - [Fact] - public void Style_Binding_Should_Be_Able_To_Override_Content() - { - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner); - var container = (ContentPresenter)target.Materialize(0, "foo").ContainerControl; - - Assert.Equal("foo", container.Content); - - container.Bind( - ContentPresenter.ContentProperty, - Observable.Never().StartWith("bar"), - BindingPriority.Style); - - Assert.Equal("bar", container.Content); - } - - [Fact] - public void Style_Binding_Should_Be_Able_To_Override_Content_Typed() - { - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty, null); - var container = (ListBoxItem)target.Materialize(0, "foo").ContainerControl; - - Assert.Equal("foo", container.Content); - - container.Bind( - ContentPresenter.ContentProperty, - Observable.Never().StartWith("bar"), - BindingPriority.Style); - - Assert.Equal("bar", container.Content); - } - - [Fact] - public void Materialize_Should_Create_Containers_When_Item_Is_Null() - { - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty, null); - var container = (ListBoxItem)target.Materialize(0, null).ContainerControl; - - Assert.True(container != null, "The containers is not materialized."); - } - - private static IList Materialize( - IItemContainerGenerator generator, - int index, - string[] items) - { - var result = new List(); - - foreach (var item in items) - { - var container = generator.Materialize(index++, item); - result.Add(container); - } - - return result; - } - } -} diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs deleted file mode 100644 index f63a764a21..0000000000 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls.Generators; -using Xunit; - -namespace Avalonia.Controls.UnitTests.Generators -{ - public class ItemContainerGeneratorTypedTests - { - [Fact] - public void Materialize_Should_Create_Containers() - { - var items = new[] { "foo", "bar", "baz" }; - var owner = new Decorator(); - var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty, null); - var containers = Materialize(target, 0, items); - var result = containers - .Select(x => x.ContainerControl) - .OfType() - .Select(x => x.Content) - .ToList(); - - Assert.Equal(items, result); - } - - private static IList Materialize( - IItemContainerGenerator generator, - int index, - string[] items) - { - var result = new List(); - - foreach (var item in items) - { - var container = generator.Materialize(index++, item); - result.Add(container); - } - - return result; - } - } -} diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index ada5e10a7f..25d93746d5 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -367,12 +368,13 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Presenter.ApplyTemplate(); - Assert.Equal(2, target.ItemContainerGenerator.Containers.Count()); + var panel = target.Presenter.Panel; + Assert.Equal(2, panel.Children.Count()); target.Template = GetTemplate(); target.ApplyTemplate(); - Assert.Empty(target.ItemContainerGenerator.Containers); + Assert.Empty(panel.Children); } [Fact] @@ -535,26 +537,6 @@ namespace Avalonia.Controls.UnitTests Assert.DoesNotContain(":singleitem", target.Classes); } - [Fact] - public void Setting_Presenter_Explicitly_Should_Set_Item_Parent() - { - var target = new TestItemsControl(); - var child = new Control(); - - var presenter = new ItemsPresenter - { - [StyledElement.TemplatedParentProperty] = target, - }; - - presenter.ApplyTemplate(); - target.Presenter = presenter; - target.Items = new[] { child }; - target.ApplyTemplate(); - - Assert.Equal(target, child.Parent); - Assert.Equal(target, ((ILogical)child).LogicalParent); - } - [Fact] public void DataContexts_Should_Be_Correctly_Set() { @@ -683,36 +665,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Presenter_Items_Should_Be_In_Sync() - { - var target = new ItemsControl - { - Template = GetTemplate(), - Items = new object[] - { - new Button(), - new Button(), - }, - }; - - var root = new TestRoot { Child = target }; - var otherPanel = new StackPanel(); - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - target.ItemContainerGenerator.Materialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - }; - - target.Items = new[] - { - new Canvas() - }; - } - [Fact] public void Detaching_Then_Reattaching_To_Logical_Tree_Twice_Does_Not_Throw() { @@ -780,14 +732,5 @@ namespace Avalonia.Controls.UnitTests }; }); } - - private class TestItemsControl : ItemsControl - { - public new ItemsPresenter Presenter - { - get { return base.Presenter; } - set { base.Presenter = value; } - } - } } } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 280f140a79..cc824876ac 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; @@ -218,6 +219,7 @@ namespace Avalonia.Controls.UnitTests // Scroll down a page. target.Scroll.Offset = new Vector(0, 10); + Layout(target); // Make sure recycled item isn't now selected. Assert.False(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected); @@ -239,8 +241,8 @@ namespace Avalonia.Controls.UnitTests Prepare(target); - Assert.Equal(new Size(20, 20), target.Scroll.Extent); - Assert.Equal(new Size(100, 10), target.Scroll.Viewport); + Assert.Equal(new Size(100, 200), target.Scroll.Extent); + Assert.Equal(new Size(100, 100), target.Scroll.Viewport); } } @@ -263,11 +265,13 @@ namespace Avalonia.Controls.UnitTests items.Clear(); items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}")); + Layout(target); + items.Remove("Item 2"); + Layout(target); - Assert.Equal( - items, - target.Presenter.Panel.Children.Cast().Select(x => (string)x.Content)); + var actual = target.Presenter.Panel.Children.Cast().Select(x => (string)x.Content).ToList(); + Assert.Equal(items.OrderBy(x => x), actual.OrderBy(x => x)); } } @@ -328,44 +332,6 @@ namespace Avalonia.Controls.UnitTests _mouse.Click(listBox, item, mouseButton); } - [Fact] - public void ListBox_After_Scroll_IndexOutOfRangeException_Shouldnt_Be_Thrown() - { - throw new NotImplementedException(); - ////using (UnitTestApplication.Start(TestServices.StyledWindow)) - ////{ - //// var items = Enumerable.Range(0, 11).Select(x => $"{x}").ToArray(); - - //// var target = new ListBox - //// { - //// Template = ListBoxTemplate(), - //// Items = items, - //// ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 11 }) - //// }; - - //// Prepare(target); - - //// var panel = target.Presenter.Panel as IVirtualizingPanel; - - //// var listBoxItems = panel.Children.OfType(); - - //// //virtualization should have created exactly 10 items - //// Assert.Equal(10, listBoxItems.Count()); - //// Assert.Equal("0", listBoxItems.First().DataContext); - //// Assert.Equal("9", listBoxItems.Last().DataContext); - - //// //instead pixeloffset > 0 there could be pretty complex sequence for repro - //// //it involves add/remove/scroll to end multiple actions - //// //which i can't find so far :(, but this is the simplest way to add it to unit test - //// panel.PixelOffset = 1; - - //// //here scroll to end -> IndexOutOfRangeException is thrown - //// target.Scroll.Offset = new Vector(0, 2); - - //// Assert.True(true); - ////} - } - [Fact] public void LayoutManager_Should_Measure_Arrange_All() { @@ -464,11 +430,11 @@ namespace Avalonia.Controls.UnitTests items.Remove("1"); lm.ExecuteLayoutPass(); - Assert.Equal("30", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 1).DataContext); - Assert.Equal("29", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 2).DataContext); - Assert.Equal("28", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 3).DataContext); - Assert.Equal("27", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 4).DataContext); - Assert.Equal("26", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 5).DataContext); + Assert.Equal("30", target.ContainerFromIndex(items.Count - 1).DataContext); + Assert.Equal("29", target.ContainerFromIndex(items.Count - 2).DataContext); + Assert.Equal("28", target.ContainerFromIndex(items.Count - 3).DataContext); + Assert.Equal("27", target.ContainerFromIndex(items.Count - 4).DataContext); + Assert.Equal("26", target.ContainerFromIndex(items.Count - 5).DataContext); } } @@ -525,7 +491,7 @@ namespace Avalonia.Controls.UnitTests var target = new ListBox() { - VerticalAlignment = Layout.VerticalAlignment.Top, + VerticalAlignment = VerticalAlignment.Top, AutoScrollToSelectedItem = true, Width = 50, ItemTemplate = new FuncDataTemplate((c, _) => new Border() { Height = 10 }), @@ -627,6 +593,8 @@ namespace Avalonia.Controls.UnitTests [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty], + [~ScrollContentPresenter.CanHorizontallyScrollProperty] = parent[~ScrollViewer.CanHorizontallyScrollProperty], + [~ScrollContentPresenter.CanVerticallyScrollProperty] = parent[~ScrollViewer.CanVerticallyScrollProperty], }.RegisterInNameScope(scope), new ScrollBar { @@ -640,45 +608,28 @@ namespace Avalonia.Controls.UnitTests private static void Prepare(ListBox target) { - // The ListBox needs to be part of a rooted visual tree. - var root = new TestRoot(); - root.Child = target; - - // Apply the template to the ListBox itself. - target.ApplyTemplate(); + target.Width = target.Height = 100; - // Then to its inner ScrollViewer. - var scrollViewer = (ScrollViewer)target.GetVisualChildren().Single(); - scrollViewer.ApplyTemplate(); - - // Then make the ScrollViewer create its child. - ((ContentPresenter)scrollViewer.Presenter).UpdateChild(); - - // Now the ItemsPresenter should be reigstered, so apply its template. - target.Presenter.ApplyTemplate(); - - // Because ListBox items are virtualized we need to do a layout to make them appear. - target.Measure(new Size(100, 100)); - target.Arrange(new Rect(0, 0, 100, 100)); - - // Now set and apply the item templates. - foreach (ListBoxItem item in target.Presenter.Panel.Children) + var root = new TestRoot(target) { - item.Template = ListBoxItemTemplate(); - item.ApplyTemplate(); - item.Presenter.ApplyTemplate(); - ((ContentPresenter)item.Presenter).UpdateChild(); - } + Resources = + { + { + typeof(ListBoxItem), + new ControlTheme(typeof(ListBoxItem)) + { + Setters = { new Setter(ListBoxItem.TemplateProperty, ListBoxItemTemplate()) } + } + } + } + }; - // The items were created before the template was applied, so now we need to go back - // and re-arrange everything. - foreach (Control i in target.GetSelfAndVisualDescendants()) - { - i.InvalidateMeasure(); - } + root.LayoutManager.ExecuteInitialLayoutPass(); + } - target.Measure(new Size(100, 100)); - target.Arrange(new Rect(0, 0, 100, 100)); + private static void Layout(Control c) + { + ((ILayoutRoot)c.GetVisualRoot()).LayoutManager.ExecuteLayoutPass(); } private class Item diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 8be1e112e7..8ded57956b 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1,1475 +1,1475 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using Avalonia.Collections; -using Avalonia.Controls.Generators; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.Data.Core; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Avalonia.Interactivity; -using Avalonia.LogicalTree; -using Avalonia.Styling; -using Avalonia.UnitTests; -using JetBrains.Annotations; -using Moq; -using Xunit; - -namespace Avalonia.Controls.UnitTests -{ - public class TreeViewTests - { - MouseTestHelper _mouse = new MouseTestHelper(); - - [Fact] - public void Items_Should_Be_Created() - { - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = CreateTestTreeData(), - }; - - var root = new TestRoot(target); - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); - Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1)); - Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); - } - - [Fact] - public void Items_Should_Be_Created_Using_ItemTemplate_If_Present() - { - TreeView target; - - var root = new TestRoot - { - Child = target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = CreateTestTreeData(), - ItemTemplate = new FuncTreeDataTemplate( - (_, __) => new Canvas(), - x => x.Children), - } - }; - - ApplyTemplates(target); - - var items = target.ItemContainerGenerator.Index.Containers - .OfType() - .ToList(); - - Assert.Equal(5, items.Count); - Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); - } - - [Fact] - public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present() - { - TreeView target; - var theme = new ControlTheme(typeof(TreeViewItem)); - - var root = new TestRoot - { - Child = target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = CreateTestTreeData(), - ItemContainerTheme = theme, - ItemTemplate = new FuncTreeDataTemplate( - (_, __) => new Canvas(), - x => x.Children), - } - }; - - ApplyTemplates(target); - - var items = target.ItemContainerGenerator.Index.Containers - .OfType() - .ToList(); - - Assert.Equal(5, items.Count); - Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme)); - } - - [Fact] - public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() - { - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = CreateTestTreeData(), - }; - - var root = new TestRoot(target); - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl; - var header = (TextBlock)container.Header; - Assert.Equal("Root", header.Text); - } - - [Fact] - public void Root_TreeContainerFromItem_Should_Return_Descendant_Item() - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - // For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs - // to be called, which requires an IStyleRoot. - var root = new TestRoot(); - root.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - var container = target.ItemContainerGenerator.Index.ContainerFromItem( - tree[0].Children[1].Children[0]); - - Assert.NotNull(container); - - var header = ((TreeViewItem)container).Header; - var headerContent = ((TextBlock)header).Text; - - Assert.Equal("Grandchild2a", headerContent); - } - - [Fact] - public void Clicking_Item_Should_Select_It() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var item = tree[0].Children[1].Children[0]; - var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); - - Assert.NotNull(container); - - _mouse.Click(container); - - Assert.Equal(item, target.SelectedItem); - Assert.True(container.IsSelected); - } - } - - [Fact] - public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var item = tree[0].Children[1].Children[0]; - var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); - - Assert.NotNull(container); - - target.SelectedItem = item; - - Assert.True(container.IsSelected); - - _mouse.Click(container, modifiers: KeyModifiers.Control); - - Assert.Null(target.SelectedItem); - Assert.False(container.IsSelected); - } - } - - [Fact] - public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree - }; +////using System; +////using System.Collections; +////using System.Collections.Generic; +////using System.Collections.ObjectModel; +////using System.ComponentModel; +////using System.Linq; +////using System.Runtime.CompilerServices; +////using Avalonia.Collections; +////using Avalonia.Controls.Generators; +////using Avalonia.Controls.Presenters; +////using Avalonia.Controls.Templates; +////using Avalonia.Data; +////using Avalonia.Data.Core; +////using Avalonia.Input; +////using Avalonia.Input.Platform; +////using Avalonia.Interactivity; +////using Avalonia.LogicalTree; +////using Avalonia.Styling; +////using Avalonia.UnitTests; +////using JetBrains.Annotations; +////using Moq; +////using Xunit; + +////namespace Avalonia.Controls.UnitTests +////{ +//// public class TreeViewTests +//// { +//// MouseTestHelper _mouse = new MouseTestHelper(); + +//// [Fact] +//// public void Items_Should_Be_Created() +//// { +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = CreateTestTreeData(), +//// }; + +//// var root = new TestRoot(target); + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); +//// Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1)); +//// Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); +//// } + +//// [Fact] +//// public void Items_Should_Be_Created_Using_ItemTemplate_If_Present() +//// { +//// TreeView target; + +//// var root = new TestRoot +//// { +//// Child = target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = CreateTestTreeData(), +//// ItemTemplate = new FuncTreeDataTemplate( +//// (_, __) => new Canvas(), +//// x => x.Children), +//// } +//// }; + +//// ApplyTemplates(target); + +//// var items = target.ItemContainerGenerator.Index.Containers +//// .OfType() +//// .ToList(); + +//// Assert.Equal(5, items.Count); +//// Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); +//// } + +//// [Fact] +//// public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present() +//// { +//// TreeView target; +//// var theme = new ControlTheme(typeof(TreeViewItem)); + +//// var root = new TestRoot +//// { +//// Child = target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = CreateTestTreeData(), +//// ItemContainerTheme = theme, +//// ItemTemplate = new FuncTreeDataTemplate( +//// (_, __) => new Canvas(), +//// x => x.Children), +//// } +//// }; + +//// ApplyTemplates(target); + +//// var items = target.ItemContainerGenerator.Index.Containers +//// .OfType() +//// .ToList(); + +//// Assert.Equal(5, items.Count); +//// Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme)); +//// } + +//// [Fact] +//// public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() +//// { +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = CreateTestTreeData(), +//// }; + +//// var root = new TestRoot(target); + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl; +//// var header = (TextBlock)container.Header; +//// Assert.Equal("Root", header.Text); +//// } + +//// [Fact] +//// public void Root_TreeContainerFromItem_Should_Return_Descendant_Item() +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// // For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs +//// // to be called, which requires an IStyleRoot. +//// var root = new TestRoot(); +//// root.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// var container = target.ItemContainerGenerator.Index.ContainerFromItem( +//// tree[0].Children[1].Children[0]); + +//// Assert.NotNull(container); + +//// var header = ((TreeViewItem)container).Header; +//// var headerContent = ((TextBlock)header).Text; + +//// Assert.Equal("Grandchild2a", headerContent); +//// } + +//// [Fact] +//// public void Clicking_Item_Should_Select_It() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var item = tree[0].Children[1].Children[0]; +//// var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); + +//// Assert.NotNull(container); + +//// _mouse.Click(container); + +//// Assert.Equal(item, target.SelectedItem); +//// Assert.True(container.IsSelected); +//// } +//// } + +//// [Fact] +//// public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var item = tree[0].Children[1].Children[0]; +//// var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); + +//// Assert.NotNull(container); + +//// target.SelectedItem = item; + +//// Assert.True(container.IsSelected); + +//// _mouse.Click(container, modifiers: KeyModifiers.Control); + +//// Assert.Null(target.SelectedItem); +//// Assert.False(container.IsSelected); +//// } +//// } + +//// [Fact] +//// public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree +//// }; - var visualRoot = new TestRoot(); - visualRoot.Child = target; +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); - var item1 = tree[0].Children[1].Children[0]; - var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); +//// var item1 = tree[0].Children[1].Children[0]; +//// var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); - var item2 = tree[0].Children[1]; - var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); +//// var item2 = tree[0].Children[1]; +//// var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); - Assert.NotNull(container1); - Assert.NotNull(container2); - - target.SelectedItem = item1; - - Assert.True(container1.IsSelected); - - _mouse.Click(container2, modifiers: KeyModifiers.Control); - - Assert.Equal(item2, target.SelectedItem); - Assert.False(container1.IsSelected); - Assert.True(container2.IsSelected); - } - } - - [Fact] - public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; +//// Assert.NotNull(container1); +//// Assert.NotNull(container2); + +//// target.SelectedItem = item1; + +//// Assert.True(container1.IsSelected); + +//// _mouse.Click(container2, modifiers: KeyModifiers.Control); + +//// Assert.Equal(item2, target.SelectedItem); +//// Assert.False(container1.IsSelected); +//// Assert.True(container2.IsSelected); +//// } +//// } + +//// [Fact] +//// public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; - var visualRoot = new TestRoot(); - visualRoot.Child = target; +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); - var rootNode = tree[0]; - - var item1 = rootNode.Children[0]; - var item2 = rootNode.Children.Last(); - - var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); - var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); - - ClickContainer(item1Container, KeyModifiers.Control); - Assert.True(item1Container.IsSelected); - - ClickContainer(item2Container, KeyModifiers.Control); - Assert.True(item2Container.IsSelected); +//// var rootNode = tree[0]; + +//// var item1 = rootNode.Children[0]; +//// var item2 = rootNode.Children.Last(); + +//// var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); +//// var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); + +//// ClickContainer(item1Container, KeyModifiers.Control); +//// Assert.True(item1Container.IsSelected); + +//// ClickContainer(item2Container, KeyModifiers.Control); +//// Assert.True(item2Container.IsSelected); - Assert.Equal(new[] { item1, item2 }, target.SelectedItems.OfType()); +//// Assert.Equal(new[] { item1, item2 }, target.SelectedItems.OfType()); - ClickContainer(item1Container, KeyModifiers.Control); - Assert.False(item1Container.IsSelected); +//// ClickContainer(item1Container, KeyModifiers.Control); +//// Assert.False(item1Container.IsSelected); - Assert.DoesNotContain(item1, target.SelectedItems.OfType()); - } - } +//// Assert.DoesNotContain(item1, target.SelectedItems.OfType()); +//// } +//// } - [Fact] - public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; +//// [Fact] +//// public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; - var visualRoot = new TestRoot(); - visualRoot.Child = target; +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); - var rootNode = tree[0]; +//// var rootNode = tree[0]; - var from = rootNode.Children[0]; - var to = rootNode.Children.Last(); +//// var from = rootNode.Children[0]; +//// var to = rootNode.Children.Last(); - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - ClickContainer(fromContainer, KeyModifiers.None); +//// ClickContainer(fromContainer, KeyModifiers.None); - Assert.True(fromContainer.IsSelected); +//// Assert.True(fromContainer.IsSelected); - ClickContainer(toContainer, KeyModifiers.Shift); - AssertChildrenSelected(target, rootNode); - } - } +//// ClickContainer(toContainer, KeyModifiers.Shift); +//// AssertChildrenSelected(target, rootNode); +//// } +//// } - [Fact] - public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); +//// [Fact] +//// public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); - var rootNode = tree[0]; - - var from = rootNode.Children.Last(); - var to = rootNode.Children[0]; - - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - - ClickContainer(fromContainer, KeyModifiers.None); +//// var rootNode = tree[0]; + +//// var from = rootNode.Children.Last(); +//// var to = rootNode.Children[0]; + +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + +//// ClickContainer(fromContainer, KeyModifiers.None); - Assert.True(fromContainer.IsSelected); +//// Assert.True(fromContainer.IsSelected); - ClickContainer(toContainer, KeyModifiers.Shift); - AssertChildrenSelected(target, rootNode); - } - } +//// ClickContainer(toContainer, KeyModifiers.Shift); +//// AssertChildrenSelected(target, rootNode); +//// } +//// } - [Fact] - public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var rootNode = tree[0]; - - var from = rootNode.Children.Last(); - var to = rootNode.Children[0]; - - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - - ClickContainer(fromContainer, KeyModifiers.None); - - ClickContainer(toContainer, KeyModifiers.Shift); - AssertChildrenSelected(target, rootNode); - - ClickContainer(fromContainer, KeyModifiers.None); - - Assert.True(fromContainer.IsSelected); - - foreach (var child in rootNode.Children) - { - if (child == from) - { - continue; - } - - var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child); - - Assert.False(container.IsSelected); - } - } - } - - [Fact] - public void Setting_SelectedItem_Should_Set_Container_Selected() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var item = tree[0].Children[1].Children[0]; - var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); - - Assert.NotNull(container); - - target.SelectedItem = item; - - Assert.True(container.IsSelected); - } - } - - [Fact] - public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var item = tree[0].Children[1].Children[0]; - - var called = false; - target.SelectionChanged += (s, e) => - { - Assert.Empty(e.RemovedItems); - Assert.Equal(1, e.AddedItems.Count); - Assert.Same(item, e.AddedItems[0]); - called = true; - }; - - target.SelectedItem = item; - Assert.True(called); - } - } - - [Fact] - public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection() - { - using (Application()) - { - var dataContext = new TestDataContext(); - - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - DataContext = dataContext - }; - - target.Bind(TreeView.ItemsProperty, new Binding("Items")); - target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem")); - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - var selectedValues = new List(); - - dataContext.PropertyChanged += (_, e) => - { - if (e.PropertyName == nameof(TestDataContext.SelectedItem)) - selectedValues.Add(dataContext.SelectedItem); - }; - selectedValues.Add(dataContext.SelectedItem); - - _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); - _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); - - Assert.Equal(3, selectedValues.Count); - Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray()); - } - } - - [Fact] - public void LogicalChildren_Should_Be_Set() - { - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplates(target); - - var result = target.GetLogicalChildren() - .OfType() - .Select(x => x.Header) - .OfType() - .Select(x => x.Text) - .ToList(); - - Assert.Equal(new[] { "Foo", "Bar", "Baz " }, result); - } - - [Fact] - public void Removing_Item_Should_Remove_Itself_And_Children_From_Index() - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var root = new TestRoot(); - root.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); - - tree[0].Children.RemoveAt(1); - - Assert.Equal(3, target.ItemContainerGenerator.Index.Containers.Count()); - } - - [Fact] - public void DataContexts_Should_Be_Correctly_Set() - { - var items = new object[] - { - "Foo", - new Node { Value = "Bar" }, - new TextBlock { Text = "Baz" }, - new TreeViewItem { Header = "Qux" }, - }; - - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - DataContext = "Base", - DataTemplates = - { - new FuncDataTemplate((x, _) => new Button { Content = x }) - }, - Items = items, - }; - - ApplyTemplates(target); - - var dataContexts = target.Presenter.Panel.Children - .Cast() - .Select(x => x.DataContext) - .ToList(); - - Assert.Equal( - new object[] { items[0], items[1], "Base", "Base" }, - dataContexts); - } - - [Fact] - public void Control_Item_Should_Not_Be_NameScope() - { - var items = new object[] - { - new TreeViewItem(), - }; - - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = items, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - var item = target.Presenter.Panel.LogicalChildren[0]; - Assert.Null(NameScope.GetNameScope((TreeViewItem)item)); - } - - [Fact] - public void Should_React_To_Children_Changing() - { - var data = CreateTestTreeData(); - - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = data, - }; - - var root = new TestRoot(target); - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); - Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1)); - Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); - - // Make sure that the binding to Node.Children does not get collected. - GC.Collect(); - - data[0].Children = new AvaloniaList - { - new Node - { - Value = "NewChild1", - } - }; - - Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); - Assert.Equal(new[] { "NewChild1" }, ExtractItemHeader(target, 1)); - } - - [Fact] - public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node() - { - using (Application()) - { - var focus = FocusManager.Instance; - var navigation = AvaloniaLocator.Current.GetService(); - var data = CreateTestTreeData(); - - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = data, - }; - - var button = new Button(); - - var root = new TestRoot - { - Child = new StackPanel - { - Children = { target, button }, - } - }; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var item = data[0].Children[0]; - var node = target.ItemContainerGenerator.Index.ContainerFromItem(item); - Assert.NotNull(node); - - target.SelectedItem = item; - node.Focus(); - Assert.Same(node, focus.Current); - - navigation.Move(focus.Current, NavigationDirection.Next); - Assert.Same(button, focus.Current); - - navigation.Move(focus.Current, NavigationDirection.Next); - Assert.Same(node, focus.Current); - } - } - - [Fact] - public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree() - { - using (Application()) - { - var focus = FocusManager.Instance; - var navigation = AvaloniaLocator.Current.GetService(); - var data = CreateTestTreeData(); - - var selectedNode = new Node { Value = "Out of Tree Selected Item" }; - - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = data, - SelectedItem = selectedNode - }; - - var button = new Button(); - - var root = new TestRoot - { - Child = new StackPanel - { - Children = { target, button }, - } - }; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var item = data[0].Children[0]; - var node = target.ItemContainerGenerator.Index.ContainerFromItem(item); - Assert.NotNull(node); - - target.SelectedItem = selectedNode; - node.Focus(); - Assert.Same(node, focus.Current); - - var next = KeyboardNavigationHandler.GetNext(node, NavigationDirection.Previous); - } - } - - [Fact] - public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes() - { - using (UnitTestApplication.Start()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var rootNode = tree[0]; - - var keymap = AvaloniaLocator.Current.GetService(); - var selectAllGesture = keymap.SelectAll.First(); - - var keyEvent = new KeyEventArgs - { - RoutedEvent = InputElement.KeyDownEvent, - Key = selectAllGesture.Key, - KeyModifiers = selectAllGesture.KeyModifiers - }; - - target.RaiseEvent(keyEvent); - - AssertChildrenSelected(target, rootNode); - } - } - - [Fact] - public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var rootNode = tree[0]; - - var from = rootNode.Children[0]; - var to = rootNode.Children.Last(); - - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - - ClickContainer(fromContainer, KeyModifiers.None); - ClickContainer(toContainer, KeyModifiers.Shift); - - var keymap = AvaloniaLocator.Current.GetService(); - var selectAllGesture = keymap.SelectAll.First(); - - var keyEvent = new KeyEventArgs - { - RoutedEvent = InputElement.KeyDownEvent, - Key = selectAllGesture.Key, - KeyModifiers = selectAllGesture.KeyModifiers - }; - - target.RaiseEvent(keyEvent); - - AssertChildrenSelected(target, rootNode); - } - } - - [Fact] - public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var rootNode = tree[0]; - - var from = rootNode.Children.Last(); - var to = rootNode.Children[0]; - - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - - ClickContainer(fromContainer, KeyModifiers.None); - ClickContainer(toContainer, KeyModifiers.Shift); - - var keymap = AvaloniaLocator.Current.GetService(); - var selectAllGesture = keymap.SelectAll.First(); - - var keyEvent = new KeyEventArgs - { - RoutedEvent = InputElement.KeyDownEvent, - Key = selectAllGesture.Key, - KeyModifiers = selectAllGesture.KeyModifiers - }; - - target.RaiseEvent(keyEvent); - - AssertChildrenSelected(target, rootNode); - } - } - - [Fact] - public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() - { - using (UnitTestApplication.Start()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - target.SelectAll(); - - AssertChildrenSelected(target, tree[0]); - Assert.Equal(5, target.SelectedItems.Count); - - _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - - Assert.Equal(5, target.SelectedItems.Count); - } - } - - [Fact] - public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - - var rootNode = tree[0]; - var to = rootNode.Children[0]; - var then = rootNode.Children[1]; - - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then); - - ClickContainer(fromContainer, KeyModifiers.None); - ClickContainer(toContainer, KeyModifiers.Shift); - - Assert.Equal(2, target.SelectedItems.Count); - - _mouse.Click(thenContainer, MouseButton.Right); - - Assert.Equal(1, target.SelectedItems.Count); - } - } - - [Fact] - public void Shift_Right_Click_Should_Not_Select_Multiple() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - - var rootNode = tree[0]; - var from = rootNode.Children[0]; - var to = rootNode.Children[1]; - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - - _mouse.Click(fromContainer); - _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift); - - Assert.Equal(1, target.SelectedItems.Count); - } - } - - [Fact] - public void Ctrl_Right_Click_Should_Not_Select_Multiple() - { - using (Application()) - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - - var rootNode = tree[0]; - var from = rootNode.Children[0]; - var to = rootNode.Children[1]; - var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); - var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - - _mouse.Click(fromContainer); - _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedItems.Count); - } - } - - [Fact] - public void TreeViewItems_Level_Should_Be_Set() - { - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - Assert.Equal(0, GetItem(target, 0).Level); - Assert.Equal(1, GetItem(target, 0, 0).Level); - Assert.Equal(1, GetItem(target, 0, 1).Level); - Assert.Equal(1, GetItem(target, 0, 2).Level); - Assert.Equal(2, GetItem(target, 0, 1, 0).Level); - } - - [Fact] - public void TreeViewItems_Level_Should_Be_Set_For_Derived_TreeView() - { - var tree = CreateTestTreeData(); - var target = new DerivedTreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - Assert.Equal(0, GetItem(target, 0).Level); - Assert.Equal(1, GetItem(target, 0, 0).Level); - Assert.Equal(1, GetItem(target, 0, 1).Level); - Assert.Equal(1, GetItem(target, 0, 2).Level); - Assert.Equal(2, GetItem(target, 0, 1, 0).Level); - } - - [Fact] - public void Adding_Node_To_Removed_And_ReAdded_Parent_Should_Not_Crash() - { - // Issue #2985 - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - var parent = tree[0]; - var node = parent.Children[1]; - - parent.Children.Remove(node); - parent.Children.Add(node); - - var item = target.ItemContainerGenerator.Index.ContainerFromItem(node); - ApplyTemplates(new[] { item }); - - // #2985 causes ArgumentException here. - node.Children.Add(new Node()); - } - - [Fact] - public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection() - { - // Issue #2980. - using (Application()) - { - var target = new DerivedTreeView - { - Template = CreateTreeViewTemplate(), - SelectionMode = SelectionMode.Multiple, - Items = new List - { - new Node { Value = "Root1", }, - new Node { Value = "Root2", }, - }, - }; - - var visualRoot = new TestRoot - { - Styles = - { - new Style(x => x.OfType()) - { - Setters = - { - new Setter(TreeViewItem.IsExpandedProperty, true), - }, - }, - }, - Child = target, - }; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - _mouse.Click(GetItem(target, 0)); - _mouse.Click(GetItem(target, 1), modifiers: KeyModifiers.Shift); - } - } - - [Fact] - public void Removing_TreeView_From_Root_Should_Preserve_TreeViewItems() - { - // Issue #3328 - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var root = new TestRoot(); - root.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - ExpandAll(target); - - Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); - - root.Child = null; - - Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); - Assert.Equal(1, target.Presenter.Panel.Children.Count); - - var rootNode = Assert.IsType(target.Presenter.Panel.Children[0]); - Assert.Equal(3, rootNode.ItemContainerGenerator.Containers.Count()); - Assert.Equal(3, rootNode.Presenter.Panel.Children.Count); - - var child2Node = Assert.IsType(rootNode.Presenter.Panel.Children[1]); - Assert.Equal(1, child2Node.ItemContainerGenerator.Containers.Count()); - Assert.Equal(1, child2Node.Presenter.Panel.Children.Count); - } - - [Fact] - public void Clearing_TreeView_Items_Clears_Index() - { - // Issue #3551 - var tree = CreateTestTreeData(); - var target = new TreeView - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - var root = new TestRoot(); - root.Child = target; - - CreateNodeDataTemplate(target); - ApplyTemplates(target); - - var rootNode = tree[0]; - var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode); - - Assert.NotNull(container); - - root.Child = null; - - tree.Clear(); - - Assert.Empty(target.ItemContainerGenerator.Index.Containers); - } - - [Fact] - public void Can_Use_Derived_TreeViewItem() - { - var tree = CreateTestTreeData(); - var target = new DerivedTreeViewWithDerivedTreeViewItems - { - Template = CreateTreeViewTemplate(), - Items = tree, - }; - - ApplyTemplates(target); - - // Verify that all items are DerivedTreeViewItem - VerifyItemType(target.ItemContainerGenerator); - - void VerifyItemType(ITreeItemContainerGenerator containerGenerator) - { - foreach (var container in containerGenerator.Index.Containers) - { - var item = Assert.IsType(container); - if (item.ItemCount > 0) - { - VerifyItemType(item.ItemContainerGenerator); - } - } - } - } - - private void ApplyTemplates(TreeView tree) - { - tree.ApplyTemplate(); - tree.Presenter.ApplyTemplate(); - ApplyTemplates(tree.Presenter.Panel.Children); - } - - private void ApplyTemplates(IEnumerable controls) - { - foreach (TreeViewItem control in controls) - { - control.Template = CreateTreeViewItemTemplate(); - control.ApplyTemplate(); - control.Presenter.ApplyTemplate(); - control.HeaderPresenter.ApplyTemplate(); - ApplyTemplates(control.Presenter.Panel.Children); - } - } - - private TreeViewItem GetItem(TreeView target, params int[] indexes) - { - var c = (ItemsControl)target; - - foreach (var index in indexes) - { - var item = ((IList)c.Items)[index]; - c = (ItemsControl)target.ItemContainerGenerator.Index.ContainerFromItem(item); - } - - return (TreeViewItem)c; - } - - private IList CreateTestTreeData() - { - return new AvaloniaList - { - new Node - { - Value = "Root", - Children = new AvaloniaList - { - new Node - { - Value = "Child1", - }, - new Node - { - Value = "Child2", - Children = new AvaloniaList - { - new Node - { - Value = "Grandchild2a", - }, - }, - }, - new Node - { - Value = "Child3", - } - } - } - }; - } - - private void CreateNodeDataTemplate(Control control) - { - control.DataTemplates.Add(new TestTreeDataTemplate()); - } - - private IControlTemplate CreateTreeViewTemplate() - { - return new FuncControlTemplate((parent, scope) => new ItemsPresenter - { - Name = "PART_ItemsPresenter", - }.RegisterInNameScope(scope)); - } - - private IControlTemplate CreateTreeViewItemTemplate() - { - return new FuncControlTemplate((parent, scope) => new Panel - { - Children = - { - new ContentPresenter - { - Name = "PART_HeaderPresenter", - [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty], - }.RegisterInNameScope(scope), - new ItemsPresenter - { - Name = "PART_ItemsPresenter", - }.RegisterInNameScope(scope) - } - }); - } - - private void ExpandAll(TreeView tree) - { - foreach (var i in tree.ItemContainerGenerator.Containers) - { - tree.ExpandSubTree((TreeViewItem)i.ContainerControl); - } - } - - private List ExtractItemHeader(TreeView tree, int level) - { - return ExtractItemContent(tree.Presenter.Panel, 0, level) - .Select(x => x.Header) - .OfType() - .Select(x => x.Text) - .ToList(); - } - - private IEnumerable ExtractItemContent(Panel panel, int currentLevel, int level) - { - foreach (TreeViewItem container in panel.Children) - { - if (container.Template == null) - { - container.Template = CreateTreeViewItemTemplate(); - container.ApplyTemplate(); - } - - if (currentLevel == level) - { - yield return container; - } - else - { - foreach (var child in ExtractItemContent(container.Presenter.Panel, currentLevel + 1, level)) - { - yield return child; - } - } - } - } - - private void ClickContainer(Control container, KeyModifiers modifiers) - { - _mouse.Click(container, modifiers: modifiers); - } - - private void AssertChildrenSelected(TreeView treeView, Node rootNode) - { - foreach (var child in rootNode.Children) - { - var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child); - - Assert.True(container.IsSelected); - } - } - - private IDisposable Application() - { - return UnitTestApplication.Start( - TestServices.MockThreadingInterface.With( - focusManager: new FocusManager(), - keyboardDevice: () => new KeyboardDevice(), - keyboardNavigation: new KeyboardNavigationHandler(), - inputManager: new InputManager())); - } - - private class Node : NotifyingBase - { - private IAvaloniaList _children; - - public string Value { get; set; } - - public IAvaloniaList Children - { - get - { - return _children; - } - - set - { - _children = value; - RaisePropertyChanged(nameof(Children)); - } - } - } - - private class TestTreeDataTemplate : ITreeDataTemplate - { - public Control Build(object param) - { - var node = (Node)param; - return new TextBlock { Text = node.Value }; - } - - public InstancedBinding ItemsSelector(object item) - { - var obs = ExpressionObserver.Create(item, o => (o as Node).Children); - return InstancedBinding.OneWay(obs); - } - - public bool Match(object data) - { - return data is Node; - } - } - - private class DerivedTreeView : TreeView - { - } - - private class DerivedTreeViewWithDerivedTreeViewItems : TreeView - { - protected override ITreeItemContainerGenerator CreateTreeItemContainerGenerator() => - CreateTreeItemContainerGenerator(); - } - - private class DerivedTreeViewItem : TreeViewItem - { - protected override IItemContainerGenerator CreateItemContainerGenerator() => CreateTreeItemContainerGenerator(); - } - - private class TestDataContext : INotifyPropertyChanged - { - private string _selectedItem; - - public TestDataContext() - { - Items = new ObservableCollection(Enumerable.Range(0, 5).Select(i => $"Item {i}")); - } - - public ObservableCollection Items { get; } - - public string SelectedItem - { - get { return _selectedItem; } - set - { - _selectedItem = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem))); - } - } - - public event PropertyChangedEventHandler PropertyChanged; - - } - } -} +//// [Fact] +//// public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var rootNode = tree[0]; + +//// var from = rootNode.Children.Last(); +//// var to = rootNode.Children[0]; + +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + +//// ClickContainer(fromContainer, KeyModifiers.None); + +//// ClickContainer(toContainer, KeyModifiers.Shift); +//// AssertChildrenSelected(target, rootNode); + +//// ClickContainer(fromContainer, KeyModifiers.None); + +//// Assert.True(fromContainer.IsSelected); + +//// foreach (var child in rootNode.Children) +//// { +//// if (child == from) +//// { +//// continue; +//// } + +//// var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child); + +//// Assert.False(container.IsSelected); +//// } +//// } +//// } + +//// [Fact] +//// public void Setting_SelectedItem_Should_Set_Container_Selected() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var item = tree[0].Children[1].Children[0]; +//// var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); + +//// Assert.NotNull(container); + +//// target.SelectedItem = item; + +//// Assert.True(container.IsSelected); +//// } +//// } + +//// [Fact] +//// public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var item = tree[0].Children[1].Children[0]; + +//// var called = false; +//// target.SelectionChanged += (s, e) => +//// { +//// Assert.Empty(e.RemovedItems); +//// Assert.Equal(1, e.AddedItems.Count); +//// Assert.Same(item, e.AddedItems[0]); +//// called = true; +//// }; + +//// target.SelectedItem = item; +//// Assert.True(called); +//// } +//// } + +//// [Fact] +//// public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection() +//// { +//// using (Application()) +//// { +//// var dataContext = new TestDataContext(); + +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// DataContext = dataContext +//// }; + +//// target.Bind(TreeView.ItemsProperty, new Binding("Items")); +//// target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem")); + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// var selectedValues = new List(); + +//// dataContext.PropertyChanged += (_, e) => +//// { +//// if (e.PropertyName == nameof(TestDataContext.SelectedItem)) +//// selectedValues.Add(dataContext.SelectedItem); +//// }; +//// selectedValues.Add(dataContext.SelectedItem); + +//// _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); +//// _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); + +//// Assert.Equal(3, selectedValues.Count); +//// Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray()); +//// } +//// } + +//// [Fact] +//// public void LogicalChildren_Should_Be_Set() +//// { +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = new[] { "Foo", "Bar", "Baz " }, +//// }; + +//// ApplyTemplates(target); + +//// var result = target.GetLogicalChildren() +//// .OfType() +//// .Select(x => x.Header) +//// .OfType() +//// .Select(x => x.Text) +//// .ToList(); + +//// Assert.Equal(new[] { "Foo", "Bar", "Baz " }, result); +//// } + +//// [Fact] +//// public void Removing_Item_Should_Remove_Itself_And_Children_From_Index() +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var root = new TestRoot(); +//// root.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); + +//// tree[0].Children.RemoveAt(1); + +//// Assert.Equal(3, target.ItemContainerGenerator.Index.Containers.Count()); +//// } + +//// [Fact] +//// public void DataContexts_Should_Be_Correctly_Set() +//// { +//// var items = new object[] +//// { +//// "Foo", +//// new Node { Value = "Bar" }, +//// new TextBlock { Text = "Baz" }, +//// new TreeViewItem { Header = "Qux" }, +//// }; + +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// DataContext = "Base", +//// DataTemplates = +//// { +//// new FuncDataTemplate((x, _) => new Button { Content = x }) +//// }, +//// Items = items, +//// }; + +//// ApplyTemplates(target); + +//// var dataContexts = target.Presenter.Panel.Children +//// .Cast() +//// .Select(x => x.DataContext) +//// .ToList(); + +//// Assert.Equal( +//// new object[] { items[0], items[1], "Base", "Base" }, +//// dataContexts); +//// } + +//// [Fact] +//// public void Control_Item_Should_Not_Be_NameScope() +//// { +//// var items = new object[] +//// { +//// new TreeViewItem(), +//// }; + +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = items, +//// }; + +//// target.ApplyTemplate(); +//// target.Presenter.ApplyTemplate(); + +//// var item = target.Presenter.Panel.LogicalChildren[0]; +//// Assert.Null(NameScope.GetNameScope((TreeViewItem)item)); +//// } + +//// [Fact] +//// public void Should_React_To_Children_Changing() +//// { +//// var data = CreateTestTreeData(); + +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = data, +//// }; + +//// var root = new TestRoot(target); + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); +//// Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1)); +//// Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); + +//// // Make sure that the binding to Node.Children does not get collected. +//// GC.Collect(); + +//// data[0].Children = new AvaloniaList +//// { +//// new Node +//// { +//// Value = "NewChild1", +//// } +//// }; + +//// Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); +//// Assert.Equal(new[] { "NewChild1" }, ExtractItemHeader(target, 1)); +//// } + +//// [Fact] +//// public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node() +//// { +//// using (Application()) +//// { +//// var focus = FocusManager.Instance; +//// var navigation = AvaloniaLocator.Current.GetService(); +//// var data = CreateTestTreeData(); + +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = data, +//// }; + +//// var button = new Button(); + +//// var root = new TestRoot +//// { +//// Child = new StackPanel +//// { +//// Children = { target, button }, +//// } +//// }; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var item = data[0].Children[0]; +//// var node = target.ItemContainerGenerator.Index.ContainerFromItem(item); +//// Assert.NotNull(node); + +//// target.SelectedItem = item; +//// node.Focus(); +//// Assert.Same(node, focus.Current); + +//// navigation.Move(focus.Current, NavigationDirection.Next); +//// Assert.Same(button, focus.Current); + +//// navigation.Move(focus.Current, NavigationDirection.Next); +//// Assert.Same(node, focus.Current); +//// } +//// } + +//// [Fact] +//// public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree() +//// { +//// using (Application()) +//// { +//// var focus = FocusManager.Instance; +//// var navigation = AvaloniaLocator.Current.GetService(); +//// var data = CreateTestTreeData(); + +//// var selectedNode = new Node { Value = "Out of Tree Selected Item" }; + +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = data, +//// SelectedItem = selectedNode +//// }; + +//// var button = new Button(); + +//// var root = new TestRoot +//// { +//// Child = new StackPanel +//// { +//// Children = { target, button }, +//// } +//// }; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var item = data[0].Children[0]; +//// var node = target.ItemContainerGenerator.Index.ContainerFromItem(item); +//// Assert.NotNull(node); + +//// target.SelectedItem = selectedNode; +//// node.Focus(); +//// Assert.Same(node, focus.Current); + +//// var next = KeyboardNavigationHandler.GetNext(node, NavigationDirection.Previous); +//// } +//// } + +//// [Fact] +//// public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes() +//// { +//// using (UnitTestApplication.Start()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var rootNode = tree[0]; + +//// var keymap = AvaloniaLocator.Current.GetService(); +//// var selectAllGesture = keymap.SelectAll.First(); + +//// var keyEvent = new KeyEventArgs +//// { +//// RoutedEvent = InputElement.KeyDownEvent, +//// Key = selectAllGesture.Key, +//// KeyModifiers = selectAllGesture.KeyModifiers +//// }; + +//// target.RaiseEvent(keyEvent); + +//// AssertChildrenSelected(target, rootNode); +//// } +//// } + +//// [Fact] +//// public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var rootNode = tree[0]; + +//// var from = rootNode.Children[0]; +//// var to = rootNode.Children.Last(); + +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + +//// ClickContainer(fromContainer, KeyModifiers.None); +//// ClickContainer(toContainer, KeyModifiers.Shift); + +//// var keymap = AvaloniaLocator.Current.GetService(); +//// var selectAllGesture = keymap.SelectAll.First(); + +//// var keyEvent = new KeyEventArgs +//// { +//// RoutedEvent = InputElement.KeyDownEvent, +//// Key = selectAllGesture.Key, +//// KeyModifiers = selectAllGesture.KeyModifiers +//// }; + +//// target.RaiseEvent(keyEvent); + +//// AssertChildrenSelected(target, rootNode); +//// } +//// } + +//// [Fact] +//// public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var rootNode = tree[0]; + +//// var from = rootNode.Children.Last(); +//// var to = rootNode.Children[0]; + +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + +//// ClickContainer(fromContainer, KeyModifiers.None); +//// ClickContainer(toContainer, KeyModifiers.Shift); + +//// var keymap = AvaloniaLocator.Current.GetService(); +//// var selectAllGesture = keymap.SelectAll.First(); + +//// var keyEvent = new KeyEventArgs +//// { +//// RoutedEvent = InputElement.KeyDownEvent, +//// Key = selectAllGesture.Key, +//// KeyModifiers = selectAllGesture.KeyModifiers +//// }; + +//// target.RaiseEvent(keyEvent); + +//// AssertChildrenSelected(target, rootNode); +//// } +//// } + +//// [Fact] +//// public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() +//// { +//// using (UnitTestApplication.Start()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple, +//// }; +//// AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); +//// target.SelectAll(); + +//// AssertChildrenSelected(target, tree[0]); +//// Assert.Equal(5, target.SelectedItems.Count); + +//// _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + +//// Assert.Equal(5, target.SelectedItems.Count); +//// } +//// } + +//// [Fact] +//// public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); + +//// var rootNode = tree[0]; +//// var to = rootNode.Children[0]; +//// var then = rootNode.Children[1]; + +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); +//// var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then); + +//// ClickContainer(fromContainer, KeyModifiers.None); +//// ClickContainer(toContainer, KeyModifiers.Shift); + +//// Assert.Equal(2, target.SelectedItems.Count); + +//// _mouse.Click(thenContainer, MouseButton.Right); + +//// Assert.Equal(1, target.SelectedItems.Count); +//// } +//// } + +//// [Fact] +//// public void Shift_Right_Click_Should_Not_Select_Multiple() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); + +//// var rootNode = tree[0]; +//// var from = rootNode.Children[0]; +//// var to = rootNode.Children[1]; +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + +//// _mouse.Click(fromContainer); +//// _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift); + +//// Assert.Equal(1, target.SelectedItems.Count); +//// } +//// } + +//// [Fact] +//// public void Ctrl_Right_Click_Should_Not_Select_Multiple() +//// { +//// using (Application()) +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// SelectionMode = SelectionMode.Multiple, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); + +//// var rootNode = tree[0]; +//// var from = rootNode.Children[0]; +//// var to = rootNode.Children[1]; +//// var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); +//// var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + +//// _mouse.Click(fromContainer); +//// _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control); + +//// Assert.Equal(1, target.SelectedItems.Count); +//// } +//// } + +//// [Fact] +//// public void TreeViewItems_Level_Should_Be_Set() +//// { +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// Assert.Equal(0, GetItem(target, 0).Level); +//// Assert.Equal(1, GetItem(target, 0, 0).Level); +//// Assert.Equal(1, GetItem(target, 0, 1).Level); +//// Assert.Equal(1, GetItem(target, 0, 2).Level); +//// Assert.Equal(2, GetItem(target, 0, 1, 0).Level); +//// } + +//// [Fact] +//// public void TreeViewItems_Level_Should_Be_Set_For_Derived_TreeView() +//// { +//// var tree = CreateTestTreeData(); +//// var target = new DerivedTreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// Assert.Equal(0, GetItem(target, 0).Level); +//// Assert.Equal(1, GetItem(target, 0, 0).Level); +//// Assert.Equal(1, GetItem(target, 0, 1).Level); +//// Assert.Equal(1, GetItem(target, 0, 2).Level); +//// Assert.Equal(2, GetItem(target, 0, 1, 0).Level); +//// } + +//// [Fact] +//// public void Adding_Node_To_Removed_And_ReAdded_Parent_Should_Not_Crash() +//// { +//// // Issue #2985 +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var visualRoot = new TestRoot(); +//// visualRoot.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// var parent = tree[0]; +//// var node = parent.Children[1]; + +//// parent.Children.Remove(node); +//// parent.Children.Add(node); + +//// var item = target.ItemContainerGenerator.Index.ContainerFromItem(node); +//// ApplyTemplates(new[] { item }); + +//// // #2985 causes ArgumentException here. +//// node.Children.Add(new Node()); +//// } + +//// [Fact] +//// public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection() +//// { +//// // Issue #2980. +//// using (Application()) +//// { +//// var target = new DerivedTreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// SelectionMode = SelectionMode.Multiple, +//// Items = new List +//// { +//// new Node { Value = "Root1", }, +//// new Node { Value = "Root2", }, +//// }, +//// }; + +//// var visualRoot = new TestRoot +//// { +//// Styles = +//// { +//// new Style(x => x.OfType()) +//// { +//// Setters = +//// { +//// new Setter(TreeViewItem.IsExpandedProperty, true), +//// }, +//// }, +//// }, +//// Child = target, +//// }; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// _mouse.Click(GetItem(target, 0)); +//// _mouse.Click(GetItem(target, 1), modifiers: KeyModifiers.Shift); +//// } +//// } + +//// [Fact] +//// public void Removing_TreeView_From_Root_Should_Preserve_TreeViewItems() +//// { +//// // Issue #3328 +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var root = new TestRoot(); +//// root.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); +//// ExpandAll(target); + +//// Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); + +//// root.Child = null; + +//// Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); +//// Assert.Equal(1, target.Presenter.Panel.Children.Count); + +//// var rootNode = Assert.IsType(target.Presenter.Panel.Children[0]); +//// Assert.Equal(3, rootNode.ItemContainerGenerator.Containers.Count()); +//// Assert.Equal(3, rootNode.Presenter.Panel.Children.Count); + +//// var child2Node = Assert.IsType(rootNode.Presenter.Panel.Children[1]); +//// Assert.Equal(1, child2Node.ItemContainerGenerator.Containers.Count()); +//// Assert.Equal(1, child2Node.Presenter.Panel.Children.Count); +//// } + +//// [Fact] +//// public void Clearing_TreeView_Items_Clears_Index() +//// { +//// // Issue #3551 +//// var tree = CreateTestTreeData(); +//// var target = new TreeView +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// var root = new TestRoot(); +//// root.Child = target; + +//// CreateNodeDataTemplate(target); +//// ApplyTemplates(target); + +//// var rootNode = tree[0]; +//// var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode); + +//// Assert.NotNull(container); + +//// root.Child = null; + +//// tree.Clear(); + +//// Assert.Empty(target.ItemContainerGenerator.Index.Containers); +//// } + +//// [Fact] +//// public void Can_Use_Derived_TreeViewItem() +//// { +//// var tree = CreateTestTreeData(); +//// var target = new DerivedTreeViewWithDerivedTreeViewItems +//// { +//// Template = CreateTreeViewTemplate(), +//// Items = tree, +//// }; + +//// ApplyTemplates(target); + +//// // Verify that all items are DerivedTreeViewItem +//// VerifyItemType(target.ItemContainerGenerator); + +//// void VerifyItemType(ITreeItemContainerGenerator containerGenerator) +//// { +//// foreach (var container in containerGenerator.Index.Containers) +//// { +//// var item = Assert.IsType(container); +//// if (item.ItemCount > 0) +//// { +//// VerifyItemType(item.ItemContainerGenerator); +//// } +//// } +//// } +//// } + +//// private void ApplyTemplates(TreeView tree) +//// { +//// tree.ApplyTemplate(); +//// tree.Presenter.ApplyTemplate(); +//// ApplyTemplates(tree.Presenter.Panel.Children); +//// } + +//// private void ApplyTemplates(IEnumerable controls) +//// { +//// foreach (TreeViewItem control in controls) +//// { +//// control.Template = CreateTreeViewItemTemplate(); +//// control.ApplyTemplate(); +//// control.Presenter.ApplyTemplate(); +//// control.HeaderPresenter.ApplyTemplate(); +//// ApplyTemplates(control.Presenter.Panel.Children); +//// } +//// } + +//// private TreeViewItem GetItem(TreeView target, params int[] indexes) +//// { +//// var c = (ItemsControl)target; + +//// foreach (var index in indexes) +//// { +//// var item = ((IList)c.Items)[index]; +//// c = (ItemsControl)target.ItemContainerGenerator.Index.ContainerFromItem(item); +//// } + +//// return (TreeViewItem)c; +//// } + +//// private IList CreateTestTreeData() +//// { +//// return new AvaloniaList +//// { +//// new Node +//// { +//// Value = "Root", +//// Children = new AvaloniaList +//// { +//// new Node +//// { +//// Value = "Child1", +//// }, +//// new Node +//// { +//// Value = "Child2", +//// Children = new AvaloniaList +//// { +//// new Node +//// { +//// Value = "Grandchild2a", +//// }, +//// }, +//// }, +//// new Node +//// { +//// Value = "Child3", +//// } +//// } +//// } +//// }; +//// } + +//// private void CreateNodeDataTemplate(Control control) +//// { +//// control.DataTemplates.Add(new TestTreeDataTemplate()); +//// } + +//// private IControlTemplate CreateTreeViewTemplate() +//// { +//// return new FuncControlTemplate((parent, scope) => new ItemsPresenter +//// { +//// Name = "PART_ItemsPresenter", +//// }.RegisterInNameScope(scope)); +//// } + +//// private IControlTemplate CreateTreeViewItemTemplate() +//// { +//// return new FuncControlTemplate((parent, scope) => new Panel +//// { +//// Children = +//// { +//// new ContentPresenter +//// { +//// Name = "PART_HeaderPresenter", +//// [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty], +//// }.RegisterInNameScope(scope), +//// new ItemsPresenter +//// { +//// Name = "PART_ItemsPresenter", +//// }.RegisterInNameScope(scope) +//// } +//// }); +//// } + +//// private void ExpandAll(TreeView tree) +//// { +//// foreach (var i in tree.ItemContainerGenerator.Containers) +//// { +//// tree.ExpandSubTree((TreeViewItem)i.ContainerControl); +//// } +//// } + +//// private List ExtractItemHeader(TreeView tree, int level) +//// { +//// return ExtractItemContent(tree.Presenter.Panel, 0, level) +//// .Select(x => x.Header) +//// .OfType() +//// .Select(x => x.Text) +//// .ToList(); +//// } + +//// private IEnumerable ExtractItemContent(Panel panel, int currentLevel, int level) +//// { +//// foreach (TreeViewItem container in panel.Children) +//// { +//// if (container.Template == null) +//// { +//// container.Template = CreateTreeViewItemTemplate(); +//// container.ApplyTemplate(); +//// } + +//// if (currentLevel == level) +//// { +//// yield return container; +//// } +//// else +//// { +//// foreach (var child in ExtractItemContent(container.Presenter.Panel, currentLevel + 1, level)) +//// { +//// yield return child; +//// } +//// } +//// } +//// } + +//// private void ClickContainer(Control container, KeyModifiers modifiers) +//// { +//// _mouse.Click(container, modifiers: modifiers); +//// } + +//// private void AssertChildrenSelected(TreeView treeView, Node rootNode) +//// { +//// foreach (var child in rootNode.Children) +//// { +//// var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child); + +//// Assert.True(container.IsSelected); +//// } +//// } + +//// private IDisposable Application() +//// { +//// return UnitTestApplication.Start( +//// TestServices.MockThreadingInterface.With( +//// focusManager: new FocusManager(), +//// keyboardDevice: () => new KeyboardDevice(), +//// keyboardNavigation: new KeyboardNavigationHandler(), +//// inputManager: new InputManager())); +//// } + +//// private class Node : NotifyingBase +//// { +//// private IAvaloniaList _children; + +//// public string Value { get; set; } + +//// public IAvaloniaList Children +//// { +//// get +//// { +//// return _children; +//// } + +//// set +//// { +//// _children = value; +//// RaisePropertyChanged(nameof(Children)); +//// } +//// } +//// } + +//// private class TestTreeDataTemplate : ITreeDataTemplate +//// { +//// public Control Build(object param) +//// { +//// var node = (Node)param; +//// return new TextBlock { Text = node.Value }; +//// } + +//// public InstancedBinding ItemsSelector(object item) +//// { +//// var obs = ExpressionObserver.Create(item, o => (o as Node).Children); +//// return InstancedBinding.OneWay(obs); +//// } + +//// public bool Match(object data) +//// { +//// return data is Node; +//// } +//// } + +//// private class DerivedTreeView : TreeView +//// { +//// } + +//// private class DerivedTreeViewWithDerivedTreeViewItems : TreeView +//// { +//// protected override ITreeItemContainerGenerator CreateTreeItemContainerGenerator() => +//// CreateTreeItemContainerGenerator(); +//// } + +//// private class DerivedTreeViewItem : TreeViewItem +//// { +//// protected override IItemContainerGenerator CreateItemContainerGenerator() => CreateTreeItemContainerGenerator(); +//// } + +//// private class TestDataContext : INotifyPropertyChanged +//// { +//// private string _selectedItem; + +//// public TestDataContext() +//// { +//// Items = new ObservableCollection(Enumerable.Range(0, 5).Select(i => $"Item {i}")); +//// } + +//// public ObservableCollection Items { get; } + +//// public string SelectedItem +//// { +//// get { return _selectedItem; } +//// set +//// { +//// _selectedItem = value; +//// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem))); +//// } +//// } + +//// public event PropertyChangedEventHandler PropertyChanged; + +//// } +//// } +////} diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 9170c68e11..f46ad44684 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -361,7 +361,8 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TreeViewItems get realized. window.LayoutManager.ExecuteInitialLayoutPass(); - Assert.Single(target.ItemContainerGenerator.Containers); + throw new NotImplementedException(); + ////Assert.Single(target.ItemContainerGenerator.Containers); // Clear the content and ensure the TreeView is removed. window.Content = null; @@ -756,20 +757,22 @@ namespace Avalonia.LeakTests window.Show(); window.LayoutManager.ExecuteInitialLayoutPass(); - void AssertInitialItemState() - { - var item0 = (ListBoxItem)lb.ItemContainerGenerator.Containers.First().ContainerControl; - var canvas0 = (Canvas)item0.Presenter.Child; - Assert.Equal("foo", canvas0.Tag); - } + throw new NotImplementedException(); + + ////void AssertInitialItemState() + ////{ + //// var item0 = (ListBoxItem)lb.ItemContainerGenerator.Containers.First().ContainerControl; + //// var canvas0 = (Canvas)item0.Presenter.Child; + //// Assert.Equal("foo", canvas0.Tag); + ////} - Assert.Equal(10, lb.ItemContainerGenerator.Containers.Count()); - AssertInitialItemState(); + ////Assert.Equal(10, lb.ItemContainerGenerator.Containers.Count()); + ////AssertInitialItemState(); - items.Clear(); - window.LayoutManager.ExecuteLayoutPass(); + ////items.Clear(); + ////window.LayoutManager.ExecuteLayoutPass(); - Assert.Empty(lb.ItemContainerGenerator.Containers); + ////Assert.Empty(lb.ItemContainerGenerator.Containers); // Process all Loaded events to free control reference(s) Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);