From 34165550f5757d2fe3a17b9a3108109d0fbdfcce Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 28 May 2019 11:54:09 -0600 Subject: [PATCH] Make CollectionView SelectedItem and SelectedItems binding function correctly (#6085) * Add automated test for CollectionView single selection bound item * Make SelectedItem Two-Way * Multiple selection test page * Bindable SelectedItems implementation * Add automated test * Simplify null checks * Add Preserve attribute so linker doesn't break test * Make multi-item select test smaller so it passes UITests on smaller screens * Clearer list-to-string method * Clear native selection on iOS when SelectedItem set to null fixes #6158 fixes #5832 --- .../CollectionViewBoundMultiSelection.cs | 63 +++++++++++++++++ .../CollectionViewBoundSingleSelection.cs | 44 ++++++++++++ .../Xamarin.Forms.Controls.Issues.Shared.projitems | 2 + .../SelectionGalleries/BoundSelectionModel.cs | 82 ++++++++++++++++++++++ .../SelectionGalleries/MultipleBoundSelection.xaml | 34 +++++++++ .../MultipleBoundSelection.xaml.cs | 42 +++++++++++ .../SelectionGalleries/SelectionGallery.cs | 4 ++ .../SelectionGalleries/SelectionHelpers.cs | 18 +++++ .../SelectionModeGallery.xaml.cs | 17 +---- .../SelectionGalleries/SingleBoundSelection.xaml | 33 +++++++++ .../SingleBoundSelection.xaml.cs | 28 ++++++++ .../Xamarin.Forms.Controls.csproj | 3 + Xamarin.Forms.Core/Items/SelectableItemsView.cs | 46 ++++++++++-- Xamarin.Forms.Core/Items/SelectionList.cs | 69 +++++++++++------- .../SelectableItemsViewController.cs | 16 +++++ 15 files changed, 457 insertions(+), 44 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundMultiSelection.cs create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundSingleSelection.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/BoundSelectionModel.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SelectionHelpers.cs create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SingleBoundSelection.xaml create mode 100644 Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/SingleBoundSelection.xaml.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundMultiSelection.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundMultiSelection.cs new file mode 100644 index 0000000..815befa --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundMultiSelection.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.None, 47803, "CollectionView: Multi Selection Binding", PlatformAffected.All)] + public class CollectionViewBoundMultiSelection : TestNavigationPage + { + protected override void Init() + { +#if APP + Device.SetFlags(new List(Device.Flags ?? new List()) { "CollectionView_Experimental" }); + + PushAsync(new GalleryPages.CollectionViewGalleries.SelectionGalleries.MultipleBoundSelection()); +#endif + } + +#if UITEST + [Test] + public void ItemsFromViewModelShouldBeSelected() + { + // Initially Items 1 and 2 should be selected (from the view model) + RunningApp.WaitForElement("Selected: Item 1, Item 2"); + + // Tapping Item 3 should select it and updating the binding + RunningApp.Tap("Item 3"); + RunningApp.WaitForElement("Selected: Item 1, Item 2, Item 3"); + + // Test clearing the selection from the view model and updating it + RunningApp.Tap("ClearAndAdd"); + RunningApp.WaitForElement("Selected: Item 1, Item 2"); + + // Test removing an item from the selection + RunningApp.Tap("Item 2"); + RunningApp.WaitForElement("Selected: Item 1"); + + // Test setting a new selection list in the view mdoel + RunningApp.Tap("Reset"); + RunningApp.WaitForElement("Selected: Item 1, Item 2"); + + RunningApp.Tap("Item 0"); + + // Test setting the selection directly with CollectionView.SelectedItems + RunningApp.Tap("DirectUpdate"); + RunningApp.WaitForElement("Selected: Item 0, Item 3"); + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundSingleSelection.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundSingleSelection.cs new file mode 100644 index 0000000..cb782e0 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBoundSingleSelection.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.CollectionView)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.None, 4539134, "CollectionView: Single Selection Binding", PlatformAffected.All)] + public class CollectionViewBoundSingleSelection : TestNavigationPage + { + protected override void Init() + { +#if APP + Device.SetFlags(new List(Device.Flags ?? new List()) { "CollectionView_Experimental" }); + + PushAsync(new GalleryPages.CollectionViewGalleries.SelectionGalleries.SingleBoundSelection()); +#endif + } + +#if UITEST + [Test] + public void SelectionShouldUpdateBinding() + { + // Initially Item 2 should be selected (from the view model) + RunningApp.WaitForElement("Selected: Item: 2"); + + // Tapping Item 3 should select it and updating the binding + RunningApp.Tap("Item 3"); + RunningApp.WaitForElement("Selected: Item: 3"); + } +#endif + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index c5a8752..bdd2727 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -14,6 +14,8 @@ + + Issue4992.xaml diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/BoundSelectionModel.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/BoundSelectionModel.cs new file mode 100644 index 0000000..838103c --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/BoundSelectionModel.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries +{ + [Preserve(AllMembers = true)] + internal class BoundSelectionModel : INotifyPropertyChanged + { + private CollectionViewGalleryTestItem _selectedItem; + private ObservableCollection _items; + private ObservableCollection _selectedItems; + + public event PropertyChangedEventHandler PropertyChanged; + + public BoundSelectionModel() + { + Items = new ObservableCollection(); + + for (int n = 0; n < 4; n++) + { + Items.Add(new CollectionViewGalleryTestItem(DateTime.Now.AddDays(n), $"Item {n}", "coffee.png", n)); + } + + SelectedItem = Items[2]; + + SelectedItems = new ObservableCollection() + { + Items[1], Items[2] + }; + } + + private void SelectedItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(SelectedItemsText)); + } + + void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public CollectionViewGalleryTestItem SelectedItem + { + get => _selectedItem; + set + { + _selectedItem = value; + OnPropertyChanged(); + } + } + + public ObservableCollection SelectedItems + { + get => _selectedItems; + set + { + if (_selectedItems != null) + { + _selectedItems.CollectionChanged -= SelectedItemsCollectionChanged; + } + + _selectedItems = value; + + _selectedItems.CollectionChanged += SelectedItemsCollectionChanged; + + OnPropertyChanged(); + OnPropertyChanged(nameof(SelectedItemsText)); + } + } + + public ObservableCollection Items + { + get => _items; + set { _items = value; OnPropertyChanged(); } + } + + public string SelectedItemsText => SelectedItems.ToCommaSeparatedList(); + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml new file mode 100644 index 0000000..d78cf95 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/SelectionGalleries/MultipleBoundSelection.xaml @@ -0,0 +1,34 @@ + + + + + +