Browse Source

Started refactoring SelectingItemsControl.

pull/69/head
Steven Kirk 11 years ago
parent
commit
ff331693f0
  1. 6
      Perspex.Base/PerspexProperty.cs
  2. 68
      Perspex.Controls/Generators/IItemContainerGenerator.cs
  3. 231
      Perspex.Controls/Generators/ItemContainerGenerator.cs
  4. 37
      Perspex.Controls/Generators/ItemContainers.cs
  5. 139
      Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  6. 2
      Perspex.Controls/Generators/TypedItemContainerGenerator.cs
  7. 22
      Perspex.Controls/ItemsControl.cs
  8. 5
      Perspex.Controls/Menu.cs
  9. 6
      Perspex.Controls/MenuItem.cs
  10. 1
      Perspex.Controls/Perspex.Controls.csproj
  11. 20
      Perspex.Controls/Presenters/DeckPresenter.cs
  12. 11
      Perspex.Controls/Presenters/ItemsPresenter.cs
  13. 343
      Perspex.Controls/Primitives/SelectingItemsControl.cs
  14. 18
      Perspex.Controls/Primitives/TabStrip.cs
  15. 27
      Perspex.Controls/Templates/DataTemplateExtensions.cs
  16. 260
      Perspex.Controls/TreeView.cs
  17. 6
      Tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  18. 74
      Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

6
Perspex.Base/PerspexProperty.cs

@ -77,8 +77,12 @@ namespace Perspex
this.defaultValues.Add(ownerType, defaultValue); this.defaultValues.Add(ownerType, defaultValue);
this.Inherits = inherits; this.Inherits = inherits;
this.DefaultBindingMode = defaultBindingMode; this.DefaultBindingMode = defaultBindingMode;
this.validation.Add(ownerType, validate);
this.IsAttached = isAttached; this.IsAttached = isAttached;
if (validate != null)
{
this.validation.Add(ownerType, validate);
}
} }
/// <summary> /// <summary>

68
Perspex.Controls/Generators/IItemContainerGenerator.cs

@ -1,6 +1,6 @@
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// <copyright file="IItemContainerGenerator.cs" company="Steven Kirk"> // <copyright file="IItemContainerGenerator.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information. // Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@ -9,30 +9,60 @@ namespace Perspex.Controls.Generators
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Templates;
public enum ItemContainerGeneratorState /// <summary>
{ /// Creates containers for items and maintains a list of created containers.
NoStarted, /// </summary>
Generating,
Generated,
}
public interface IItemContainerGenerator public interface IItemContainerGenerator
{ {
event EventHandler StateChanged; /// <summary>
/// Signalled whenever new containers are initialized.
ItemContainerGeneratorState State { get; } /// </summary>
IObservable<ItemContainers> ContainersInitialized { get; }
Control GetContainerForItem(object item);
object GetItemForContainer(Control container); /// <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>
/// <param name="items">The items.</param>
/// <param name="itemTemplate">An optional item template.</param>
/// <returns>The created controls.</returns>
IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IDataTemplate itemTemplate);
IEnumerable<Tuple<object, Control>> GetAll(); /// <summary>
/// Removes a set of created containers from the index and returns the removed controls.
/// </summary>
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="count">The number of items to remove.</param>
/// <returns>The removed controls.</returns>
IList<IControl> RemoveContainers(int startingIndex, int count);
IEnumerable<Control> Generate(IEnumerable items); /// <summary>
/// Clears the created containers from the index and returns the removed controls.
/// </summary>
/// <returns>The removed controls.</returns>
IList<IControl> ClearContainers();
IEnumerable<Control> Remove(IEnumerable item); /// <summary>
/// Gets the container control representing the item with the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The container, or null if no container created.</returns>
IControl ContainerFromIndex(int index);
void RemoveAll(); /// <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>
int IndexFromContainer(IControl container);
} }
} }

231
Perspex.Controls/Generators/ItemContainerGenerator.cs

@ -1,6 +1,6 @@
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// <copyright file="ItemContainerGenerator.cs" company="Steven Kirk"> // <copyright file="ItemContainerGenerator.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information. // Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@ -10,145 +10,182 @@ namespace Perspex.Controls.Generators
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Subjects;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
/// <summary>
/// Creates containers for items and maintains a list of created containers.
/// </summary>
public class ItemContainerGenerator : IItemContainerGenerator public class ItemContainerGenerator : IItemContainerGenerator
{ {
private Dictionary<object, Control> containersByItem = new Dictionary<object, Control>(); private Dictionary<int, IControl> containers = new Dictionary<int, IControl>();
private Dictionary<Control, object> itemsByContainer = new Dictionary<Control, object>(); private Subject<ItemContainers> containersInitialized;
private ItemContainerGeneratorState state; /// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
public ItemContainerGenerator(Control owner) /// </summary>
/// <param name="owner">The owner control.</param>
public ItemContainerGenerator(IControl owner)
{ {
this.Owner = owner; this.Owner = owner;
this.containersInitialized = new Subject<ItemContainers>();
} }
public event EventHandler StateChanged; /// <summary>
/// Signalled whenever new containers are initialized.
public ItemContainerGeneratorState State /// </summary>
public IObservable<ItemContainers> ContainersInitialized => this.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>
/// <param name="items">The items.</param>
/// <param name="itemTemplate">An optional item template.</param>
/// <returns>The created container controls.</returns>
public IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IDataTemplate itemTemplate)
{ {
get Contract.Requires<ArgumentNullException>(items != null);
int index = startingIndex;
var result = new List<IControl>();
foreach (var item in items)
{ {
return this.state; IControl container = this.CreateContainer(item, itemTemplate);
result.Add(container);
} }
private set this.AddContainers(startingIndex, result);
{ this.containersInitialized.OnNext(new ItemContainers(startingIndex, result));
if (this.state != value)
{
this.state = value;
if (this.StateChanged != null) return result.Where(x => x != null).ToList();
{
this.StateChanged(this, EventArgs.Empty);
}
}
}
} }
protected Control Owner /// <summary>
/// Removes a set of created containers from the index and returns the removed controls.
/// </summary>
/// <param name="startingIndex">
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="count">The number of items to remove.</param>
/// <returns>The removed controls.</returns>
public IList<IControl> RemoveContainers(int startingIndex, int count)
{ {
get; var result = new List<IControl>();
private set;
} for (int i = startingIndex; i < startingIndex + count; ++i)
{
var container = this.containers[i];
if (container != null)
{
result.Add(container);
this.containers[i] = null;
}
}
public Control GetContainerForItem(object item)
{
Control result;
this.containersByItem.TryGetValue(item, out result);
return result; return result;
} }
public object GetItemForContainer(Control container) /// <summary>
/// Clears the created containers from the index and returns the removed controls.
/// </summary>
/// <returns>The removed controls.</returns>
public IList<IControl> ClearContainers()
{ {
object result; var result = this.containers;
this.itemsByContainer.TryGetValue(container, out result); this.containers = new Dictionary<int, IControl>();
return result; return result.Values.ToList();
} }
public IEnumerable<Tuple<object, Control>> GetAll() /// <summary>
/// Gets the container control representing the item with the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The container or null if no container created.</returns>
public IControl ContainerFromIndex(int index)
{ {
return this.containersByItem.Select(x => Tuple.Create(x.Key, x.Value)); IControl result;
this.containers.TryGetValue(index, out result);
return result;
} }
IEnumerable<Control> IItemContainerGenerator.Generate(IEnumerable items) /// <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)
{ {
List<Control> result = new List<Control>(); foreach (var i in this.containers)
this.State = ItemContainerGeneratorState.Generating;
try
{ {
foreach (object item in items) if (i.Value == container)
{ {
Control container = this.CreateContainerOverride(item); return i.Key;
if (container != null)
{
if (container.DataContext == null)
{
container.DataContext = item;
}
container.TemplatedParent = null;
this.AddInternal(item, container);
result.Add(container);
}
} }
} }
finally
{
this.State = ItemContainerGeneratorState.Generated;
}
return result; return -1;
} }
IEnumerable<Control> IItemContainerGenerator.Remove(IEnumerable items) /// <summary>
/// Creates the container for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="itemTemplate">An optional item template.</param>
/// <returns>The created container control.</returns>
protected virtual IControl CreateContainer(object item, IDataTemplate itemTemplate)
{ {
List<Control> result = new List<Control>(); if (item == null)
foreach (var item in items)
{ {
result.Add(this.RemoveByItemInternal(item)); return null;
}
else if (itemTemplate != null && itemTemplate.Match(item))
{
var result = itemTemplate.Build(item);
result.DataContext = item;
return result;
}
else
{
return this.Owner.MaterializeDataTemplate(item);
} }
return result;
}
void IItemContainerGenerator.RemoveAll()
{
this.containersByItem.Clear();
this.itemsByContainer.Clear();
}
protected virtual Control CreateContainerOverride(object item)
{
return (Control)this.Owner.MaterializeDataTemplate(item);
} }
protected void AddInternal(object item, Control container) /// <summary>
/// Adds a collection of containers to the index.
/// </summary>
/// <param name="index">The starting index.</param>
/// <param name="container">The container.</param>
protected void AddContainers(int index, IList<IControl> container)
{ {
this.containersByItem.Add(item, container); Contract.Requires<ArgumentNullException>(container != null);
this.itemsByContainer.Add(container, item);
}
protected object RemoveByContainerInternal(Control container) foreach (var c in container)
{ {
object item = this.itemsByContainer[container]; if (!this.containers.ContainsKey(index))
this.containersByItem.Remove(item); {
this.itemsByContainer.Remove(container); this.containers[index] = c;
return item; }
} else
{
throw new InvalidOperationException("Container already created.");
}
protected Control RemoveByItemInternal(object item) ++index;
{ }
Control container = this.containersByItem[item];
this.containersByItem.Remove(item);
this.itemsByContainer.Remove(container);
return container;
} }
} }
} }

37
Perspex.Controls/Generators/ItemContainers.cs

@ -0,0 +1,37 @@
// -----------------------------------------------------------------------
// <copyright file="ItemContainers.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls.Generators
{
using System.Collections.Generic;
/// <summary>
/// Holds details about a set of item containers in an <see cref="IItemContainerGenerator"/>.
/// </summary>
public class ItemContainers
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainers"/> class.
/// </summary>
/// <param name="startingIndex">The index of the first container in the source items.</param>
/// <param name="containers">The containers.</param>
public ItemContainers(int startingIndex, IList<IControl> containers)
{
this.StartingIndex = startingIndex;
this.Items = containers;
}
/// <summary>
/// Gets the index of the first container in the source items.
/// </summary>
public int StartingIndex { get; }
/// <summary>
/// Gets the containers. May contain null entries.
/// </summary>
public IList<IControl> Items { get; }
}
}

139
Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -19,74 +19,75 @@ namespace Perspex.Controls.Generators
{ {
} }
IEnumerable<Control> IItemContainerGenerator.Remove(IEnumerable items) ////IEnumerable<Control> IItemContainerGenerator.Remove(IEnumerable items)
{ ////{
var result = new List<Control>(); //// throw new NotImplementedException();
//// var result = new List<Control>();
foreach (var item in items)
{ //// foreach (var item in items)
var container = (T)this.GetContainerForItem(item); //// {
this.Remove(container, result); //// var container = (T)this.GetContainerForItem(item);
} //// this.Remove(container, result);
//// }
return result;
} //// return result;
////}
protected override Control CreateContainerOverride(object item)
{ ////protected override Control CreateContainerOverride(object item)
T result = item as T; ////{
//// T result = item as T;
if (result == null)
{ //// if (result == null)
TreeDataTemplate template = this.GetTreeDataTemplate(item); //// {
//// TreeDataTemplate template = this.GetTreeDataTemplate(item);
result = new T
{ //// result = new T
Header = template.Build(item), //// {
Items = template.ItemsSelector(item), //// Header = template.Build(item),
IsExpanded = template.IsExpanded(item), //// Items = template.ItemsSelector(item),
}; //// IsExpanded = template.IsExpanded(item),
} //// };
//// }
return result;
} //// return result;
////}
private TreeDataTemplate GetTreeDataTemplate(object item)
{ ////private TreeDataTemplate GetTreeDataTemplate(object item)
IDataTemplate template = this.Owner.FindDataTemplate(item); ////{
//// IDataTemplate template = this.Owner.FindDataTemplate(item);
if (template == null)
{ //// if (template == null)
template = DataTemplate.Default; //// {
} //// template = DataTemplate.Default;
//// }
TreeDataTemplate treeTemplate = template as TreeDataTemplate;
//// TreeDataTemplate treeTemplate = template as TreeDataTemplate;
if (treeTemplate == null)
{ //// if (treeTemplate == null)
treeTemplate = new TreeDataTemplate(template.Build, x => null); //// {
} //// treeTemplate = new TreeDataTemplate(template.Build, x => null);
//// }
return treeTemplate;
} //// return treeTemplate;
////}
private void Remove(T container, List<Control> removed)
{ ////private void Remove(T container, List<Control> removed)
if (container.Items != null) ////{
{ //// if (container.Items != null)
foreach (var childItem in container.Items) //// {
{ //// foreach (var childItem in container.Items)
var childContainer = (T)this.GetContainerForItem(childItem); //// {
//// var childContainer = (T)this.GetContainerForItem(childItem);
if (childContainer != null)
{ //// if (childContainer != null)
this.Remove(childContainer, removed); //// {
} //// this.Remove(childContainer, removed);
} //// }
} //// }
//// }
this.RemoveByContainerInternal(container);
removed.Add(container); //// this.RemoveByContainerInternal(container);
} //// removed.Add(container);
////}
} }
} }

2
Perspex.Controls/Generators/TypedItemContainerGenerator.cs

@ -15,7 +15,7 @@ namespace Perspex.Controls.Generators
{ {
} }
protected override Control CreateContainerOverride(object item) protected override IControl CreateContainer(object item, IDataTemplate itemTemplate)
{ {
T result = item as T; T result = item as T;

22
Perspex.Controls/ItemsControl.cs

@ -77,29 +77,29 @@ namespace Perspex.Controls
set { this.SetValue(ItemsPanelProperty, value); } set { this.SetValue(ItemsPanelProperty, value); }
} }
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren public IItemsPresenter Presenter
{
get
{
this.ApplyTemplate();
return this.logicalChildren;
}
}
protected IItemsPresenter Presenter
{ {
get get
{ {
return this.presenter; return this.presenter;
} }
set protected set
{ {
this.presenter = value; this.presenter = value;
this.logicalChildren.Source = ((IVisual)value?.Panel)?.VisualChildren; this.logicalChildren.Source = ((IVisual)value?.Panel)?.VisualChildren;
} }
} }
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get
{
this.ApplyTemplate();
return this.logicalChildren;
}
}
protected virtual ItemContainerGenerator CreateItemContainerGenerator() protected virtual ItemContainerGenerator CreateItemContainerGenerator()
{ {
return new ItemContainerGenerator(this); return new ItemContainerGenerator(this);

5
Perspex.Controls/Menu.cs

@ -63,8 +63,9 @@ namespace Perspex.Controls
{ {
get get
{ {
return (this.SelectedItem != null) ? var index = this.SelectedIndex;
(MenuItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem) : return (index != -1) ?
(MenuItem)this.ItemContainerGenerator.ContainerFromIndex(index) :
null; null;
} }
} }

6
Perspex.Controls/MenuItem.cs

@ -476,11 +476,11 @@ namespace Perspex.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void PopupOpened(object sender, EventArgs e) private void PopupOpened(object sender, EventArgs e)
{ {
var selected = this.SelectedItem; var selected = this.SelectedIndex;
if (selected != null) if (selected != -1)
{ {
var container = this.ItemContainerGenerator.GetContainerForItem(selected); var container = this.ItemContainerGenerator.ContainerFromIndex(selected);
if (container != null) if (container != null)
{ {

1
Perspex.Controls/Perspex.Controls.csproj

@ -36,6 +36,7 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Generators\ItemContainers.cs" />
<Compile Include="IControl.cs" /> <Compile Include="IControl.cs" />
<Compile Include="ISetLogicalParent.cs" /> <Compile Include="ISetLogicalParent.cs" />
<Compile Include="MenuItemAccessKeyHandler.cs" /> <Compile Include="MenuItemAccessKeyHandler.cs" />

20
Perspex.Controls/Presenters/DeckPresenter.cs

@ -102,7 +102,11 @@ namespace Perspex.Controls.Presenters
((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical; ((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical;
this.AddVisualChild(this.Panel); this.AddVisualChild(this.Panel);
this.createdPanel = true; this.createdPanel = true;
this.SelectedItemChanged(Tuple.Create<object, object>(null, this.SelectedItem));
if (this.SelectedItem != null)
{
this.SelectedItemChanged(Tuple.Create<object, object>(null, this.SelectedItem));
}
} }
private IItemContainerGenerator GetGenerator() private IItemContainerGenerator GetGenerator()
@ -121,37 +125,37 @@ namespace Perspex.Controls.Presenters
if (this.createdPanel) if (this.createdPanel)
{ {
var generator = this.GetGenerator(); var generator = this.GetGenerator();
Control from = null; IControl from = null;
Control to = null; IControl to = null;
int fromIndex = -1; int fromIndex = -1;
int toIndex = -1; int toIndex = -1;
if (value.Item1 != null) if (value.Item1 != null)
{ {
from = generator.GetContainerForItem(value.Item1);
fromIndex = this.Items.IndexOf(value.Item1); fromIndex = this.Items.IndexOf(value.Item1);
from = generator.ContainerFromIndex(fromIndex);
} }
if (value.Item2 != null) if (value.Item2 != null)
{ {
to = generator.Generate(new[] { value.Item2 }).FirstOrDefault(); toIndex = this.Items.IndexOf(value.Item2);
to = generator.CreateContainers(toIndex, new[] { value.Item2 }, null).FirstOrDefault();
if (to != null) if (to != null)
{ {
this.Panel.Children.Add(to); this.Panel.Children.Add(to);
toIndex = this.Items.IndexOf(value.Item2);
} }
} }
if (this.Transition != null) if (this.Transition != null)
{ {
await this.Transition.Start(from, to, fromIndex < toIndex); await this.Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
} }
if (from != null) if (from != null)
{ {
this.Panel.Children.Remove(from); this.Panel.Children.Remove(from);
generator.Remove(new[] { value.Item1 }); generator.RemoveContainers(fromIndex, 1);
} }
} }
} }

11
Perspex.Controls/Presenters/ItemsPresenter.cs

@ -147,7 +147,8 @@ namespace Perspex.Controls.Presenters
{ {
if (items != null) if (items != null)
{ {
this.Panel.Children.AddRange(this.ItemContainerGenerator.Generate(this.Items)); this.Panel.Children.AddRange(
this.ItemContainerGenerator.CreateContainers(0, this.Items, null));
INotifyCollectionChanged incc = items as INotifyCollectionChanged; INotifyCollectionChanged incc = items as INotifyCollectionChanged;
@ -170,7 +171,7 @@ namespace Perspex.Controls.Presenters
if (e.OldValue != null) if (e.OldValue != null)
{ {
generator.RemoveAll(); generator.ClearContainers();
this.Panel.Children.Clear(); this.Panel.Children.Clear();
INotifyCollectionChanged incc = e.OldValue as INotifyCollectionChanged; INotifyCollectionChanged incc = e.OldValue as INotifyCollectionChanged;
@ -203,11 +204,13 @@ namespace Perspex.Controls.Presenters
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
this.Panel.Children.AddRange(generator.Generate(e.NewItems)); this.Panel.Children.AddRange(
generator.CreateContainers(e.NewStartingIndex, e.NewItems, null));
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
this.Panel.Children.RemoveAll(generator.Remove(e.OldItems)); this.Panel.Children.RemoveAll(
generator.RemoveContainers(e.OldStartingIndex, e.OldItems.Count));
break; break;
} }

343
Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -10,9 +10,10 @@ namespace Perspex.Controls.Primitives
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Perspex.Controls.Utils; using Perspex.Controls.Generators;
using Perspex.Input; using Perspex.Input;
using Perspex.Interactivity; using Perspex.Interactivity;
using Perspex.Styling;
using Perspex.VisualTree; using Perspex.VisualTree;
/// <summary> /// <summary>
@ -53,9 +54,17 @@ namespace Perspex.Controls.Primitives
/// </summary> /// </summary>
static SelectingItemsControl() static SelectingItemsControl()
{ {
IsSelectedChangedEvent.AddClassHandler<SelectingItemsControl>(x => x.ItemIsSelectedChanged); IsSelectedChangedEvent.AddClassHandler<SelectingItemsControl>(x => x.ContainerSelectionChanged);
SelectedIndexProperty.Changed.Subscribe(SelectedIndexChanged); SelectedIndexProperty.Changed.AddClassHandler<SelectingItemsControl>(x => x.SelectedIndexChanged);
SelectedItemProperty.Changed.Subscribe(SelectedItemChanged); SelectedItemProperty.Changed.AddClassHandler<SelectingItemsControl>(x => x.SelectedItemChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="SelectingItemsControl"/> class.
/// </summary>
public SelectingItemsControl()
{
this.ItemContainerGenerator.ContainersInitialized.Subscribe(this.ContainersInitialized);
} }
/// <summary> /// <summary>
@ -76,258 +85,186 @@ namespace Perspex.Controls.Primitives
set { this.SetValue(SelectedItemProperty, value); } set { this.SetValue(SelectedItemProperty, value); }
} }
/// <summary> /// <inheritdoc/>
/// Called when the <see cref="Items"/> property changes.
/// </summary>
/// <param name="oldValue">The old value of the property.</param>
/// <param name="newValue">The new value of the property.</param>
protected override void ItemsChanged(IEnumerable oldValue, IEnumerable newValue) protected override void ItemsChanged(IEnumerable oldValue, IEnumerable newValue)
{ {
base.ItemsChanged(oldValue, newValue); base.ItemsChanged(oldValue, newValue);
this.SelectedIndex = IndexOf(newValue, this.SelectedItem);
var selected = this.SelectedItem;
if (selected != null)
{
if (newValue == null || !newValue.Contains(selected))
{
this.SelectedItem = null;
}
}
} }
/// <summary> /// <inheritdoc/>
/// Called when a <see cref="INotifyCollectionChanged.CollectionChanged"/> event is raised
/// on <see cref="Items"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
base.ItemsCollectionChanged(sender, e); base.ItemsCollectionChanged(sender, e);
var selected = this.SelectedItem;
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Replace:
if (e.OldItems.Contains(selected)) var selectedIndex = this.SelectedIndex;
if (selectedIndex >= e.OldStartingIndex &&
selectedIndex < e.OldStartingIndex + e.OldItems.Count)
{ {
this.SelectedItem = null; this.SelectedIndex = -1;
} }
break; break;
case NotifyCollectionChangedAction.Move:
this.SelectedItem = this.Items.IndexOf(selected); case NotifyCollectionChangedAction.Reset:
this.SelectedIndex = IndexOf(e.NewItems, this.SelectedItem);
break; break;
} }
} }
/// <summary> /// <inheritdoc/>
/// Called when the selection on a child item changes. protected override void OnGotFocus(GotFocusEventArgs e)
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void ItemIsSelectedChanged(RoutedEventArgs e)
{ {
var selectable = e.Source as ISelectable; base.OnGotFocus(e);
this.TrySetSelectionFromContainerEvent(e.Source);
if (selectable != null && selectable != this && selectable.IsSelected)
{
var container = this.ItemContainerGenerator.GetItemForContainer((Control)selectable);
if (container != null)
{
this.SelectedItem = container;
e.Handled = true;
}
}
} }
/// <summary> /// <summary>
/// Moves the selection in the specified direction. /// Gets the index of an item in a collection.
/// </summary> /// </summary>
/// <param name="direction">The direction.</param> /// <param name="items">The collection.</param>
protected virtual void MoveSelection(FocusNavigationDirection direction) /// <param name="item">The item.</param>
/// <returns>The index of the item or -1 if the item was not found.</returns>
private static int IndexOf(IEnumerable items, object item)
{ {
var panel = this.Presenter?.Panel as INavigablePanel; if (items != null && item != null)
var selected = this.SelectedItem;
var container = selected != null ?
this.ItemContainerGenerator.GetContainerForItem(selected) :
null;
if (panel != null)
{ {
var next = panel.GetControl(direction, container); var list = items as IList;
if (next != null)
{
this.SelectedItem = this.ItemContainerGenerator.GetItemForContainer((Control)next);
}
}
else
{
// TODO: Try doing a visual search?
}
}
/// <summary> if (list != null)
/// Called when a key is pressed within the control.
/// </summary>
/// <param name="e">The event args.</param>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled)
{
switch (e.Key)
{ {
case Key.Up: return list.IndexOf(item);
this.MoveSelection(FocusNavigationDirection.Up);
break;
case Key.Down:
this.MoveSelection(FocusNavigationDirection.Down);
break;
case Key.Left:
this.MoveSelection(FocusNavigationDirection.Left);
break;
case Key.Right:
this.MoveSelection(FocusNavigationDirection.Right);
break;
default:
return;
} }
else
var selected = this.SelectedItem;
if (selected != null)
{ {
var container = this.ItemContainerGenerator.GetContainerForItem(selected); int index = 0;
if (container != null) foreach (var i in items)
{ {
container.BringIntoView(); if (object.Equals(i, item))
FocusManager.Instance.Focus(container, true); {
return index;
}
++index;
} }
} }
e.Handled = true;
} }
return -1;
} }
/// <summary> /// <summary>
/// Called when the pointer is pressed within the control. /// Sets a container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="container">The container.</param>
protected override void OnPointerPressed(PointerPressEventArgs e) /// <param name="selected">Whether the control is selected</param>
private static void MarkContainerSelected(IControl container, bool selected)
{ {
IVisual source = (IVisual)e.Source; var selectable = container as ISelectable;
var selectable = source.GetVisualAncestors() var styleable = container as IStyleable;
.OfType<ISelectable>()
.OfType<Control>()
.FirstOrDefault();
if (selectable != null) if (selectable != null)
{ {
var item = this.ItemContainerGenerator.GetItemForContainer(selectable); selectable.IsSelected = selected;
}
if (item != null) else if (styleable != null)
{
if (selected)
{ {
this.SelectedItem = item; styleable.Classes.Add("selected");
selectable.BringIntoView(); }
FocusManager.Instance.Focus(selectable); else
{
styleable.Classes.Remove("selected");
} }
} }
e.Handled = true;
} }
/// <summary> /// <summary>
/// Called when the control's template has been applied. /// Coerces the <see cref="SelectedIndex"/> property.
/// </summary> /// </summary>
protected override void OnTemplateApplied() /// <param name="sender">The object with the property.</param>
/// <param name="index">The proposed value of the property.</param>
/// <returns>The final value of the property.</returns>
private static int ValidateSelectedIndex(SelectingItemsControl sender, int index)
{ {
base.OnTemplateApplied(); var items = sender.Items;
this.SelectedItemChanged(this.SelectedItem); return (index >= 0 && index < items?.Cast<object>().Count()) ? index : -1;
} }
/// <summary> /// <summary>
/// Provides coercion for the <see cref="SelectedIndex"/> property. /// Coerces the <see cref="SelectedItem"/> property.
/// </summary> /// </summary>
/// <param name="o">The object on which the property has changed.</param> /// <param name="sender">The object with the property.</param>
/// <param name="value">The proposed value.</param> /// <param name="item">The proposed value of the property.</param>
/// <returns>The coerced value.</returns> /// <returns>The final value of the property.</returns>
private static int ValidateSelectedIndex(PerspexObject o, int value) private static object ValidateSelectedItem(SelectingItemsControl sender, object item)
{ {
var control = o as SelectingItemsControl; var items = sender.Items;
return items?.Cast<object>().Contains(item) == true ? item : null;
if (control != null)
{
if (value < -1)
{
return -1;
}
else if (value > -1)
{
var items = control.Items;
if (items != null)
{
var count = items.Count();
return Math.Min(value, count - 1);
}
else
{
return -1;
}
}
}
return value;
} }
/// <summary> /// <summary>
/// Provides coercion for the <see cref="SelectedItem"/> property. /// Called when new containers are initialized by the <see cref="ItemContainerGenerator"/>.
/// </summary> /// </summary>
/// <param name="o">The object on which the property has changed.</param> /// <param name="containers">The containers.</param>
/// <param name="value">The proposed value.</param> private void ContainersInitialized(ItemContainers containers)
/// <returns>The coerced value.</returns>
private static object ValidateSelectedItem(PerspexObject o, object value)
{ {
var control = o as SelectingItemsControl; var selectedIndex = this.SelectedIndex;
var selectedContainer = containers.Items.OfType<ISelectable>().FirstOrDefault(x => x.IsSelected);
if (control != null) if (selectedContainer != null)
{ {
if (value != null && (control.Items == null || control.Items.IndexOf(value) == -1)) this.SelectedIndex = containers.Items.IndexOf((IControl)selectedContainer) + containers.StartingIndex;
{ }
return null; else if (selectedIndex >= containers.StartingIndex &&
} selectedIndex < containers.StartingIndex + containers.Items.Count)
{
var container = containers.Items[selectedIndex - containers.StartingIndex];
MarkContainerSelected(container, true);
} }
}
return value; /// <summary>
/// Called when a container raises the <see cref="IsSelectedChangedEvent"/>.
/// </summary>
/// <param name="e">The event.</param>
private void ContainerSelectionChanged(RoutedEventArgs e)
{
this.TrySetSelectionFromContainerEvent(e.Source);
} }
/// <summary> /// <summary>
/// Called when the <see cref="SelectedIndex"/> property changes. /// Called when the <see cref="SelectedIndex"/> property changes.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private static void SelectedIndexChanged(PerspexPropertyChangedEventArgs e) private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e)
{ {
var control = e.Sender as SelectingItemsControl; var index = (int)e.OldValue;
if (control != null) if (index != -1)
{ {
var index = (int)e.NewValue; var container = this.ItemContainerGenerator.ContainerFromIndex(index);
MarkContainerSelected(container, false);
}
if (index == -1) index = (int)e.NewValue;
{
control.SelectedItem = null; if (index == -1)
} {
else this.SelectedItem = null;
{ }
control.SelectedItem = control.Items.ElementAt((int)e.NewValue); else
} {
this.SelectedItem = this.Items.Cast<object>().ElementAt((int)e.NewValue);
var container = this.ItemContainerGenerator.ContainerFromIndex(index);
MarkContainerSelected(container, true);
} }
} }
@ -335,50 +272,28 @@ namespace Perspex.Controls.Primitives
/// Called when the <see cref="SelectedItem"/> property changes. /// Called when the <see cref="SelectedItem"/> property changes.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private static void SelectedItemChanged(PerspexPropertyChangedEventArgs e) private void SelectedItemChanged(PerspexPropertyChangedEventArgs e)
{ {
var control = e.Sender as SelectingItemsControl; this.SelectedIndex = IndexOf(this.Items, e.NewValue);
if (control != null)
{
control.SelectedItemChanged(e.NewValue);
}
} }
/// <summary> /// <summary>
/// Called when the <see cref="SelectedItem"/> property changes. /// Tries to set the selection to a container that raised an event.
/// </summary> /// </summary>
/// <param name="selected">The new selected item.</param> /// <param name="eventSource">The control that raised the event.</param>
private void SelectedItemChanged(object selected) private void TrySetSelectionFromContainerEvent(IInteractive eventSource)
{ {
var containers = this.ItemContainerGenerator.GetAll() var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
.Select(x => x.Item2) .OfType<ILogical>()
.OfType<ISelectable>(); .FirstOrDefault(x => x.LogicalParent == this);
var selectedContainer = (selected != null) ?
this.ItemContainerGenerator.GetContainerForItem(selected) :
null;
if (this.Presenter != null && this.Presenter.Panel != null)
{
KeyboardNavigation.SetTabOnceActiveElement(this.Presenter.Panel, selectedContainer);
}
foreach (var item in containers) if (item != null)
{
item.IsSelected = item == selectedContainer;
}
if (selected == null)
{
this.SelectedIndex = -1;
}
else
{ {
var items = this.Items; var index = this.ItemContainerGenerator.IndexFromContainer((IControl)item);
if (items != null) if (index != -1)
{ {
this.SelectedIndex = items.IndexOf(selected); this.SelectedIndex = index;
} }
} }
} }

18
Perspex.Controls/Primitives/TabStrip.cs

@ -47,22 +47,22 @@ namespace Perspex.Controls.Primitives
result = new TypedItemContainerGenerator<TabItem>(this); result = new TypedItemContainerGenerator<TabItem>(this);
} }
result.StateChanged += this.ItemsContainerGeneratorStateChanged; ////result.StateChanged += this.ItemsContainerGeneratorStateChanged;
return result; return result;
} }
private void ItemsContainerGeneratorStateChanged(object sender, EventArgs e) private void ItemsContainerGeneratorStateChanged(object sender, EventArgs e)
{ {
if (this.ItemContainerGenerator.State == ItemContainerGeneratorState.Generated) ////if (this.ItemContainerGenerator.State == ItemContainerGeneratorState.Generated)
{ ////{
var tabs = this.ItemContainerGenerator.GetAll() //// var tabs = this.ItemContainerGenerator.GetAll()
.Select(x => x.Item2) //// .Select(x => x.Item2)
.OfType<TabItem>() //// .OfType<TabItem>()
.ToList(); //// .ToList();
this.SelectedItem = tabs.FirstOrDefault(x => x.IsSelected) ?? tabs.FirstOrDefault(); //// this.SelectedItem = tabs.FirstOrDefault(x => x.IsSelected) ?? tabs.FirstOrDefault();
} ////}
} }
} }
} }

27
Perspex.Controls/Templates/DataTemplateExtensions.cs

@ -1,6 +1,6 @@
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// <copyright file="DataTemplateExtensions.cs" company="Steven Kirk"> // <copyright file="DataTemplateExtensions.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information. // Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright> // </copyright>
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@ -10,9 +10,18 @@ namespace Perspex.Controls.Templates
using Perspex.LogicalTree; using Perspex.LogicalTree;
using Splat; using Splat;
/// <summary>
/// Defines extension methods for working with <see cref="IDataTemplate"/>s.
/// </summary>
public static class DataTemplateExtensions public static class DataTemplateExtensions
{ {
public static IControl MaterializeDataTemplate(this Control control, object data) /// <summary>
/// Materializes a piece of data based on a data template.
/// </summary>
/// <param name="control">The control materializing the data template.</param>
/// <param name="data">The data.</param>
/// <returns>The data materialized as a control.</returns>
public static IControl MaterializeDataTemplate(this IControl control, object data)
{ {
IDataTemplate template = control.FindDataTemplate(data); IDataTemplate template = control.FindDataTemplate(data);
IControl result; IControl result;
@ -26,9 +35,9 @@ namespace Perspex.Controls.Templates
result.DataContext = data; result.DataContext = data;
} }
} }
else if (data is Control) else if (data is IControl)
{ {
result = (Control)data; result = (IControl)data;
} }
else else
{ {
@ -38,9 +47,15 @@ namespace Perspex.Controls.Templates
return result; return result;
} }
public static IDataTemplate FindDataTemplate(this Control control, object data) /// <summary>
/// Find a data template that matches a piece of data.
/// </summary>
/// <param name="control">The control searching for the data template.</param>
/// <param name="data">The data.</param>
/// <returns>The data template or null if no matching data template was found.</returns>
public static IDataTemplate FindDataTemplate(this IControl control, object data)
{ {
foreach (var i in control.GetSelfAndLogicalAncestors().OfType<Control>()) foreach (var i in control.GetSelfAndLogicalAncestors().OfType<IControl>())
{ {
foreach (IDataTemplate dt in i.DataTemplates.Reverse()) foreach (IDataTemplate dt in i.DataTemplates.Reverse())
{ {

260
Perspex.Controls/TreeView.cs

@ -47,115 +47,115 @@ namespace Perspex.Controls
{ {
// TODO: Up and down movement is a *HACK* and probably pretty slow. Probably needs // TODO: Up and down movement is a *HACK* and probably pretty slow. Probably needs
// rewriting at some point. // rewriting at some point.
if (this.SelectedItem != null) ////if (this.SelectedItem != null)
{ ////{
switch (direction) //// switch (direction)
{ //// {
case FocusNavigationDirection.Up: //// case FocusNavigationDirection.Up:
{ //// {
var list = this.Flatten(); //// var list = this.Flatten();
var index = list.IndexOf(this.SelectedItem); //// var index = list.IndexOf(this.SelectedItem);
if (index > 0) //// if (index > 0)
{ //// {
this.SelectedItem = list[index - 1]; //// this.SelectedItem = list[index - 1];
} //// }
break; //// break;
} //// }
case FocusNavigationDirection.Down: //// case FocusNavigationDirection.Down:
{ //// {
var list = this.Flatten(); //// var list = this.Flatten();
var index = list.IndexOf(this.SelectedItem); //// var index = list.IndexOf(this.SelectedItem);
if (index + 1 < list.Count) //// if (index + 1 < list.Count)
{ //// {
this.SelectedItem = list[index + 1]; //// this.SelectedItem = list[index + 1];
} //// }
break; //// break;
} //// }
case FocusNavigationDirection.Left: //// case FocusNavigationDirection.Left:
{ //// {
var node = (TreeViewItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem); //// var node = (TreeViewItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem);
node.IsExpanded = false; //// node.IsExpanded = false;
break; //// break;
} //// }
case FocusNavigationDirection.Right: //// case FocusNavigationDirection.Right:
{ //// {
var node = (TreeViewItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem); //// var node = (TreeViewItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem);
node.IsExpanded = true; //// node.IsExpanded = true;
break; //// break;
} //// }
} //// }
} ////}
} }
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e); ////base.OnKeyDown(e);
if (!e.Handled) ////if (!e.Handled)
{ ////{
switch (e.Key) //// switch (e.Key)
{ //// {
case Key.Up: //// case Key.Up:
this.MoveSelection(FocusNavigationDirection.Up); //// this.MoveSelection(FocusNavigationDirection.Up);
break; //// break;
case Key.Down: //// case Key.Down:
this.MoveSelection(FocusNavigationDirection.Down); //// this.MoveSelection(FocusNavigationDirection.Down);
break; //// break;
case Key.Left: //// case Key.Left:
this.MoveSelection(FocusNavigationDirection.Left); //// this.MoveSelection(FocusNavigationDirection.Left);
break; //// break;
case Key.Right: //// case Key.Right:
this.MoveSelection(FocusNavigationDirection.Right); //// this.MoveSelection(FocusNavigationDirection.Right);
break; //// break;
default: //// default:
return; //// return;
} //// }
var selected = this.SelectedItem; //// var selected = this.SelectedItem;
if (selected != null) //// if (selected != null)
{ //// {
var container = this.ItemContainerGenerator.GetContainerForItem(selected); //// var container = this.ItemContainerGenerator.GetContainerForItem(selected);
if (container != null) //// if (container != null)
{ //// {
container.BringIntoView(); //// container.BringIntoView();
FocusManager.Instance.Focus(container, true); //// FocusManager.Instance.Focus(container, true);
} //// }
} //// }
e.Handled = true; //// e.Handled = true;
} ////}
} }
protected override void OnPointerPressed(PointerPressEventArgs e) protected override void OnPointerPressed(PointerPressEventArgs e)
{ {
IVisual source = (IVisual)e.Source; ////IVisual source = (IVisual)e.Source;
var selectable = source.GetVisualAncestors() ////var selectable = source.GetVisualAncestors()
.OfType<ISelectable>() //// .OfType<ISelectable>()
.OfType<Control>() //// .OfType<Control>()
.FirstOrDefault(); //// .FirstOrDefault();
if (selectable != null) ////if (selectable != null)
{ ////{
var item = this.ItemContainerGenerator.GetItemForContainer(selectable); //// var item = this.ItemContainerGenerator.GetItemForContainer(selectable);
if (item != null) //// if (item != null)
{ //// {
this.SelectedItem = item; //// this.SelectedItem = item;
selectable.BringIntoView(); //// selectable.BringIntoView();
FocusManager.Instance.Focus(selectable); //// FocusManager.Instance.Focus(selectable);
} //// }
} ////}
e.Handled = true; ////e.Handled = true;
} }
private static object ValidateSelectedItem(PerspexObject o, object value) private static object ValidateSelectedItem(PerspexObject o, object value)
@ -182,39 +182,39 @@ namespace Perspex.Controls
private void Flatten(IEnumerable items, List<object> result) private void Flatten(IEnumerable items, List<object> result)
{ {
if (items != null) ////if (items != null)
{ ////{
foreach (object item in items) //// foreach (object item in items)
{ //// {
var container = (TreeViewItem)this.ItemContainerGenerator.GetContainerForItem(item); //// var container = (TreeViewItem)this.ItemContainerGenerator.GetContainerForItem(item);
result.Add(item); //// result.Add(item);
if (container.IsExpanded) //// if (container.IsExpanded)
{ //// {
this.Flatten(container.Items, result); //// this.Flatten(container.Items, result);
} //// }
} //// }
} ////}
} }
private void SelectedItemChanged(object selected) private void SelectedItemChanged(object selected)
{ {
var containers = this.ItemContainerGenerator.GetAll() ////var containers = this.ItemContainerGenerator.GetAll()
.Select(x => x.Item2) //// .Select(x => x.Item2)
.OfType<ISelectable>(); //// .OfType<ISelectable>();
var selectedContainer = (selected != null) ? ////var selectedContainer = (selected != null) ?
this.ItemContainerGenerator.GetContainerForItem(selected) : //// this.ItemContainerGenerator.GetContainerForItem(selected) :
null; //// null;
if (this.Presenter != null && this.Presenter.Panel != null) ////if (this.Presenter != null && this.Presenter.Panel != null)
{ ////{
KeyboardNavigation.SetTabOnceActiveElement(this.Presenter.Panel, selectedContainer); //// KeyboardNavigation.SetTabOnceActiveElement(this.Presenter.Panel, selectedContainer);
} ////}
foreach (var item in containers) ////foreach (var item in containers)
{ ////{
item.IsSelected = item == selectedContainer; //// item.IsSelected = item == selectedContainer;
} ////}
} }
} }
} }

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

@ -91,10 +91,14 @@ namespace Perspex.Controls.UnitTests.Presenters
}; };
target.ApplyTemplate(); target.ApplyTemplate();
items.RemoveAt(2);
var text = target.Panel.Children.OfType<TextBlock>().Select(x => x.Text).ToList(); var text = target.Panel.Children.OfType<TextBlock>().Select(x => x.Text).ToList();
Assert.Equal(new[] { "foo", "bar" }, text); Assert.Equal(new[] { "foo", "bar" }, text);
items.RemoveAt(1);
text = target.Panel.Children.OfType<TextBlock>().Select(x => x.Text).ToList();
Assert.Equal(new[] { "foo", "bar" }, text);
} }
[Fact] [Fact]

74
Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -6,6 +6,7 @@
namespace Perspex.Controls.UnitTests.Primitives namespace Perspex.Controls.UnitTests.Primitives
{ {
using System.Collections.ObjectModel;
using Perspex.Collections; using Perspex.Collections;
using Perspex.Controls.Presenters; using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives; using Perspex.Controls.Primitives;
@ -192,7 +193,7 @@ namespace Perspex.Controls.UnitTests.Primitives
} }
[Fact] [Fact]
public void Setting_SelectedIndex_Should_Coerce() public void Setting_SelectedIndex_Out_Of_Bounds_Should_Clear_Selection()
{ {
var items = new[] var items = new[]
{ {
@ -209,11 +210,11 @@ namespace Perspex.Controls.UnitTests.Primitives
target.ApplyTemplate(); target.ApplyTemplate();
target.SelectedIndex = 2; target.SelectedIndex = 2;
Assert.Equal(1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
[Fact] [Fact]
public void Setting_SelectedIndex_With_No_Items_Should_Not_Throw_Exception() public void Setting_SelectedItem_To_Non_Existent_Item_Should_Clear_Selection()
{ {
var target = new Target var target = new Target
{ {
@ -221,25 +222,36 @@ namespace Perspex.Controls.UnitTests.Primitives
}; };
target.ApplyTemplate(); target.ApplyTemplate();
target.SelectedIndex = 2; target.SelectedItem = new Item();
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
Assert.Null(target.SelectedItem);
} }
[Fact] [Fact]
public void Setting_SelectedItem_With_No_Items_Should_Not_Throw_Exception() public void Adding_Selected_Item_Should_Update_Selection()
{ {
var items = new PerspexList<Item>(new[]
{
new Item(),
new Item(),
});
var target = new Target var target = new Target
{ {
Items = items,
Template = this.Template(), Template = this.Template(),
}; };
target.ApplyTemplate(); target.ApplyTemplate();
target.SelectedItem = new Item(); items.Add(new Item { IsSelected = true });
Assert.Equal(2, target.SelectedIndex);
Assert.Equal(items[2], target.SelectedItem);
} }
[Fact] [Fact]
public void Clearing_Items_Should_Clear_Selection() public void Setting_Items_To_Null_Should_Clear_Selection()
{ {
var items = new PerspexList<Item> var items = new PerspexList<Item>
{ {
@ -293,18 +305,54 @@ namespace Perspex.Controls.UnitTests.Primitives
} }
[Fact] [Fact]
public void PointerPressed_Event_Should_Be_Handled() public void Resetting_Items_Collection_Should_Clear_Selection()
{ {
var target = new Target(); // Need to use ObservableCollection here as PerspexList signals a Clear as an
// add + remove.
var items = new ObservableCollection<Item>
{
new Item(),
new Item(),
};
var e = new PointerPressEventArgs var target = new Target
{ {
RoutedEvent = InputElement.PointerPressedEvent Items = items,
Template = this.Template(),
}; };
target.RaiseEvent(e); target.ApplyTemplate();
target.SelectedIndex = 1;
Assert.True(e.Handled); Assert.Equal(items[1], target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
items.Clear();
Assert.Equal(null, target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
}
[Fact]
public void Focusing_Item_Should_Select_It()
{
var target = new Target
{
Template = this.Template(),
Items = new[] { "foo", "bar" },
};
target.ApplyTemplate();
var e = new GotFocusEventArgs
{
RoutedEvent = InputElement.GotFocusEvent
};
target.Presenter.Panel.Children[1].RaiseEvent(e);
Assert.Equal(1, target.SelectedIndex);
Assert.False(e.Handled);
} }
[Fact] [Fact]

Loading…
Cancel
Save