Browse Source

Make changing selecting in item work.

Make SelectingItemsControl listen for IsSelectedChangedEvents from child
items and update its selection accordingly.
pull/58/head
Steven Kirk 11 years ago
parent
commit
4f98bb5476
  1. 33
      Perspex.Controls/ListBoxItem.cs
  2. 177
      Perspex.Controls/Primitives/SelectingItemsControl.cs
  3. 31
      Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

33
Perspex.Controls/ListBoxItem.cs

@ -6,20 +6,51 @@
namespace Perspex.Controls
{
using System;
using Perspex.Controls.Primitives;
using Perspex.Interactivity;
/// <summary>
/// An selectable item in a <see cref="ListBox"/>.
/// </summary>
public class ListBoxItem : ContentControl, ISelectable
{
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly PerspexProperty<bool> IsSelectedProperty =
PerspexProperty.Register<ListBoxItem, bool>("IsSelected");
PerspexProperty.Register<ListBoxItem, bool>(nameof(IsSelected));
/// <summary>
/// Initializes static members of the <see cref="ListBoxItem"/> class.
/// </summary>
static ListBoxItem()
{
Control.PseudoClass(IsSelectedProperty, ":selected");
IsSelectedProperty.Changed.Subscribe(IsSelectedChanged);
}
/// <summary>
/// Gets or sets the selection state of the item.
/// </summary>
public bool IsSelected
{
get { return this.GetValue(IsSelectedProperty); }
set { this.SetValue(IsSelectedProperty, value); }
}
/// <summary>
/// Called when the <see cref="IsSelected"/> property changes on an object.
/// </summary>
/// <param name="e">The sender.</param>
private static void IsSelectedChanged(PerspexPropertyChangedEventArgs e)
{
var interactive = e.Sender as IInteractive;
if (interactive != null)
{
interactive.RaiseEvent(new RoutedEventArgs(SelectingItemsControl.IsSelectedChangedEvent));
}
}
}
}

177
Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -6,66 +6,81 @@
namespace Perspex.Controls.Primitives
{
using Perspex.Controls.Utils;
using Perspex.Input;
using Perspex.VisualTree;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Specialized;
using Perspex.Controls.Utils;
using Perspex.Input;
using Perspex.VisualTree;
using Perspex.Interactivity;
/// <summary>
/// An <see cref="ItemsControl"/> that maintains a selection.
/// </summary>
/// <remarks>
/// TODO: Support multiple selection.
/// </remarks>
public abstract class SelectingItemsControl : ItemsControl
{
/// <summary>
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
public static readonly PerspexProperty<int> SelectedIndexProperty =
PerspexProperty.Register<SelectingItemsControl, int>("SelectedIndex", defaultValue: -1, coerce: CoerceSelectedIndex);
PerspexProperty.Register<SelectingItemsControl, int>(
nameof(SelectedIndex),
defaultValue: -1,
coerce: CoerceSelectedIndex);
/// <summary>
/// Defines the <see cref="SelectedItem"/> property.
/// </summary>
public static readonly PerspexProperty<object> SelectedItemProperty =
PerspexProperty.Register<SelectingItemsControl, object>("SelectedItem", coerce: CoerceSelectedItem);
PerspexProperty.Register<SelectingItemsControl, object>(
nameof(SelectedItem),
coerce: CoerceSelectedItem);
/// <summary>
/// Event that should be raised by items that implement <see cref="ISelectable"/> to
/// notify the parent <see cref="SelectingItemsControl"/> that their selection state
/// has changed.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> IsSelectedChangedEvent =
RoutedEvent.Register<SelectingItemsControl, RoutedEventArgs>("IsSelectedChanged", RoutingStrategies.Bubble);
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
/// </summary>
static SelectingItemsControl()
{
SelectedIndexProperty.Changed.Subscribe(x =>
{
var control = x.Sender as SelectingItemsControl;
if (control != null)
{
var index = (int)x.NewValue;
if (index == -1)
{
control.SelectedItem = null;
}
else
{
control.SelectedItem = control.Items.ElementAt((int)x.NewValue);
}
}
});
SelectedItemProperty.Changed.Subscribe(x =>
{
var control = x.Sender as SelectingItemsControl;
if (control != null)
{
control.SelectedItemChanged(x.NewValue);
}
});
IsSelectedChangedEvent.AddClassHandler<SelectingItemsControl>(x => x.ItemSelectionChanged);
SelectedIndexProperty.Changed.Subscribe(SelectedIndexChanged);
SelectedItemProperty.Changed.Subscribe(SelectedItemChanged);
}
/// <summary>
/// Gets or sets the index of the selected item.
/// </summary>
public int SelectedIndex
{
get { return this.GetValue(SelectedIndexProperty); }
set { this.SetValue(SelectedIndexProperty, value); }
}
/// <summary>
/// Gets or sets the selected item.
/// </summary>
public object SelectedItem
{
get { return this.GetValue(SelectedItemProperty); }
set { this.SetValue(SelectedItemProperty, value); }
}
/// <summary>
/// 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)
{
base.ItemsChanged(oldValue, newValue);
@ -81,6 +96,12 @@ namespace Perspex.Controls.Primitives
}
}
/// <summary>
/// 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)
{
base.ItemsCollectionChanged(sender, e);
@ -95,6 +116,7 @@ namespace Perspex.Controls.Primitives
{
this.SelectedItem = null;
}
break;
case NotifyCollectionChangedAction.Move:
this.SelectedItem = this.Items.IndexOf(selected);
@ -102,6 +124,25 @@ namespace Perspex.Controls.Primitives
}
}
/// <summary>
/// Called when the selection on a child item changes.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void ItemSelectionChanged(RoutedEventArgs e)
{
var selectable = e.Source as ISelectable;
if (selectable != null && selectable != this && selectable.IsSelected)
{
this.SelectedItem = selectable;
e.Handled = true;
}
}
/// <summary>
/// Moves the selection in the specified direction.
/// </summary>
/// <param name="direction">The direction.</param>
protected virtual void MoveSelection(FocusNavigationDirection direction)
{
var panel = this.Presenter?.Panel as INavigablePanel;
@ -125,6 +166,10 @@ namespace Perspex.Controls.Primitives
}
}
/// <summary>
/// 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);
@ -166,6 +211,10 @@ namespace Perspex.Controls.Primitives
}
}
/// <summary>
/// Called when the pointer is pressed within the control.
/// </summary>
/// <param name="e">The event args.</param>
protected override void OnPointerPressed(PointerPressEventArgs e)
{
IVisual source = (IVisual)e.Source;
@ -189,12 +238,21 @@ namespace Perspex.Controls.Primitives
e.Handled = true;
}
/// <summary>
/// Called when the control's template has been applied.
/// </summary>
protected override void OnTemplateApplied()
{
base.OnTemplateApplied();
this.SelectedItemChanged(this.SelectedItem);
}
/// <summary>
/// Provides coercion for the <see cref="SelectedIndex"/> property.
/// </summary>
/// <param name="o">The object on which the property has changed.</param>
/// <param name="value">The proposed value.</param>
/// <returns>The coerced value.</returns>
private static int CoerceSelectedIndex(PerspexObject o, int value)
{
var control = o as SelectingItemsControl;
@ -224,6 +282,12 @@ namespace Perspex.Controls.Primitives
return value;
}
/// <summary>
/// Provides coercion for the <see cref="SelectedItem"/> property.
/// </summary>
/// <param name="o">The object on which the property has changed.</param>
/// <param name="value">The proposed value.</param>
/// <returns>The coerced value.</returns>
private static object CoerceSelectedItem(PerspexObject o, object value)
{
var control = o as SelectingItemsControl;
@ -239,6 +303,47 @@ namespace Perspex.Controls.Primitives
return value;
}
/// <summary>
/// Called when the <see cref="SelectedIndex"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void SelectedIndexChanged(PerspexPropertyChangedEventArgs e)
{
var control = e.Sender as SelectingItemsControl;
if (control != null)
{
var index = (int)e.NewValue;
if (index == -1)
{
control.SelectedItem = null;
}
else
{
control.SelectedItem = control.Items.ElementAt((int)e.NewValue);
}
}
}
/// <summary>
/// Called when the <see cref="SelectedItem"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void SelectedItemChanged(PerspexPropertyChangedEventArgs e)
{
var control = e.Sender as SelectingItemsControl;
if (control != null)
{
control.SelectedItemChanged(e.NewValue);
}
}
/// <summary>
/// Called when the <see cref="SelectedItem"/> property changes.
/// </summary>
/// <param name="selected">The new selected item.</param>
private void SelectedItemChanged(object selected)
{
var containers = this.ItemContainerGenerator.GetAll()

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

@ -10,6 +10,7 @@ namespace Perspex.Controls.Primitives.UnitTests
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Input;
using Perspex.Interactivity;
using Xunit;
public class SelectingItemsControlTests
@ -305,6 +306,36 @@ namespace Perspex.Controls.Primitives.UnitTests
Assert.True(e.Handled);
}
[Fact]
public void Raising_IsSelectedChanged_Should_Update_Selection()
{
var items = new[]
{
new Item(),
new Item(),
};
var target = new Target
{
Items = items,
Template = this.Template(),
};
target.ApplyTemplate();
target.SelectedItem = items[1];
Assert.False(items[0].IsSelected);
Assert.True(items[1].IsSelected);
items[0].IsSelected = true;
items[0].RaiseEvent(new RoutedEventArgs(SelectingItemsControl.IsSelectedChangedEvent));
Assert.Equal(target.SelectedIndex, 0);
Assert.Equal(target.SelectedItem, items[0]);
Assert.True(items[0].IsSelected);
Assert.False(items[1].IsSelected);
}
private ControlTemplate Template()
{
return ControlTemplate.Create<Target>(control =>

Loading…
Cancel
Save