Browse Source

Refactored ItemContainerGenerator<T>.

To allow TreeItemContainerGenerator to inherit from it.
pull/278/head
Steven Kirk 11 years ago
parent
commit
53926a47bf
  1. 2
      src/Perspex.Controls/DropDown.cs
  2. 20
      src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs
  3. 18
      src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
  4. 204
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  5. 3
      src/Perspex.Controls/ListBox.cs
  6. 10
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  7. 2
      src/Perspex.Controls/Primitives/TabStrip.cs
  8. 154
      src/Perspex.Controls/TreeView.cs
  9. 39
      src/Perspex.Controls/TreeViewItem.cs
  10. 2
      tests/Perspex.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
  11. 6
      tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

2
src/Perspex.Controls/DropDown.cs

@ -80,7 +80,7 @@ namespace Perspex.Controls
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<ListBoxItem>(this);
return new ItemContainerGenerator<ListBoxItem>(this, ListBoxItem.ContentProperty);
}
protected override void OnKeyDown(KeyEventArgs e)

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

@ -11,23 +11,9 @@ namespace Perspex.Controls.Generators
public interface ITreeItemContainerGenerator : IItemContainerGenerator
{
/// <summary>
/// Gets all of the generated container controls.
/// Gets the item container for the root of the tree, or null if this generator is itself
/// the root of the tree.
/// </summary>
/// <returns>The containers.</returns>
IEnumerable<IControl> GetAllContainers();
/// <summary>
/// Gets the item that is contained by the specified container.
/// </summary>
/// <param name="container">The container.</param>
/// <returns>The item.</returns>
object ItemFromContainer(IControl container);
/// <summary>
/// Gets the container for the specified item
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The container.</returns>
IControl ContainerFromItem(object item);
ITreeItemContainerGenerator RootGenerator { get; }
}
}

18
src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs

@ -1,6 +1,9 @@
// 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;
using System.Linq.Expressions;
using System.Reflection;
using Perspex.Controls.Templates;
namespace Perspex.Controls.Generators
@ -9,17 +12,26 @@ namespace Perspex.Controls.Generators
/// Creates containers for items and maintains a list of created containers.
/// </summary>
/// <typeparam name="T">The type of the container.</typeparam>
public class ItemContainerGenerator<T> : ItemContainerGenerator where T : class, IContentControl, new()
public class ItemContainerGenerator<T> : ItemContainerGenerator where T : class, IControl, new()
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator{T}"/> class.
/// </summary>
/// <param name="owner">The owner control.</param>
public ItemContainerGenerator(Control owner)
/// <param name="contentProperty">The container's Content property.</param>
public ItemContainerGenerator(
IControl owner,
PerspexProperty contentProperty)
: base(owner)
{
ContentProperty = contentProperty;
}
/// <summary>
/// Gets the container's Content property.
/// </summary>
protected PerspexProperty ContentProperty { get; }
/// <inheritdoc/>
protected override IControl CreateContainer(object item)
{
@ -36,7 +48,7 @@ namespace Perspex.Controls.Generators
else
{
var result = new T();
result.Content = Owner.MaterializeDataTemplate(item);
result.SetValue(ContentProperty, Owner.MaterializeDataTemplate(item));
if (!(item is IControl))
{

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

@ -1,11 +1,6 @@
// 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;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using Perspex.Controls.Templates;
namespace Perspex.Controls.Generators
@ -14,161 +9,53 @@ namespace Perspex.Controls.Generators
/// Creates containers for tree items and maintains a list of created containers.
/// </summary>
/// <typeparam name="T">The type of the container.</typeparam>
public class TreeItemContainerGenerator<T> : ITreeItemContainerGenerator where T : TreeViewItem, new()
public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator
where T : class, IControl, new()
{
private Dictionary<object, T> _containers = new Dictionary<object, T>();
private readonly Subject<ItemContainers> _containersInitialized = new Subject<ItemContainers>();
private ITreeItemContainerGenerator rootGenerator;
/// <summary>
/// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class.
/// </summary>
/// <param name="owner">The owner control.</param>
public TreeItemContainerGenerator(IControl owner)
{
Owner = owner;
}
/// <summary>
/// Gets the currently realized containers.
/// </summary>
public IEnumerable<IControl> Containers => _containers.Values;
/// <summary>
/// Signalled whenever new containers are initialized.
/// </summary>
public IObservable<ItemContainers> ContainersInitialized => _containersInitialized;
/// <summary>
/// Gets the owner control.
/// </summary>
public IControl Owner { get; }
/// <summary>
/// Creates container controls for a collection of items.
/// </summary>
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// <param name="contentProperty">The container's Content property.</param>
/// <param name="itemsProperty">The container's Items property.</param>
/// <param name="isExpandedProperty">The container's IsExpanded property.</param>
/// <param name="rootGenerator">
/// The item container for the root of the tree, or null if this generator is itself the
/// root of the tree.
/// </param>
/// <param name="items">The items.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created container controls.</returns>
public IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IMemberSelector selector)
public TreeItemContainerGenerator(
IControl owner,
PerspexProperty contentProperty,
PerspexProperty itemsProperty,
PerspexProperty isExpandedProperty,
ITreeItemContainerGenerator rootGenerator)
: base(owner, contentProperty)
{
Contract.Requires<ArgumentNullException>(items != null);
int index = startingIndex;
var result = new List<IControl>();
foreach (var item in items)
{
var i = selector != null ? selector.Select(item) : item;
var container = CreateContainer(i);
_containers.Add(i, container);
result.Add(container);
}
_containersInitialized.OnNext(new ItemContainers(startingIndex, result));
return result.Where(x => x != null).ToList();
ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty;
RootGenerator = rootGenerator;
}
/// <summary>
/// Removes a set of created containers from the index and returns the removed controls.
/// Gets the item container for the root of the tree, or null if this generator is itself
/// the root of the tree.
/// </summary>
/// <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>
public IList<IControl> RemoveContainers(int startingIndex, IEnumerable items)
{
var result = new List<IControl>();
foreach (var item in items)
{
T container;
if (_containers.TryGetValue(item, out container))
{
Remove(container, result);
}
}
return result;
}
public ITreeItemContainerGenerator RootGenerator { get; }
/// <summary>
/// Clears the created containers from the index and returns the removed controls.
/// Gets the item container's Items property.
/// </summary>
/// <returns>The removed controls.</returns>
public IList<IControl> ClearContainers()
{
var result = _containers;
_containers = new Dictionary<object, T>();
return result.Values.Cast<IControl>().ToList();
}
protected PerspexProperty ItemsProperty { get; }
/// <summary>
/// Gets the container control representing the item with the specified index.
/// Gets the item container's IsExpanded property.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The container or null if no container created.</returns>
public IControl ContainerFromIndex(int index)
{
throw new NotImplementedException();
}
protected PerspexProperty IsExpandedProperty { get; }
/// <summary>
/// Gets the index of the specified container control.
/// </summary>
/// <param name="container">The container.</param>
/// <returns>The index of the container or -1 if not found.</returns>
public int IndexFromContainer(IControl container)
{
throw new NotImplementedException();
}
/// <summary>
/// Gets all of the generated container controls.
/// </summary>
/// <returns>The containers.</returns>
public IEnumerable<IControl> GetAllContainers()
{
return _containers.Values;
}
/// <summary>
/// Gets the item that is contained by the specified container.
/// </summary>
/// <param name="container">The container.</param>
/// <returns>The item.</returns>
public object ItemFromContainer(IControl container)
{
return container.DataContext;
}
/// <summary>
/// Gets the container for the specified item
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The container.</returns>
public IControl ContainerFromItem(object item)
{
T result;
_containers.TryGetValue(item, out result);
return result;
}
/// <summary>
/// Creates the container for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The created container control.</returns>
protected virtual T CreateContainer(object item)
/// <inheritdoc/>
protected override IControl CreateContainer(object item)
{
var container = item as T;
@ -183,12 +70,11 @@ namespace Perspex.Controls.Generators
else
{
var template = GetTreeDataTemplate(item);
var result = new T
{
Header = template.Build(item),
Items = template.ItemsSelector(item),
IsExpanded = template.IsExpanded(item),
};
var result = new T();
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ItemsProperty, template.ItemsSelector(item));
result.SetValue(IsExpandedProperty, template.IsExpanded(item));
if (!(item is IControl))
{
@ -222,29 +108,5 @@ namespace Perspex.Controls.Generators
return treeTemplate;
}
private void Remove(T container, IList<IControl> removed)
{
if (container.Items != null)
{
foreach (var childItem in container.Items)
{
T childContainer;
if (_containers.TryGetValue(childItem, out childContainer))
{
Remove(childContainer, removed);
}
}
}
// TODO: Dual index.
var i = _containers.FirstOrDefault(x => x.Value == container);
if (i.Key != null)
{
_containers.Remove(i.Key);
}
}
}
}

3
src/Perspex.Controls/ListBox.cs

@ -7,6 +7,7 @@ using Perspex.Collections;
using Perspex.Controls.Generators;
using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Interactivity;
namespace Perspex.Controls
{
@ -43,7 +44,7 @@ namespace Perspex.Controls
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<ListBoxItem>(this);
return new ItemContainerGenerator<ListBoxItem>(this, ListBoxItem.ContentProperty);
}
/// <inheritdoc/>

10
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -388,8 +388,8 @@ namespace Perspex.Controls.Primitives
}
/// <summary>
/// Updates the selection based on an event source that may have originated in a container
/// that belongs to the control.
/// Updates the selection based on an event that may have originated in a container that
/// belongs to the control.
/// </summary>
/// <param name="eventSource">The control that raised the event.</param>
/// <param name="select">Whether the container should be selected or unselected.</param>
@ -405,11 +405,11 @@ namespace Perspex.Controls.Primitives
bool rangeModifier = false,
bool toggleModifier = false)
{
var item = GetContainerFromEventSource(eventSource);
var container = GetContainerFromEventSource(eventSource);
if (item != null)
if (container != null)
{
UpdateSelection(item, select, rangeModifier, toggleModifier);
UpdateSelection(container, select, rangeModifier, toggleModifier);
return true;
}

2
src/Perspex.Controls/Primitives/TabStrip.cs

@ -43,7 +43,7 @@ namespace Perspex.Controls.Primitives
}
else
{
result = new ItemContainerGenerator<TabItem>(this);
result = new ItemContainerGenerator<TabItem>(this, TabItem.ContentProperty);
}
return result;

154
src/Perspex.Controls/TreeView.cs

@ -6,11 +6,19 @@ using System.Linq;
using Perspex.Controls.Generators;
using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Interactivity;
using Perspex.VisualTree;
namespace Perspex.Controls
{
/// <summary>
/// Displays a hierachical tree of data.
/// </summary>
public class TreeView : ItemsControl
{
/// <summary>
/// Defines the <see cref="SelectedItem"/> property.
/// </summary>
public static readonly PerspexProperty<object> SelectedItemProperty =
SelectingItemsControl.SelectedItemProperty.AddOwner<TreeView>(
o => o.SelectedItem,
@ -18,62 +26,146 @@ namespace Perspex.Controls
private object _selectedItem;
static TreeView()
{
SelectedItemProperty.Changed.Subscribe(x =>
{
var control = x.Sender as TreeView;
if (control != null)
{
control.SelectedItemChanged(x.NewValue);
}
});
}
public new ITreeItemContainerGenerator ItemContainerGenerator => (ITreeItemContainerGenerator)base.ItemContainerGenerator;
/// <summary>
/// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
/// </summary>
public new ITreeItemContainerGenerator ItemContainerGenerator =>
(ITreeItemContainerGenerator)base.ItemContainerGenerator;
/// <summary>
/// Gets or sets the selected item.
/// </summary>
public object SelectedItem
{
get { return _selectedItem; }
set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new TreeItemContainerGenerator<TreeViewItem>(this);
return new TreeItemContainerGenerator<TreeViewItem>(
this,
TreeViewItem.HeaderProperty,
TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty,
null);
}
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
var control = (IControl)e.Source;
var item = ItemContainerGenerator.ItemFromContainer(control);
if (e.NavigationMethod == NavigationMethod.Directional)
{
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.InputModifiers & InputModifiers.Shift) != 0);
}
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressEventArgs e)
{
base.OnPointerPressed(e);
if (item != null)
if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right)
{
SelectedItem = item;
e.Handled = true;
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.InputModifiers & InputModifiers.Shift) != 0,
(e.InputModifiers & InputModifiers.Control) != 0);
}
}
private void SelectedItemChanged(object selected)
/// <summary>
/// Updates the selection for an item based on user interaction.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="select">Whether the item should be selected or unselected.</param>
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
protected void UpdateSelectionFromContainer(
IControl container,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false)
{
}
/// <summary>
/// Updates the selection based on an event that may have originated in a container that
/// belongs to the control.
/// </summary>
/// <param name="eventSource">The control that raised the event.</param>
/// <param name="select">Whether the container should be selected or unselected.</param>
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <returns>
/// True if the event originated from a container that belongs to the control; otherwise
/// false.
/// </returns>
protected bool UpdateSelectionFromEventSource(
IInteractive eventSource,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false)
{
var containers = ItemContainerGenerator.GetAllContainers().OfType<ISelectable>();
var selectedContainer = (selected != null) ?
ItemContainerGenerator.ContainerFromItem(selected) :
null;
var container = GetContainerFromEventSource(eventSource);
if (Presenter != null && Presenter.Panel != null)
if (container != null)
{
KeyboardNavigation.SetTabOnceActiveElement(
(InputElement)Presenter.Panel,
selectedContainer);
UpdateSelectionFromContainer(container, select, rangeModifier, toggleModifier);
return true;
}
foreach (var item in containers)
return false;
}
/// <summary>
/// Tries to get the container that was the source of an event.
/// </summary>
/// <param name="eventSource">The control that raised the event.</param>
/// <returns>The container or null if the event did not originate in a container.</returns>
protected IControl GetContainerFromEventSource(IInteractive eventSource)
{
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
.OfType<ILogical>()
.FirstOrDefault(x => x.LogicalParent is TreeViewItem);
if (item != null)
{
item.IsSelected = item == selectedContainer;
var treeViewItem = (TreeViewItem)item.LogicalParent;
if (treeViewItem.ItemContainerGenerator.RootGenerator == this.ItemContainerGenerator)
{
return treeViewItem;
}
}
return null;
}
/// <inheritdoc/>
private void SelectedItemChanged(object 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;
//}
}
}
}

39
src/Perspex.Controls/TreeViewItem.cs

@ -1,10 +1,9 @@
// 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;
using System.Linq;
using Perspex.Controls.Mixins;
using Perspex.Controls.Generators;
using Perspex.Controls.Mixins;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
@ -66,39 +65,31 @@ namespace Perspex.Controls
set { SetValue(IsSelectedProperty, value); }
}
/// <summary>
/// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
/// </summary>
public new ITreeItemContainerGenerator ItemContainerGenerator =>
(ITreeItemContainerGenerator)base.ItemContainerGenerator;
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
if (_treeView == null)
{
throw new InvalidOperationException(
"Cannot get the ItemContainerGenerator for a TreeViewItem " +
"before it is added to a TreeView.");
}
return _treeView.ItemContainerGenerator;
return new TreeItemContainerGenerator<TreeViewItem>(
this,
TreeViewItem.HeaderProperty,
TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty,
_treeView?.ItemContainerGenerator);
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
{
base.OnAttachedToVisualTree(root);
if (this.GetVisualParent() != null)
{
_treeView = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault();
if (_treeView == null)
{
throw new InvalidOperationException("TreeViewItems must be added to a TreeView.");
}
}
else
{
_treeView = null;
}
_treeView = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault();
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
if (!e.Handled)

2
tests/Perspex.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

@ -74,7 +74,7 @@ namespace Perspex.Controls.UnitTests.Presenters
{
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<TestItem>(this);
return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty);
}
}
}

6
tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -39,7 +39,9 @@ namespace Perspex.Controls.UnitTests.Presenters
Items = new[] { "foo", "bar" },
};
target.ItemContainerGenerator = new ItemContainerGenerator<ListBoxItem>(target);
target.ItemContainerGenerator = new ItemContainerGenerator<ListBoxItem>(
target,
ListBoxItem.ContentProperty);
target.ApplyTemplate();
Assert.Equal(2, target.Panel.Children.Count);
@ -231,7 +233,7 @@ namespace Perspex.Controls.UnitTests.Presenters
{
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<TestItem>(this);
return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty);
}
}
}

Loading…
Cancel
Save