Browse Source

Update tree item container index dynamically.

Update `TreeItemContainerGenerator.Index` when a `TreeViewItem` is added to or removed from the logical tree. This ensures that removed `TreeViewItem`s won't try to add duplicate containers to the index.

Fixes #2985
pull/3216/head
Steven Kirk 7 years ago
parent
commit
15dfb88fe7
  1. 5
      src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
  2. 49
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  3. 3
      src/Avalonia.Controls/TreeView.cs
  4. 9
      src/Avalonia.Controls/TreeViewItem.cs
  5. 7
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

5
src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs

@ -12,5 +12,10 @@ namespace Avalonia.Controls.Generators
/// Gets the container index for the tree. /// Gets the container index for the tree.
/// </summary> /// </summary>
TreeContainerIndex Index { get; } TreeContainerIndex Index { get; }
/// <summary>
/// Updates the index based on the parent <see cref="TreeView"/>.
/// </summary>
void UpdateIndex();
} }
} }

49
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -3,8 +3,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -15,6 +17,8 @@ namespace Avalonia.Controls.Generators
public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator
where T : class, IControl, new() where T : class, IControl, new()
{ {
private TreeView _treeView;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class. /// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class.
/// </summary> /// </summary>
@ -23,31 +27,28 @@ namespace Avalonia.Controls.Generators
/// <param name="contentTemplateProperty">The container's ContentTemplate property.</param> /// <param name="contentTemplateProperty">The container's ContentTemplate property.</param>
/// <param name="itemsProperty">The container's Items property.</param> /// <param name="itemsProperty">The container's Items property.</param>
/// <param name="isExpandedProperty">The container's IsExpanded property.</param> /// <param name="isExpandedProperty">The container's IsExpanded property.</param>
/// <param name="index">The container index for the tree</param>
public TreeItemContainerGenerator( public TreeItemContainerGenerator(
IControl owner, IControl owner,
AvaloniaProperty contentProperty, AvaloniaProperty contentProperty,
AvaloniaProperty contentTemplateProperty, AvaloniaProperty contentTemplateProperty,
AvaloniaProperty itemsProperty, AvaloniaProperty itemsProperty,
AvaloniaProperty isExpandedProperty, AvaloniaProperty isExpandedProperty)
TreeContainerIndex index)
: base(owner, contentProperty, contentTemplateProperty) : base(owner, contentProperty, contentTemplateProperty)
{ {
Contract.Requires<ArgumentNullException>(owner != null); Contract.Requires<ArgumentNullException>(owner != null);
Contract.Requires<ArgumentNullException>(contentProperty != null); Contract.Requires<ArgumentNullException>(contentProperty != null);
Contract.Requires<ArgumentNullException>(itemsProperty != null); Contract.Requires<ArgumentNullException>(itemsProperty != null);
Contract.Requires<ArgumentNullException>(isExpandedProperty != null); Contract.Requires<ArgumentNullException>(isExpandedProperty != null);
Contract.Requires<ArgumentNullException>(index != null);
ItemsProperty = itemsProperty; ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty; IsExpandedProperty = isExpandedProperty;
Index = index; UpdateIndex();
} }
/// <summary> /// <summary>
/// Gets the container index for the tree. /// Gets the container index for the tree.
/// </summary> /// </summary>
public TreeContainerIndex Index { get; } public TreeContainerIndex Index { get; private set; }
/// <summary> /// <summary>
/// Gets the item container's Items property. /// Gets the item container's Items property.
@ -70,7 +71,7 @@ namespace Avalonia.Controls.Generators
} }
else if (container != null) else if (container != null)
{ {
Index.Add(item, container); Index?.Add(item, container);
return container; return container;
} }
else else
@ -92,7 +93,7 @@ namespace Avalonia.Controls.Generators
result.DataContext = item; result.DataContext = item;
} }
Index.Add(item, result); Index?.Add(item, result);
return result; return result;
} }
@ -101,24 +102,50 @@ namespace Avalonia.Controls.Generators
public override IEnumerable<ItemContainerInfo> Clear() public override IEnumerable<ItemContainerInfo> Clear()
{ {
var items = base.Clear(); var items = base.Clear();
Index.Remove(0, items); Index?.Remove(0, items);
return items; return items;
} }
public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count) public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
{ {
Index.Remove(startingIndex, GetContainerRange(startingIndex, count)); Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count); return base.Dematerialize(startingIndex, count);
} }
public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count) public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
{ {
Index.Remove(startingIndex, GetContainerRange(startingIndex, count)); Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.RemoveRange(startingIndex, count); return base.RemoveRange(startingIndex, count);
} }
public override bool TryRecycle(int oldIndex, int newIndex, object item) => false; 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 if (Owner.IsAttachedToLogicalTree)
{
var treeView = Owner.GetSelfAndLogicalAncestors().OfType<TreeView>().FirstOrDefault();
if (treeView != _treeView)
{
Clear();
Index = treeView?.ItemContainerGenerator?.Index;
_treeView = treeView;
}
}
else
{
Clear();
Index = null;
_treeView = null;
}
}
class WrapperTreeDataTemplate : ITreeDataTemplate class WrapperTreeDataTemplate : ITreeDataTemplate
{ {
private readonly IDataTemplate _inner; private readonly IDataTemplate _inner;

3
src/Avalonia.Controls/TreeView.cs

@ -393,8 +393,7 @@ namespace Avalonia.Controls
TreeViewItem.HeaderProperty, TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty, TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty, TreeViewItem.IsExpandedProperty);
new TreeContainerIndex());
result.Index.Materialized += ContainerMaterialized; result.Index.Materialized += ContainerMaterialized;
return result; return result;
} }

9
src/Avalonia.Controls/TreeViewItem.cs

@ -98,17 +98,18 @@ namespace Avalonia.Controls
TreeViewItem.HeaderProperty, TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty, TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty, TreeViewItem.IsExpandedProperty);
_treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
_treeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault(); _treeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault();
Level = CalculateDistanceFromLogicalParent<TreeView>(this) - 1; Level = CalculateDistanceFromLogicalParent<TreeView>(this) - 1;
ItemContainerGenerator.UpdateIndex();
if (ItemTemplate == null && _treeView?.ItemTemplate != null) if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{ {
@ -119,7 +120,7 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
ItemContainerGenerator.Clear(); ItemContainerGenerator.UpdateIndex();
} }
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)

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

@ -10,6 +10,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Diagnostics;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -33,6 +34,8 @@ namespace Avalonia.Controls.UnitTests
Items = CreateTestTreeData(), Items = CreateTestTreeData(),
}; };
var root = new TestRoot(target);
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
@ -77,6 +80,8 @@ namespace Avalonia.Controls.UnitTests
Items = CreateTestTreeData(), Items = CreateTestTreeData(),
}; };
var root = new TestRoot(target);
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
@ -527,6 +532,8 @@ namespace Avalonia.Controls.UnitTests
Items = data, Items = data,
}; };
var root = new TestRoot(target);
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);

Loading…
Cancel
Save