diff --git a/Perspex.Controls/ListBoxItem.cs b/Perspex.Controls/ListBoxItem.cs index ce9cdacb5d..e567dc9de6 100644 --- a/Perspex.Controls/ListBoxItem.cs +++ b/Perspex.Controls/ListBoxItem.cs @@ -6,20 +6,51 @@ namespace Perspex.Controls { + using System; + using Perspex.Controls.Primitives; + using Perspex.Interactivity; + + /// + /// An selectable item in a . + /// public class ListBoxItem : ContentControl, ISelectable { + /// + /// Defines the property. + /// public static readonly PerspexProperty IsSelectedProperty = - PerspexProperty.Register("IsSelected"); + PerspexProperty.Register(nameof(IsSelected)); + /// + /// Initializes static members of the class. + /// static ListBoxItem() { Control.PseudoClass(IsSelectedProperty, ":selected"); + IsSelectedProperty.Changed.Subscribe(IsSelectedChanged); } + /// + /// Gets or sets the selection state of the item. + /// public bool IsSelected { get { return this.GetValue(IsSelectedProperty); } set { this.SetValue(IsSelectedProperty, value); } } + + /// + /// Called when the property changes on an object. + /// + /// The sender. + private static void IsSelectedChanged(PerspexPropertyChangedEventArgs e) + { + var interactive = e.Sender as IInteractive; + + if (interactive != null) + { + interactive.RaiseEvent(new RoutedEventArgs(SelectingItemsControl.IsSelectedChangedEvent)); + } + } } } diff --git a/Perspex.Controls/Primitives/SelectingItemsControl.cs b/Perspex.Controls/Primitives/SelectingItemsControl.cs index aab65f82a0..87fa6defd0 100644 --- a/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/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; + + /// + /// An that maintains a selection. + /// + /// + /// TODO: Support multiple selection. + /// public abstract class SelectingItemsControl : ItemsControl { + /// + /// Defines the property. + /// public static readonly PerspexProperty SelectedIndexProperty = - PerspexProperty.Register("SelectedIndex", defaultValue: -1, coerce: CoerceSelectedIndex); - + PerspexProperty.Register( + nameof(SelectedIndex), + defaultValue: -1, + coerce: CoerceSelectedIndex); + + /// + /// Defines the property. + /// public static readonly PerspexProperty SelectedItemProperty = - PerspexProperty.Register("SelectedItem", coerce: CoerceSelectedItem); - + PerspexProperty.Register( + nameof(SelectedItem), + coerce: CoerceSelectedItem); + + /// + /// Event that should be raised by items that implement to + /// notify the parent that their selection state + /// has changed. + /// + public static readonly RoutedEvent IsSelectedChangedEvent = + RoutedEvent.Register("IsSelectedChanged", RoutingStrategies.Bubble); + + /// + /// Initializes static members of the class. + /// 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(x => x.ItemSelectionChanged); + SelectedIndexProperty.Changed.Subscribe(SelectedIndexChanged); + SelectedItemProperty.Changed.Subscribe(SelectedItemChanged); } + /// + /// Gets or sets the index of the selected item. + /// public int SelectedIndex { get { return this.GetValue(SelectedIndexProperty); } set { this.SetValue(SelectedIndexProperty, value); } } + /// + /// Gets or sets the selected item. + /// public object SelectedItem { get { return this.GetValue(SelectedItemProperty); } set { this.SetValue(SelectedItemProperty, value); } } + /// + /// Called when the property changes. + /// + /// The old value of the property. + /// The new value of the property. protected override void ItemsChanged(IEnumerable oldValue, IEnumerable newValue) { base.ItemsChanged(oldValue, newValue); @@ -81,6 +96,12 @@ namespace Perspex.Controls.Primitives } } + /// + /// Called when a event is raised + /// on . + /// + /// The event sender. + /// The event args. 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 } } + /// + /// Called when the selection on a child item changes. + /// + /// The event args. + 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; + } + } + + /// + /// Moves the selection in the specified direction. + /// + /// The direction. protected virtual void MoveSelection(FocusNavigationDirection direction) { var panel = this.Presenter?.Panel as INavigablePanel; @@ -125,6 +166,10 @@ namespace Perspex.Controls.Primitives } } + /// + /// Called when a key is pressed within the control. + /// + /// The event args. protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); @@ -166,6 +211,10 @@ namespace Perspex.Controls.Primitives } } + /// + /// Called when the pointer is pressed within the control. + /// + /// The event args. protected override void OnPointerPressed(PointerPressEventArgs e) { IVisual source = (IVisual)e.Source; @@ -189,12 +238,21 @@ namespace Perspex.Controls.Primitives e.Handled = true; } + /// + /// Called when the control's template has been applied. + /// protected override void OnTemplateApplied() { base.OnTemplateApplied(); this.SelectedItemChanged(this.SelectedItem); } + /// + /// Provides coercion for the property. + /// + /// The object on which the property has changed. + /// The proposed value. + /// The coerced value. private static int CoerceSelectedIndex(PerspexObject o, int value) { var control = o as SelectingItemsControl; @@ -224,6 +282,12 @@ namespace Perspex.Controls.Primitives return value; } + /// + /// Provides coercion for the property. + /// + /// The object on which the property has changed. + /// The proposed value. + /// The coerced value. private static object CoerceSelectedItem(PerspexObject o, object value) { var control = o as SelectingItemsControl; @@ -239,6 +303,47 @@ namespace Perspex.Controls.Primitives return value; } + /// + /// Called when the property changes. + /// + /// The event args. + 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); + } + } + } + + /// + /// Called when the property changes. + /// + /// The event args. + private static void SelectedItemChanged(PerspexPropertyChangedEventArgs e) + { + var control = e.Sender as SelectingItemsControl; + + if (control != null) + { + control.SelectedItemChanged(e.NewValue); + } + } + + /// + /// Called when the property changes. + /// + /// The new selected item. private void SelectedItemChanged(object selected) { var containers = this.ItemContainerGenerator.GetAll() diff --git a/Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 9a469f1060..a983b75f87 100644 --- a/Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/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(control =>