Browse Source

Tidied up tree item container generators.

In doing so fixed a bug in DevTools where tree items would become
unselectable.
pull/386/head
Steven Kirk 11 years ago
parent
commit
78abef1551
  1. 19
      src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs
  2. 87
      src/Perspex.Controls/Generators/TreeContainerIndex.cs
  3. 106
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  4. 1
      src/Perspex.Controls/Perspex.Controls.csproj
  5. 8
      src/Perspex.Controls/TreeView.cs
  6. 2
      src/Perspex.Controls/TreeViewItem.cs
  7. 5
      tests/Perspex.Controls.UnitTests/TreeViewTests.cs

19
src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs

@ -11,23 +11,8 @@ namespace Perspex.Controls.Generators
public interface ITreeItemContainerGenerator : IItemContainerGenerator public interface ITreeItemContainerGenerator : IItemContainerGenerator
{ {
/// <summary> /// <summary>
/// Gets the item container for the root of the tree, or null if this generator is itself /// Gets the container index for the tree.
/// the root of the tree.
/// </summary> /// </summary>
ITreeItemContainerGenerator RootGenerator { get; } TreeContainerIndex Index { get; }
/// <summary>
/// Gets the item container for the specified item, anywhere in the tree.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The container, or null if not found.</returns>
IControl TreeContainerFromItem(object item);
/// <summary>
/// Gets the item for the specified item container, anywhere in the tree.
/// </summary>
/// <param name="container">The container.</param>
/// <returns>The item, or null if not found.</returns>
object TreeItemFromContainer(IControl container);
} }
} }

87
src/Perspex.Controls/Generators/TreeContainerIndex.cs

@ -0,0 +1,87 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
namespace Perspex.Controls.Generators
{
/// <summary>
/// Maintains an index of all item containers currently materialized by a <see cref="TreeView"/>.
/// </summary>
/// <remarks>
/// Each <see cref="TreeViewItem"/> has its own <see cref="TreeItemContainerGenerator{T}"/>
/// that maintains the list of its direct children, but they also share an instance of this
/// class in their <see cref="TreeItemContainerGenerator{T}.Index"/> property which tracks
/// the containers materialized for the entire tree.
/// </remarks>
public class TreeContainerIndex
{
private readonly Dictionary<object, IControl> _itemToContainer = new Dictionary<object, IControl>();
private readonly Dictionary<IControl, object> _containerToItem = new Dictionary<IControl, object>();
/// <summary>
/// Gets the currently materialized containers.
/// </summary>
public IEnumerable<IControl> Items => _containerToItem.Keys;
/// <summary>
/// Adds an entry to the index.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="container">The item container.</param>
public void Add(object item, IControl container)
{
_itemToContainer.Add(item, container);
_containerToItem.Add(container, item);
}
/// <summary>
/// Removes a container from the index.
/// </summary>
/// <param name="container">The item container.</param>
public void Remove(IControl container)
{
var item = _containerToItem[container];
_containerToItem.Remove(container);
_itemToContainer.Remove(item);
}
/// <summary>
/// Removes a set of containers from the index.
/// </summary>
/// <param name="containers">The item containers.</param>
public void Remove(IEnumerable<ItemContainer> containers)
{
foreach (var container in containers)
{
var item = _containerToItem[container.ContainerControl];
_containerToItem.Remove(container.ContainerControl);
_itemToContainer.Remove(item);
}
}
/// <summary>
/// Gets the container for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The container, or null of not found.</returns>
public IControl ContainerFromItem(object item)
{
IControl result;
_itemToContainer.TryGetValue(item, out result);
return result;
}
/// <summary>
/// Gets the item for a container.
/// </summary>
/// <param name="container">The container.</param>
/// <returns>The item, or null of not found.</returns>
public object ItemFromContainer(IControl container)
{
object result;
_containerToItem.TryGetValue(container, out result);
return result;
}
}
}

106
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved. // Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
@ -14,9 +15,6 @@ namespace Perspex.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 readonly Dictionary<object, T> _itemToContainer;
private readonly Dictionary<IControl, object> _containerToItem;
/// <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>
@ -24,34 +22,30 @@ namespace Perspex.Controls.Generators
/// <param name="contentProperty">The container's Content property.</param> /// <param name="contentProperty">The container's Content 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="rootGenerator"> /// <param name="index">The container index for the tree</param>
/// The item container for the root of the tree, or null if this generator is itself the
/// root of the tree.
/// </param>
public TreeItemContainerGenerator( public TreeItemContainerGenerator(
IControl owner, IControl owner,
PerspexProperty contentProperty, PerspexProperty contentProperty,
PerspexProperty itemsProperty, PerspexProperty itemsProperty,
PerspexProperty isExpandedProperty, PerspexProperty isExpandedProperty,
ITreeItemContainerGenerator rootGenerator) TreeContainerIndex index)
: base(owner, contentProperty) : base(owner, contentProperty)
{ {
Contract.Requires<ArgumentNullException>(owner != null);
Contract.Requires<ArgumentNullException>(contentProperty != null);
Contract.Requires<ArgumentNullException>(itemsProperty != null);
Contract.Requires<ArgumentNullException>(isExpandedProperty != null);
Contract.Requires<ArgumentNullException>(index != null);
ItemsProperty = itemsProperty; ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty; IsExpandedProperty = isExpandedProperty;
RootGenerator = rootGenerator; Index = index;
if (rootGenerator == null)
{
_itemToContainer = new Dictionary<object, T>();
_containerToItem = new Dictionary<IControl, object>();
}
} }
/// <summary> /// <summary>
/// Gets the item container for the root of the tree, or null if this generator is itself /// Gets the container index for the tree.
/// the root of the tree.
/// </summary> /// </summary>
public ITreeItemContainerGenerator RootGenerator { get; } public TreeContainerIndex Index { get; }
/// <summary> /// <summary>
/// Gets the item container's Items property. /// Gets the item container's Items property.
@ -63,30 +57,6 @@ namespace Perspex.Controls.Generators
/// </summary> /// </summary>
protected PerspexProperty IsExpandedProperty { get; } protected PerspexProperty IsExpandedProperty { get; }
/// <summary>
/// Gets the item container for the specified item, anywhere in the tree.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The container, or null if not found.</returns>
public IControl TreeContainerFromItem(object item)
{
T result;
_itemToContainer.TryGetValue(item, out result);
return result;
}
/// <summary>
/// Gets the item for the specified item container, anywhere in the tree.
/// </summary>
/// <param name="container">The container.</param>
/// <returns>The item, or null if not found.</returns>
public object TreeItemFromContainer(IControl container)
{
object result;
_containerToItem.TryGetValue(container, out result);
return result;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override IControl CreateContainer(object item) protected override IControl CreateContainer(object item)
{ {
@ -114,7 +84,7 @@ namespace Perspex.Controls.Generators
result.DataContext = item; result.DataContext = item;
} }
AddToIndex(item, result); Index.Add(item, result);
return result; return result;
} }
@ -122,59 +92,17 @@ namespace Perspex.Controls.Generators
public override IEnumerable<ItemContainer> Clear() public override IEnumerable<ItemContainer> Clear()
{ {
ClearIndex(); var items = base.Clear();
return base.Clear(); Index.Remove(items);
return items;
} }
public override IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count) public override IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count)
{ {
RemoveFromIndex(GetContainerRange(startingIndex, count)); Index.Remove(GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count); return base.Dematerialize(startingIndex, count);
} }
private void AddToIndex(object item, T container)
{
if (RootGenerator != null)
{
((TreeItemContainerGenerator<T>)RootGenerator).AddToIndex(item, container);
}
else
{
_itemToContainer.Add(item, container);
_containerToItem.Add(container, item);
}
}
private void RemoveFromIndex(IEnumerable<ItemContainer> containers)
{
if (RootGenerator != null)
{
((TreeItemContainerGenerator<T>)RootGenerator).RemoveFromIndex(containers);
}
else
{
foreach (var container in containers)
{
var item = _containerToItem[container.ContainerControl];
_containerToItem.Remove(container.ContainerControl);
_itemToContainer.Remove(item);
}
}
}
private void ClearIndex()
{
if (RootGenerator != null)
{
((TreeItemContainerGenerator<T>)RootGenerator).ClearIndex();
}
else
{
_containerToItem.Clear();
_itemToContainer.Clear();
}
}
/// <summary> /// <summary>
/// Gets the data template for the specified item. /// Gets the data template for the specified item.
/// </summary> /// </summary>

1
src/Perspex.Controls/Perspex.Controls.csproj

@ -46,6 +46,7 @@
<Compile Include="DockPanel.cs" /> <Compile Include="DockPanel.cs" />
<Compile Include="Expander.cs" /> <Compile Include="Expander.cs" />
<Compile Include="Generators\ItemContainer.cs" /> <Compile Include="Generators\ItemContainer.cs" />
<Compile Include="Generators\TreeContainerIndex.cs" />
<Compile Include="HotkeyManager.cs" /> <Compile Include="HotkeyManager.cs" />
<Compile Include="INameScope.cs" /> <Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" /> <Compile Include="IPseudoClasses.cs" />

8
src/Perspex.Controls/TreeView.cs

@ -58,7 +58,7 @@ namespace Perspex.Controls
TreeViewItem.HeaderProperty, TreeViewItem.HeaderProperty,
TreeViewItem.ItemsProperty, TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty, TreeViewItem.IsExpandedProperty,
null); new TreeContainerIndex());
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -101,13 +101,13 @@ namespace Perspex.Controls
bool rangeModifier = false, bool rangeModifier = false,
bool toggleModifier = false) bool toggleModifier = false)
{ {
var item = ItemContainerGenerator.TreeItemFromContainer(container); var item = ItemContainerGenerator.Index.ItemFromContainer(container);
if (item != null) if (item != null)
{ {
if (SelectedItem != null) if (SelectedItem != null)
{ {
var old = ItemContainerGenerator.TreeContainerFromItem(SelectedItem); var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem);
MarkContainerSelected(old, false); MarkContainerSelected(old, false);
} }
@ -162,7 +162,7 @@ namespace Perspex.Controls
if (item != null) if (item != null)
{ {
if (item.ItemContainerGenerator.RootGenerator == this.ItemContainerGenerator) if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index)
{ {
return item; return item;
} }

2
src/Perspex.Controls/TreeViewItem.cs

@ -79,7 +79,7 @@ namespace Perspex.Controls
TreeViewItem.HeaderProperty, TreeViewItem.HeaderProperty,
TreeViewItem.ItemsProperty, TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty, TreeViewItem.IsExpandedProperty,
_treeView?.ItemContainerGenerator); _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());
} }
/// <inheritdoc/> /// <inheritdoc/>

5
tests/Perspex.Controls.UnitTests/TreeViewTests.cs

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Perspex.Collections;
using Perspex.Controls.Presenters; using Perspex.Controls.Presenters;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Perspex.Input; using Perspex.Input;
@ -65,7 +66,7 @@ namespace Perspex.Controls.UnitTests
ApplyTemplates(target); ApplyTemplates(target);
var container = target.ItemContainerGenerator.TreeContainerFromItem( var container = target.ItemContainerGenerator.Index.ContainerFromItem(
tree[0].Children[1].Children[0]); tree[0].Children[1].Children[0]);
Assert.NotNull(container); Assert.NotNull(container);
@ -92,7 +93,7 @@ namespace Perspex.Controls.UnitTests
ApplyTemplates(target); ApplyTemplates(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.TreeContainerFromItem(item); var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container); Assert.NotNull(container);

Loading…
Cancel
Save