Browse Source

Make TreeView selection work etc.

Unfortunately stuff is still broken - I think ItemContainerGenerator
needs a refactor.
pull/278/head
Steven Kirk 11 years ago
parent
commit
6a005d35c8
  1. 6
      src/Perspex.Controls/Control.cs
  2. 6
      src/Perspex.Controls/Generators/IItemContainerGenerator.cs
  3. 14
      src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs
  4. 65
      src/Perspex.Controls/Generators/ItemContainerGenerator.cs
  5. 92
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  6. 2
      src/Perspex.Controls/Presenters/CarouselPresenter.cs
  7. 2
      src/Perspex.Controls/Presenters/ItemsPresenter.cs
  8. 70
      src/Perspex.Controls/TreeView.cs
  9. 108
      tests/Perspex.Controls.UnitTests/TreeViewTests.cs

6
src/Perspex.Controls/Control.cs

@ -406,7 +406,11 @@ namespace Perspex.Controls
base.OnAttachedToVisualTree(root);
IStyler styler = PerspexLocator.Current.GetService<IStyler>();
styler.ApplyStyles(this);
if (styler != null)
{
styler.ApplyStyles(this);
}
}
/// <summary>

6
src/Perspex.Controls/Generators/IItemContainerGenerator.cs

@ -43,9 +43,9 @@ namespace Perspex.Controls.Generators
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="items">The items.</param>
/// <returns>The removed controls.</returns>
IList<IControl> RemoveContainers(int startingIndex, IEnumerable items);
/// <param name="count">The the number of items to remove.</param>
/// <returns>The removed containers.</returns>
IList<IControl> RemoveContainers(int startingIndex, int count);
/// <summary>
/// Clears the created containers from the index and returns the removed controls.

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

@ -15,5 +15,19 @@ namespace Perspex.Controls.Generators
/// the root of the tree.
/// </summary>
ITreeItemContainerGenerator RootGenerator { 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);
}
}

65
src/Perspex.Controls/Generators/ItemContainerGenerator.cs

@ -15,7 +15,7 @@ namespace Perspex.Controls.Generators
/// </summary>
public class ItemContainerGenerator : IItemContainerGenerator
{
private Dictionary<int, IControl> _containers = new Dictionary<int, IControl>();
private List<IControl> _containers = new List<IControl>();
private readonly Subject<ItemContainers> _containersInitialized = new Subject<ItemContainers>();
@ -31,7 +31,7 @@ namespace Perspex.Controls.Generators
/// <summary>
/// Gets the currently realized containers.
/// </summary>
public IEnumerable<IControl> Containers => _containers.Values;
public IEnumerable<IControl> Containers => _containers;
/// <summary>
/// Signalled whenever new containers are initialized.
@ -81,24 +81,12 @@ namespace Perspex.Controls.Generators
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="items">The items.</param>
/// <param name="count">The the number of items to remove.</param>
/// <returns>The removed controls.</returns>
public IList<IControl> RemoveContainers(int startingIndex, IEnumerable items)
public virtual IList<IControl> RemoveContainers(int startingIndex, int count)
{
var result = new List<IControl>();
var count = items.Cast<object>().Count();
for (int i = startingIndex; i < startingIndex + count; ++i)
{
var container = _containers[i];
if (container != null)
{
result.Add(container);
_containers.Remove(i);
}
}
var result = _containers.GetRange(startingIndex, count);
_containers.RemoveRange(startingIndex, count);
return result;
}
@ -106,11 +94,11 @@ namespace Perspex.Controls.Generators
/// Clears the created containers from the index and returns the removed controls.
/// </summary>
/// <returns>The removed controls.</returns>
public IList<IControl> ClearContainers()
public virtual IList<IControl> ClearContainers()
{
var result = _containers;
_containers = new Dictionary<int, IControl>();
return result.Values.ToList();
_containers = new List<IControl>();
return result;
}
/// <summary>
@ -120,9 +108,12 @@ namespace Perspex.Controls.Generators
/// <returns>The container or null if no container created.</returns>
public IControl ContainerFromIndex(int index)
{
IControl result;
_containers.TryGetValue(index, out result);
return result;
if (index < _containers.Count)
{
return _containers[index];
}
return null;
}
/// <summary>
@ -132,15 +123,7 @@ namespace Perspex.Controls.Generators
/// <returns>The index of the container or -1 if not found.</returns>
public int IndexFromContainer(IControl container)
{
foreach (var i in _containers)
{
if (i.Value == container)
{
return i.Key;
}
}
return -1;
return _containers.IndexOf(container);
}
/// <summary>
@ -171,7 +154,16 @@ namespace Perspex.Controls.Generators
foreach (var c in container)
{
if (!_containers.ContainsKey(index))
while (_containers.Count < index)
{
_containers.Add(null);
}
if (_containers.Count == index)
{
_containers.Add(c);
}
else if (_containers[index] == null)
{
_containers[index] = c;
}
@ -183,5 +175,10 @@ namespace Perspex.Controls.Generators
++index;
}
}
protected IEnumerable<IControl> GetContainerRange(int index, int count)
{
return _containers.GetRange(index, count);
}
}
}

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

@ -1,6 +1,8 @@
// 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;
using System.Collections.Generic;
using Perspex.Controls.Templates;
namespace Perspex.Controls.Generators
@ -12,7 +14,8 @@ namespace Perspex.Controls.Generators
public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator
where T : class, IControl, new()
{
private ITreeItemContainerGenerator rootGenerator;
private Dictionary<object, T> _itemToContainer;
private Dictionary<IControl, object> _containerToItem;
/// <summary>
/// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class.
@ -36,6 +39,12 @@ namespace Perspex.Controls.Generators
ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty;
RootGenerator = rootGenerator;
if (rootGenerator == null)
{
_itemToContainer = new Dictionary<object, T>();
_containerToItem = new Dictionary<IControl, object>();
}
}
/// <summary>
@ -54,6 +63,30 @@ namespace Perspex.Controls.Generators
/// </summary>
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/>
protected override IControl CreateContainer(object item)
{
@ -81,10 +114,67 @@ namespace Perspex.Controls.Generators
result.DataContext = item;
}
AddToIndex(item, result);
return result;
}
}
public override IList<IControl> ClearContainers()
{
ClearIndex();
return base.ClearContainers();
}
public override IList<IControl> RemoveContainers(int startingIndex, int count)
{
RemoveFromIndex(GetContainerRange(startingIndex, count));
return base.RemoveContainers(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<IControl> containers)
{
if (RootGenerator != null)
{
((TreeItemContainerGenerator<T>)RootGenerator).RemoveFromIndex(containers);
}
else
{
foreach (var container in containers)
{
var item = _containerToItem[container];
_containerToItem.Remove(container);
_itemToContainer.Remove(item);
}
}
}
private void ClearIndex()
{
if (RootGenerator != null)
{
((TreeItemContainerGenerator<T>)RootGenerator).ClearIndex();
}
else
{
_containerToItem.Clear();
_itemToContainer.Clear();
}
}
/// <summary>
/// Gets the data template for the specified item.
/// </summary>

2
src/Perspex.Controls/Presenters/CarouselPresenter.cs

@ -215,7 +215,7 @@ namespace Perspex.Controls.Presenters
if (from != null)
{
Panel.Children.Remove(from);
generator.RemoveContainers(fromIndex, new[] { from });
generator.RemoveContainers(fromIndex, 1);
}
}

2
src/Perspex.Controls/Presenters/ItemsPresenter.cs

@ -248,7 +248,7 @@ namespace Perspex.Controls.Presenters
case NotifyCollectionChangedAction.Remove:
Panel.Children.RemoveAll(
generator.RemoveContainers(e.OldStartingIndex, e.OldItems));
generator.RemoveContainers(e.OldStartingIndex, e.OldItems.Count));
break;
}

70
src/Perspex.Controls/TreeView.cs

@ -7,6 +7,7 @@ using Perspex.Controls.Generators;
using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.Styling;
using Perspex.VisualTree;
namespace Perspex.Controls
@ -92,6 +93,23 @@ namespace Perspex.Controls
bool rangeModifier = false,
bool toggleModifier = false)
{
var item = ItemContainerGenerator.TreeItemFromContainer(container);
if (item != null)
{
if (SelectedItem != null)
{
var old = ItemContainerGenerator.TreeContainerFromItem(SelectedItem);
MarkContainerSelected(old, false);
}
SelectedItem = item;
if (SelectedItem != null)
{
MarkContainerSelected(container, true);
}
}
}
/// <summary>
@ -131,41 +149,45 @@ namespace Perspex.Controls
protected IControl GetContainerFromEventSource(IInteractive eventSource)
{
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
.OfType<ILogical>()
.FirstOrDefault(x => x.LogicalParent is TreeViewItem);
.OfType<TreeViewItem>()
.FirstOrDefault();
if (item != null)
{
var treeViewItem = (TreeViewItem)item.LogicalParent;
if (treeViewItem.ItemContainerGenerator.RootGenerator == this.ItemContainerGenerator)
if (item.ItemContainerGenerator.RootGenerator == this.ItemContainerGenerator)
{
return treeViewItem;
return item;
}
}
return null;
}
/// <inheritdoc/>
private void SelectedItemChanged(object selected)
/// <summary>
/// Sets a container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="selected">Whether the control is selected</param>
private void MarkContainerSelected(IControl container, bool selected)
{
//var containers = ItemContainerGenerator.GetAllContainers().OfType<ISelectable>();
//var selectedContainer = (selected != null) ?
// ItemContainerGenerator.ContainerFromItem(selected) :
// null;
//if (Presenter != null && Presenter.Panel != null)
//{
// KeyboardNavigation.SetTabOnceActiveElement(
// (InputElement)Presenter.Panel,
// selectedContainer);
//}
//foreach (var item in containers)
//{
// item.IsSelected = item == selectedContainer;
//}
var selectable = container as ISelectable;
var styleable = container as IStyleable;
if (selectable != null)
{
selectable.IsSelected = selected;
}
else if (styleable != null)
{
if (selected)
{
styleable.Classes.Add(":selected");
}
else
{
styleable.Classes.Remove(":selected");
}
}
}
}
}

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

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.LogicalTree;
using Xunit;
@ -24,9 +25,80 @@ namespace Perspex.Controls.UnitTests
target.ApplyTemplate();
Assert.Equal(new[] { "Root" }, ExtractItemContent(target, 0));
Assert.Equal(new[] { "Child1", "Child2" }, ExtractItemContent(target, 1));
Assert.Equal(new[] { "Grandchild2a" }, ExtractItemContent(target, 2));
Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
Assert.Equal(new[] { "Child1", "Child2" }, ExtractItemHeader(target, 1));
Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
}
[Fact]
public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers()
{
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
DataTemplates = CreateNodeDataTemplate(),
};
target.ApplyTemplate();
var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single();
var header = (TextBlock)container.Header;
Assert.Equal("Root", header.Text);
}
[Fact]
public void Root_TreeContainerFromItem_Should_Return_Descendent_Item()
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
DataTemplates = CreateNodeDataTemplate(),
};
// For TreeViewItem to find its parent TreeView, OnAttachedToVisualTree needs
// to be called, which requires an IRenderRoot.
var visualRoot = new TestRoot();
visualRoot.Child = target;
ApplyTemplates(target);
var container = target.ItemContainerGenerator.TreeContainerFromItem(
tree[0].Children[1].Children[0]);
var header = ((TreeViewItem)container).Header;
var headerContent = ((TextBlock)header).Text;
Assert.Equal("Grandchild2a", headerContent);
}
[Fact]
public void Clicking_Item_Should_Select_It()
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
DataTemplates = CreateNodeDataTemplate(),
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
ApplyTemplates(target);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.TreeContainerFromItem(item);
container.RaiseEvent(new PointerPressEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
}
[Fact]
@ -40,12 +112,14 @@ namespace Perspex.Controls.UnitTests
target.ApplyTemplate();
Assert.Equal(3, target.GetLogicalChildren().Count());
var result = target.GetLogicalChildren()
.OfType<TreeViewItem>()
.Select(x => x.Header)
.OfType<TextBlock>()
.Select(x => x.Text)
.ToList();
foreach (var child in target.GetLogicalChildren())
{
Assert.IsType<TreeViewItem>(child);
}
Assert.Equal(new[] { "Foo", "Bar", "Baz " }, result);
}
[Fact]
@ -82,6 +156,22 @@ namespace Perspex.Controls.UnitTests
dataContexts);
}
private void ApplyTemplates(TreeView tree)
{
tree.ApplyTemplate();
ApplyTemplates(tree.Presenter.Panel.Children);
}
private void ApplyTemplates(IEnumerable<IControl> controls)
{
foreach (TreeViewItem control in controls)
{
control.Template = CreateTreeViewItemTemplate();
control.ApplyTemplate();
ApplyTemplates(control.Presenter.Panel.Children);
}
}
private IList<Node> CreateTestTreeData()
{
return new[]
@ -139,7 +229,7 @@ namespace Perspex.Controls.UnitTests
});
}
private List<string> ExtractItemContent(TreeView tree, int level)
private List<string> ExtractItemHeader(TreeView tree, int level)
{
return ExtractItemContent(tree.Presenter.Panel, 0, level)
.Select(x => x.Header)

Loading…
Cancel
Save