diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index bb60094494..06416437db 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,29 +1,53 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + !BooleanString + !!BooleanString + + + + + + + + + - - - - + + + + + + + + + + + + + + + + - - - - !BooleanString - !!BooleanString - - - - - - - - + + \ No newline at end of file diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index 02e8d9d65d..057666d5c4 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using ReactiveUI; namespace BindingTest.ViewModels @@ -12,12 +13,13 @@ namespace BindingTest.ViewModels public MainWindowViewModel() { - Items = new ObservableCollection - { - new TestItem { StringValue = "Foo" }, - new TestItem { StringValue = "Bar" }, - new TestItem { StringValue = "Baz" }, - }; + Items = new ObservableCollection( + Enumerable.Range(0, 20).Select(x => new TestItem + { + StringValue = "Item " + x + })); + + SelectedItems = new ObservableCollection(); ShuffleItems = ReactiveCommand.Create(); ShuffleItems.Subscribe(_ => @@ -28,6 +30,7 @@ namespace BindingTest.ViewModels } public ObservableCollection Items { get; } + public ObservableCollection SelectedItems { get; } public ReactiveCommand ShuffleItems { get; } public string BooleanString diff --git a/src/Perspex.Controls/ListBox.cs b/src/Perspex.Controls/ListBox.cs index f74f71b74a..1ecd086cae 100644 --- a/src/Perspex.Controls/ListBox.cs +++ b/src/Perspex.Controls/ListBox.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.Collections; +using System.Collections.Generic; +using Perspex.Collections; using Perspex.Controls.Generators; using Perspex.Controls.Primitives; using Perspex.Input; @@ -12,12 +15,24 @@ namespace Perspex.Controls /// public class ListBox : SelectingItemsControl { + /// + /// Defines the property. + /// + public static readonly new PerspexProperty SelectedItemsProperty = + SelectingItemsControl.SelectedItemsProperty; + /// /// Defines the property. /// public static readonly new PerspexProperty SelectionModeProperty = SelectingItemsControl.SelectionModeProperty; + /// + public new IList SelectedItems + { + get { return base.SelectedItems; } + } + /// public new SelectionMode SelectionMode { diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index db6838104c..8671be4a47 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -23,9 +23,9 @@ namespace Perspex.Controls.Primitives /// provides a base class for s /// that maintain a selection (single or multiple). By default only its /// and properties are visible; the - /// multiple selection properties and - /// together with the properties are protected, however a derived - /// class can expose these if it wishes to support multiple selection. + /// current multiple selection together with the + /// properties are protected, however a derived class can expose + /// these if it wishes to support multiple selection. /// /// /// maintains a selection respecting the current @@ -54,21 +54,14 @@ namespace Perspex.Controls.Primitives o => o.SelectedItem, (o, v) => o.SelectedItem = v); - /// - /// Defines the property. - /// - protected static readonly PerspexProperty> SelectedIndexesProperty = - PerspexProperty.RegisterDirect>( - nameof(SelectedIndexes), - o => o.SelectedIndexes); - /// /// Defines the property. /// - protected static readonly PerspexProperty> SelectedItemsProperty = - PerspexProperty.RegisterDirect>( + protected static readonly PerspexProperty SelectedItemsProperty = + PerspexProperty.RegisterDirect( nameof(SelectedItems), - o => o.SelectedItems); + o => o.SelectedItems, + (o, v) => o.SelectedItems = v); /// /// Defines the property. @@ -85,8 +78,9 @@ namespace Perspex.Controls.Primitives public static readonly RoutedEvent IsSelectedChangedEvent = RoutedEvent.Register("IsSelectedChanged", RoutingStrategies.Bubble); - private PerspexList _selectedIndexes = new PerspexList(); - private PerspexList _selectedItems = new PerspexList(); + private int _selectedIndex = -1; + private object _selectedItem; + private IList _selectedItems; private bool _ignoreContainerSelectionChanged; /// @@ -103,9 +97,6 @@ namespace Perspex.Controls.Primitives public SelectingItemsControl() { ItemContainerGenerator.ContainersInitialized.Subscribe(ContainersInitialized); - _selectedIndexes.Validate = ValidateIndex; - _selectedIndexes.ForEachItem(SelectedIndexesAdded, SelectedIndexesRemoved, SelectionReset); - _selectedItems.ForEachItem(SelectedItemsAdded, SelectedItemsRemoved, SelectionReset); } /// @@ -115,7 +106,7 @@ namespace Perspex.Controls.Primitives { get { - return _selectedIndexes.Count > 0 ? _selectedIndexes[0]: -1; + return _selectedIndex; } set @@ -125,14 +116,9 @@ namespace Perspex.Controls.Primitives if (old != effective) { - _selectedIndexes.Clear(); - - if (effective != -1) - { - _selectedIndexes.Add(effective); - } - + _selectedIndex = effective; RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue); + SelectedItem = ElementAt(Items, effective); } } } @@ -144,42 +130,62 @@ namespace Perspex.Controls.Primitives { get { - return _selectedItems.FirstOrDefault(); + return _selectedItem; } set { var old = SelectedItem; - var effective = Items?.Cast().Contains(value) == true ? value : null; + var index = IndexOf(Items, value); + var effective = index != -1 ? value : null; if (effective != old) { - _selectedItems.Clear(); + _selectedItem = effective; + RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue); + SelectedIndex = index; if (effective != null) { - _selectedItems.Add(effective); + if (SelectedItems.Count != 1 || SelectedItems[0] != effective) + { + SelectedItems.Clear(); + SelectedItems.Add(effective); + } + } + else if (SelectedItems.Count > 0) + { + SelectedItems.Clear(); } - - RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue); } } } - /// - /// Gets the selected indexes. - /// - protected IPerspexList SelectedIndexes - { - get { return _selectedIndexes; } - } - /// /// Gets the selected items. /// - protected IPerspexList SelectedItems + protected IList SelectedItems { - get { return _selectedItems; } + get + { + if (_selectedItems == null) + { + _selectedItems = new PerspexList(); + SubscribeToSelectedItems(); + } + + return _selectedItems; + } + + set + { + if (value != null) + { + UnsubscribeFromSelectedItems(); + _selectedItems = value; + SubscribeToSelectedItems(); + } + } } /// @@ -285,7 +291,7 @@ namespace Perspex.Controls.Primitives var mode = SelectionMode; var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0; var multi = (mode & SelectionMode.Multiple) != 0; - var range = multi && SelectedIndexes.Count > 0 ? rangeModifier : false; + var range = multi && SelectedIndex != -1 ? rangeModifier : false; if (!toggle && !range) { @@ -293,21 +299,24 @@ namespace Perspex.Controls.Primitives } else if (multi && range) { - SynchronizeIndexes(SelectedIndexes, SelectedIndexes[0], index); + SynchronizeItems( + SelectedItems, + GetRange(Items, SelectedIndex, index)); } else { - var i = SelectedIndexes.IndexOf(index); + var item = ElementAt(Items, index); + var i = SelectedItems.IndexOf(item); if (i != -1 && (!AlwaysSelected || SelectedItems.Count > 1)) { - SelectedIndexes.RemoveAt(i); + SelectedItems.Remove(item); } else { if (multi) { - SelectedIndexes.Add(index); + SelectedItems.Add(item); } else { @@ -315,6 +324,14 @@ namespace Perspex.Controls.Primitives } } } + + if (Presenter?.Panel != null) + { + var container = ItemContainerGenerator.ContainerFromIndex(index); + KeyboardNavigation.SetTabOnceActiveElement( + (InputElement)Presenter.Panel, + container); + } } else { @@ -373,6 +390,26 @@ namespace Perspex.Controls.Primitives return false; } + /// + /// Gets the item at the specified index in a collection. + /// + /// The collection. + /// The index. + /// The index of the item or -1 if the item was not found. + private static object ElementAt(IEnumerable items, int index) + { + var typedItems = items?.Cast(); + + if (index != -1 && typedItems != null && index < typedItems.Count()) + { + return typedItems.ElementAt(index) ?? null; + } + else + { + return null; + } + } + /// /// Gets the index of an item in a collection. /// @@ -409,85 +446,54 @@ namespace Perspex.Controls.Primitives } /// - /// Generates a range of integers between the first and last inclusive. + /// Gets a range of items from an IEnumerable. /// - /// The first integer. - /// The last integer. - /// The range. - private static IEnumerable Range(int first, int last) + /// The items. + /// The index of the first item. + /// The index of the last item. + /// The items. + private static IEnumerable GetRange(IEnumerable items, int first, int last) { + var list = (items as IList) ?? items.Cast().ToList(); int step = first > last ? -1 : 1; for (int i = first; i != last; i += step) { - yield return i; + yield return list[i]; } - yield return last; + yield return list[last]; } /// - /// Makes a list of integers equal the range first...last. + /// Makes a list of objects equal another. /// - /// The list of indexes. - /// The first in the range. - /// The last in the range. - private static void SynchronizeIndexes(IPerspexList indexes, int first, int last) + /// The items collection. + /// The desired items. + private static void SynchronizeItems(IList items, IEnumerable desired) { - var i = 0; - var next = first; - int step = first > last ? -1 : 1; + int index = 0; - while (i < indexes.Count && indexes[i] == next && next != last) + foreach (var i in desired) { - ++i; - next += step; - } - - if (next != last || i != indexes.Count - 1) - { - if (i < indexes.Count - 1) + if (index < items.Count) { - indexes.RemoveRange(i, indexes.Count - i); - } - - indexes.AddRange(Range(next, last)); - } - } - - /// - /// Sets a container's 'selected' class or . - /// - /// The container. - /// Whether the control is selected - private void MarkContainerSelected(IControl container, bool selected) - { - try - { - var selectable = container as ISelectable; - var styleable = container as IStyleable; - - _ignoreContainerSelectionChanged = true; - - if (selectable != null) - { - selectable.IsSelected = selected; - } - else if (styleable != null) - { - if (selected) - { - styleable.Classes.Add(":selected"); - } - else + if (items[index] != i) { - styleable.Classes.Remove(":selected"); + items[index] = i; } } + else + { + items.Add(i); + } + + ++index; } - finally + + while (index < items.Count) { - _ignoreContainerSelectionChanged = false; + items.RemoveAt(items.Count - 1); } } @@ -530,165 +536,214 @@ namespace Perspex.Controls.Primitives } /// - /// Sets an item container's 'selected' class or . + /// Called when the currently selected item is lost and the selection must be changed + /// depending on the property. /// - /// The index of the item. - /// Whether the control is selected - /// The container. - private IControl MarkIndexSelected(int index, bool selected) + private void LostSelection() { - var container = ItemContainerGenerator.ContainerFromIndex(index); + var items = Items?.Cast(); - if (container != null) + if (items != null && AlwaysSelected) { - MarkContainerSelected(container, selected); + var index = Math.Min(SelectedIndex, items.Count() - 1); + + if (index > -1) + { + SelectedItem = items.ElementAt(index); + return; + } } - return container; + SelectedIndex = -1; } /// - /// Called when an index is added to the collection. + /// Sets a container's 'selected' class or . /// - /// The index in the SelectedIndexes collection. - /// The item indexes. - private void SelectedIndexesAdded(int listIndex, IEnumerable itemIndexes) + /// The container. + /// Whether the control is selected + private void MarkContainerSelected(IControl container, bool selected) { - var indexes = (itemIndexes as IList) ?? itemIndexes.ToList(); - IControl container = null; - - if (SelectedItems.Count != SelectedIndexes.Count) + try { - var items = indexes.Select(x => Items.Cast().ElementAt(x)); - SelectedItems.AddRange(items); - } + var selectable = container as ISelectable; + var styleable = container as IStyleable; - foreach (var itemIndex in indexes) - { - container = MarkIndexSelected(itemIndex, true); - } + _ignoreContainerSelectionChanged = true; - if (SelectedIndexes.Count == 1) - { - RaisePropertyChanged(SelectedIndexProperty, -1, SelectedIndexes[0], BindingPriority.LocalValue); + if (selectable != null) + { + selectable.IsSelected = selected; + } + else if (styleable != null) + { + if (selected) + { + styleable.Classes.Add(":selected"); + } + else + { + styleable.Classes.Remove(":selected"); + } + } } - - if (container != null && Presenter?.Panel != null) + finally { - KeyboardNavigation.SetTabOnceActiveElement((InputElement)Presenter.Panel, container); + _ignoreContainerSelectionChanged = false; } } /// - /// Called when an index is removed from the collection. + /// Sets an item container's 'selected' class or . /// - /// The index in the SelectedIndexes collection. - /// The item indexes. - private void SelectedIndexesRemoved(int listIndex, IEnumerable itemIndexes) + /// The index of the item. + /// Whether the item should be selected or deselected. + private void MarkItemSelected(int index, bool selected) { - var sync = SelectedIndexes.Count != SelectedItems.Count; - - SelectedItems.RemoveRange(listIndex, itemIndexes.Count()); - - foreach (var itemIndex in itemIndexes) - { - MarkIndexSelected(itemIndex, false); - } + var container = ItemContainerGenerator.ContainerFromIndex(index); - if (SelectedIndexes.Count == 0) + if (container != null) { - RaisePropertyChanged( - SelectedIndexProperty, - itemIndexes.First(), - -1, - BindingPriority.LocalValue); + MarkContainerSelected(container, selected); } } /// - /// Called when an item is added to the collection. + /// Sets an item container's 'selected' class or . /// - /// The index in the SelectedItems collection. /// The item. - private void SelectedItemsAdded(int index, object item) + /// Whether the item should be selected or deselected. + private void MarkItemSelected(object item, bool selected) { - if (SelectedIndexes.Count != SelectedItems.Count) - { - SelectedIndexes.Insert(index, IndexOf(Items, item)); - } + var index = IndexOf(Items, item); - if (SelectedItems.Count == 1) + if (index != -1) { - RaisePropertyChanged(SelectedItemProperty, null, item, BindingPriority.LocalValue); + MarkItemSelected(index, selected); } } /// - /// Called when an item is removed from the collection. + /// Called when the CollectionChanged event is raised. /// - /// The index in the SelectedItems collection. - /// The item. - private void SelectedItemsRemoved(int index, object item) + /// The event sender. + /// The event args. + private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - if (SelectedIndexes.Count != SelectedItems.Count) + switch (e.Action) { - SelectedIndexes.RemoveAt(index); + case NotifyCollectionChangedAction.Add: + SelectedItemsAdded(e.NewItems.Cast().ToList()); + break; + + case NotifyCollectionChangedAction.Remove: + if (SelectedItems.Count == 0) + { + SelectedIndex = -1; + } + else + { + foreach (var item in e.OldItems) + { + MarkItemSelected(item, false); + } + } + + break; + + case NotifyCollectionChangedAction.Reset: + foreach (var item in ItemContainerGenerator.Containers) + { + MarkContainerSelected(item, false); + } + + SelectedIndex = -1; + SelectedItemsAdded(SelectedItems); + break; + + case NotifyCollectionChangedAction.Replace: + foreach (var item in e.OldItems) + { + MarkItemSelected(item, false); + } + + foreach (var item in e.NewItems) + { + MarkItemSelected(item, true); + } + + if (SelectedItem != SelectedItems[0]) + { + var oldItem = SelectedItem; + var oldIndex = SelectedIndex; + var item = SelectedItems[0]; + var index = IndexOf(Items, item); + _selectedIndex = index; + _selectedItem = item; + RaisePropertyChanged(SelectedIndexProperty, oldIndex, index, BindingPriority.LocalValue); + RaisePropertyChanged(SelectedItemProperty, oldItem, item, BindingPriority.LocalValue); + } + + break; } } /// - /// Called when the collection is reset. + /// Called when items are added to the collection. /// - private void SelectionReset() + /// The added items. + private void SelectedItemsAdded(IList items) { - if (SelectedIndexes.Count > 0) + if (items.Count > 0) { - SelectedIndexes.Clear(); - } + foreach (var item in items) + { + MarkItemSelected(item, true); + } - if (SelectedItems.Count > 0) - { - SelectedItems.Clear(); - } + if (SelectedItem == null) + { + var index = IndexOf(Items, items[0]); - foreach (var container in ItemContainerGenerator.Containers) - { - MarkContainerSelected(container, false); + if (index != -1) + { + _selectedItem = items[0]; + _selectedIndex = index; + RaisePropertyChanged(SelectedIndexProperty, -1, index, BindingPriority.LocalValue); + RaisePropertyChanged(SelectedItemProperty, null, items[0], BindingPriority.LocalValue); + } + } } } /// - /// Validates items added to the collection. + /// Subscribes to the CollectionChanged event, if any. /// - /// The index to be added. - private void ValidateIndex(int index) + private void SubscribeToSelectedItems() { - if (index < 0 || index >= Items?.Cast().Count()) + var incc = _selectedItems as INotifyCollectionChanged; + + if (incc != null) { - throw new IndexOutOfRangeException(); + incc.CollectionChanged += SelectedItemsCollectionChanged; } + + SelectedItemsCollectionChanged( + _selectedItems, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// - /// Called when the currently selected item is lost and the selection must be changed - /// depending on the property. + /// Unsubscribes from the CollectionChanged event, if any. /// - private void LostSelection() + private void UnsubscribeFromSelectedItems() { - var items = Items?.Cast(); + var incc = _selectedItems as INotifyCollectionChanged; - if (items != null && AlwaysSelected) + if (incc != null) { - var index = Math.Min(SelectedIndex, items.Count() - 1); - - if (index > -1) - { - SelectedItem = items.ElementAt(index); - return; - } + incc.CollectionChanged -= SelectedItemsCollectionChanged; } - - SelectedIndex = -1; } } } diff --git a/src/Perspex.Controls/TabControl.cs b/src/Perspex.Controls/TabControl.cs index ee87741980..fa0ae06f4b 100644 --- a/src/Perspex.Controls/TabControl.cs +++ b/src/Perspex.Controls/TabControl.cs @@ -35,7 +35,7 @@ namespace Perspex.Controls { SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); FocusableProperty.OverrideDefaultValue(false); - SelectedIndexProperty.Changed.AddClassHandler(x => x.SelectedIndexChanged); + SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); } /// @@ -100,14 +100,11 @@ namespace Perspex.Controls /// Called when the property changes. /// /// The event args. - private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e) + private void SelectedItemChanged(PerspexPropertyChangedEventArgs e) { - if ((int)e.NewValue != -1) - { - var item = SelectedItem as IContentControl; - var content = item?.Content ?? item; - SelectedTab = item as TabItem; - } + var item = e.NewValue as IContentControl; + var content = item?.Content ?? item; + SelectedTab = item as TabItem; } } } diff --git a/tests/Perspex.Controls.UnitTests/ListBoxTests.cs b/tests/Perspex.Controls.UnitTests/ListBoxTests.cs index 4fe5020e2d..966c01a496 100644 --- a/tests/Perspex.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Perspex.Controls.UnitTests/ListBoxTests.cs @@ -4,6 +4,7 @@ using System.Linq; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; +using Perspex.Input; using Perspex.LogicalTree; using Perspex.Styling; using Xunit; @@ -65,6 +66,30 @@ namespace Perspex.Controls.UnitTests dataContexts); } + [Fact] + public void Setting_SelectedItem_Should_Set_Panel_Keyboard_Navigation() + { + var target = new ListBox + { + Template = new ControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + + target.ApplyTemplate(); + + target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + MouseButton = MouseButton.Left, + }); + + var panel = target.Presenter.Panel; + + Assert.Equal( + KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), + panel.Children[1]); + } + private Control CreateListBoxTemplate(ITemplatedControl parent) { return new ScrollViewer diff --git a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 421a858950..928bd62b0f 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -420,31 +420,6 @@ namespace Perspex.Controls.UnitTests.Primitives Assert.Equal(target.SelectedItem, items[1]); } - [Fact] - public void Setting_SelectedItem_Should_Set_Panel_Keyboard_Navigation() - { - var items = new[] - { - new Item(), - new Item(), - }; - - var target = new SelectingItemsControl - { - Items = items, - Template = Template(), - }; - - target.ApplyTemplate(); - target.SelectedItem = items[1]; - - var panel = target.Presenter.Panel; - - Assert.Equal( - KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), - panel.Children[1]); - } - private ControlTemplate Template() { return new ControlTemplate(control => diff --git a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index c61cb3282e..8a2d0f72f0 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.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.Collections; +using System.Collections.Generic; +using System.Linq; using Perspex.Collections; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; @@ -12,7 +15,7 @@ namespace Perspex.Controls.UnitTests.Primitives public class SelectingItemsControlTests_Multiple { [Fact] - public void Setting_SelectedIndex_Should_Add_To_SelectedIndexes() + public void Setting_SelectedIndex_Should_Add_To_SelectedItems() { var target = new TestSelector { @@ -23,11 +26,11 @@ namespace Perspex.Controls.UnitTests.Primitives target.ApplyTemplate(); target.SelectedIndex = 1; - Assert.Equal(new[] { 1 }, target.SelectedIndexes); + Assert.Equal(new[] { "bar" }, target.SelectedItems.Cast().ToList()); } [Fact] - public void Adding_SelectedIndexes_Should_Set_SelectedIndex() + public void Adding_SelectedItems_Should_Set_SelectedIndex() { var target = new TestSelector { @@ -36,13 +39,13 @@ namespace Perspex.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - target.SelectedIndexes.Add(1); + target.SelectedItems.Add("bar"); Assert.Equal(1, target.SelectedIndex); } [Fact] - public void Adding_First_SelectedIndex_Should_Raise_SelectedIndex_SelectedItem_Changed() + public void Assigning_SelectedItems_Should_Set_SelectedIndex() { var target = new TestSelector { @@ -50,27 +53,14 @@ namespace Perspex.Controls.UnitTests.Primitives Template = Template(), }; - bool indexRaised = false; - bool itemRaised = false; - target.PropertyChanged += (s, e) => - { - indexRaised |= e.Property.Name == "SelectedIndex" && - (int)e.OldValue == -1 && - (int)e.NewValue == 1; - itemRaised |= e.Property.Name == "SelectedItem" && - (string)e.OldValue == null && - (string)e.NewValue == "bar"; - }; - target.ApplyTemplate(); - target.SelectedIndexes.Add(1); + target.SelectedItems = new[] { "bar" }; - Assert.True(indexRaised); - Assert.True(itemRaised); + Assert.Equal(1, target.SelectedIndex); } [Fact] - public void Adding_Subsequent_SelectedIndexes_Should_Not_Raise_SelectedIndex_SelectedItem_Changed() + public void Reassigning_SelectedItems_Should_Clear_Selection() { var target = new TestSelector { @@ -79,16 +69,11 @@ namespace Perspex.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - target.SelectedIndexes.Add(0); - - bool raised = false; - target.PropertyChanged += (s, e) => - raised |= e.Property.Name == "SelectedIndex" || - e.Property.Name == "SelectedItem"; - - target.SelectedIndexes.Add(1); + target.SelectedItems.Add("bar"); + target.SelectedItems = new PerspexList(); - Assert.False(raised); + Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(null, target.SelectedItem); } [Fact] @@ -120,7 +105,7 @@ namespace Perspex.Controls.UnitTests.Primitives } [Fact] - public void Removing_Last_SelectedIndex_Should_Raise_SelectedIndex_Changed() + public void Adding_Subsequent_SelectedItems_Should_Not_Raise_SelectedIndex_SelectedItem_Changed() { var target = new TestSelector { @@ -129,94 +114,168 @@ namespace Perspex.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - target.SelectedIndexes.Add(0); + target.SelectedItems.Add("foo"); bool raised = false; target.PropertyChanged += (s, e) => - raised = e.Property.Name == "SelectedIndex" && - (int)e.OldValue == 0 && - (int)e.NewValue == -1; + raised |= e.Property.Name == "SelectedIndex" || + e.Property.Name == "SelectedItem"; - target.SelectedIndexes.RemoveAt(0); + target.SelectedItems.Add("bar"); + + Assert.False(raised); + } + + [Fact] + public void Removing_Last_SelectedItem_Should_Raise_SelectedIndex_Changed() + { + var target = new TestSelector + { + Items = new[] { "foo", "bar" }, + Template = Template(), + }; + + target.ApplyTemplate(); + target.SelectedItems.Add("foo"); + + bool raised = false; + target.PropertyChanged += (s, e) => + raised |= e.Property.Name == "SelectedIndex" && + (int)e.OldValue == 0 && + (int)e.NewValue == -1; + + target.SelectedItems.RemoveAt(0); Assert.True(raised); } [Fact] - public void Adding_To_SelectedIndexes_Should_Add_To_SelectedItems() + public void Adding_SelectedItems_Should_Set_Item_IsSelected() + { + var items = new[] + { + new ListBoxItem(), + new ListBoxItem(), + new ListBoxItem(), + }; + + var target = new TestSelector + { + Items = items, + Template = Template(), + }; + + target.ApplyTemplate(); + target.SelectedItems.Add(items[0]); + target.SelectedItems.Add(items[1]); + + var foo = target.Presenter.Panel.Children[0]; + + Assert.True(items[0].IsSelected); + Assert.True(items[1].IsSelected); + Assert.False(items[2].IsSelected); + } + + [Fact] + public void Assigning_SelectedItems_Should_Set_Item_IsSelected() { + var items = new[] + { + new ListBoxItem(), + new ListBoxItem(), + new ListBoxItem(), + }; + var target = new TestSelector { - Items = new[] - { - "foo", - "bar", - }, + Items = items, Template = Template(), }; target.ApplyTemplate(); - target.SelectedIndexes.Add(1); + target.SelectedItems = new PerspexList { items[0], items[1] }; - Assert.Equal(new[] { "bar" }, target.SelectedItems); + Assert.True(items[0].IsSelected); + Assert.True(items[1].IsSelected); + Assert.False(items[2].IsSelected); } [Fact] - public void Adding_To_SelectedItems_Should_Add_To_SelectedIndexes() + public void Removing_SelectedItems_Should_Clear_Item_IsSelected() { + var items = new[] + { + new ListBoxItem(), + new ListBoxItem(), + new ListBoxItem(), + }; + var target = new TestSelector { - Items = new[] - { - "foo", - "bar", - }, + Items = items, Template = Template(), }; target.ApplyTemplate(); - target.SelectedItems.Add("bar"); + target.SelectedItems.Add(items[0]); + target.SelectedItems.Add(items[1]); + target.SelectedItems.Remove(items[1]); - Assert.Equal(new[] { 1 }, target.SelectedIndexes); + Assert.True(items[0].IsSelected); + Assert.False(items[1].IsSelected); } [Fact] - public void Adding_SelectedIndexes_Should_Set_Item_IsSelected() + public void Reassigning_SelectedItems_Should_Clear_Item_IsSelected() { + var items = new[] + { + new ListBoxItem(), + new ListBoxItem(), + new ListBoxItem(), + }; + var target = new TestSelector { - Items = new[] - { - new ListBoxItem(), - new ListBoxItem(), - }, + Items = items, Template = Template(), }; target.ApplyTemplate(); - target.SelectedIndexes.Add(1); + target.SelectedItems.Add(items[0]); + target.SelectedItems.Add(items[1]); - Assert.True(((ListBoxItem)target.Presenter.Panel.Children[1]).IsSelected); + target.SelectedItems = new PerspexList { items[0], items[1] }; + + Assert.False(items[0].IsSelected); + Assert.False(items[1].IsSelected); } [Fact] - public void Removing_SelectedIndexes_Should_Clear_Item_IsSelected() + public void Replacing_First_SelectedItem_Should_Update_SelectedItem_SelectedIndex() { + var items = new[] + { + new ListBoxItem(), + new ListBoxItem(), + new ListBoxItem(), + }; + var target = new TestSelector { - Items = new[] - { - new ListBoxItem(), - new ListBoxItem(), - }, + Items = items, Template = Template(), }; target.ApplyTemplate(); - target.SelectedIndexes.Add(1); - target.SelectedIndexes.Remove(1); + target.SelectedIndex = 1; + target.SelectedItems[0] = items[2]; - Assert.False(((ListBoxItem)target.Presenter.Panel.Children[1]).IsSelected); + Assert.Equal(2, target.SelectedIndex); + Assert.Equal(items[2], target.SelectedItem); + Assert.False(items[0].IsSelected); + Assert.False(items[1].IsSelected); + Assert.True(items[2].IsSelected); } [Fact] @@ -241,8 +300,7 @@ namespace Perspex.Controls.UnitTests.Primitives target.SelectedIndex = 1; target.SelectRange(3); - Assert.Equal(new[] { 1, 2, 3 }, target.SelectedIndexes); - Assert.Equal(new[] { "bar", "baz", "qux" }, target.SelectedItems); + Assert.Equal(new[] { "bar", "baz", "qux" }, target.SelectedItems.Cast().ToList()); } [Fact] @@ -267,8 +325,7 @@ namespace Perspex.Controls.UnitTests.Primitives target.SelectedIndex = 3; target.SelectRange(1); - Assert.Equal(new[] { 3, 2, 1 }, target.SelectedIndexes); - Assert.Equal(new[] { "qux", "baz", "bar" }, target.SelectedItems); + Assert.Equal(new[] { "qux", "baz", "bar" }, target.SelectedItems.Cast().ToList()); } [Fact] @@ -294,20 +351,15 @@ namespace Perspex.Controls.UnitTests.Primitives target.SelectRange(5); target.SelectRange(4); - Assert.Equal(new[] { 2, 3, 4 }, target.SelectedIndexes); - Assert.Equal(new[] { "baz", "qux", "qiz" }, target.SelectedItems); + Assert.Equal(new[] { "baz", "qux", "qiz" }, target.SelectedItems.Cast().ToList()); } private class TestSelector : SelectingItemsControl { - public new IPerspexList SelectedIndexes - { - get { return base.SelectedIndexes; } - } - - public new IPerspexList SelectedItems + public new IList SelectedItems { get { return base.SelectedItems; } + set { base.SelectedItems = value; } } public new SelectionMode SelectionMode