Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Make CollectionView SelectedItem and SelectedItems binding function correctly #6085

Merged
merged 10 commits into from
May 28, 2019
Original file line number Diff line number Diff line change
@@ -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<string>(Device.Flags ?? new List<string>()) { "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
}
}
Original file line number Diff line number Diff line change
@@ -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<string>(Device.Flags ?? new List<string>()) { "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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59172.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5766.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CollectionViewBoundMultiSelection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CollectionViewBoundSingleSelection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue4684.xaml.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue4992.xaml.cs">
<DependentUpon>Issue4992.xaml</DependentUpon>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CollectionViewGalleryTestItem> _items;
private ObservableCollection<object> _selectedItems;

public event PropertyChangedEventHandler PropertyChanged;

public BoundSelectionModel()
{
Items = new ObservableCollection<CollectionViewGalleryTestItem>();

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<object>()
{
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<object> SelectedItems
{
get => _selectedItems;
set
{
if (_selectedItems != null)
{
_selectedItems.CollectionChanged -= SelectedItemsCollectionChanged;
}

_selectedItems = value;

_selectedItems.CollectionChanged += SelectedItemsCollectionChanged;

OnPropertyChanged();
OnPropertyChanged(nameof(SelectedItemsText));
}
}

public ObservableCollection<CollectionViewGalleryTestItem> Items
{
get => _items;
set { _items = value; OnPropertyChanged(); }
}

public string SelectedItemsText => SelectedItems.ToCommaSeparatedList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries.MultipleBoundSelection">
<ContentPage.Content>
<StackLayout Spacing="2">

<Label Text="The selected items in the CollectionView should always match the 'Selected' Label below. If it does not, this test has failed."
FontSize="10" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" />

<Label Text="{Binding SelectedItemsText, StringFormat='{}Selected: {0}'}" FontAttributes="Bold"
FontSize="10" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" />

<Button AutomationId="ClearAndAdd" HeightRequest="35" FontSize="10" Text="Clear VM selection and add Items 1 and 2" Clicked="ClearAndAdd" />

<Button AutomationId="Reset" HeightRequest="35" FontSize="10" Text="Set VM selection to new list" Clicked="ResetClicked" />

<Button AutomationId="DirectUpdate" HeightRequest="35" FontSize="10" Text="Clear CV selection and add Items 0 and 3" Clicked="DirectUpdateClicked" />

<CollectionView x:Name="CollectionView" ItemsSource="{Binding Items}"
SelectionMode="Multiple" SelectedItems="{Binding SelectedItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Image Source="{Binding Image}" HeightRequest="30" />
<Label FontSize="10" Text="{Binding Caption}"></Label>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

</StackLayout>
</ContentPage.Content>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.ObjectModel;
using Xamarin.Forms.Xaml;

namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MultipleBoundSelection : ContentPage
{
BoundSelectionModel _vm;

public MultipleBoundSelection()
{
_vm = new BoundSelectionModel();
BindingContext = _vm;
InitializeComponent();
}

private void ClearAndAdd(object sender, EventArgs e)
{
_vm.SelectedItems.Clear();
_vm.SelectedItems.Add(_vm.Items[1]);
_vm.SelectedItems.Add(_vm.Items[2]);
}

private void ResetClicked(object sender, EventArgs e)
{
_vm.SelectedItems = new ObservableCollection<object>
{
_vm.Items[1],
_vm.Items[2]
};
}

private void DirectUpdateClicked(object sender, EventArgs e)
{
CollectionView.SelectedItems.Clear();
CollectionView.SelectedItems.Add(_vm.Items[0]);
CollectionView.SelectedItems.Add(_vm.Items[3]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public SelectionGallery()
new PreselectedItemGallery(), Navigation),
GalleryBuilder.NavButton("Preselected Items", () =>
new PreselectedItemsGallery(), Navigation),
GalleryBuilder.NavButton("Single Selection, Bound", () =>
new SingleBoundSelection(), Navigation),
GalleryBuilder.NavButton("Multiple Selection, Bound", () =>
new MultipleBoundSelection(), Navigation),
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;

namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries
{
internal static class SelectionHelpers
{
public static string ToCommaSeparatedList(this IEnumerable<object> items)
{
if (items == null)
{
return string.Empty;
}

return string.Join(", ", items.Cast<CollectionViewGalleryTestItem>().Select(i => i.Caption));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ void CollectionViewOnSelectionChanged(object sender, SelectionChangedEventArgs a

void UpdateSelectionInfo(IEnumerable<object> currentSelectedItems, IEnumerable<object> previousSelectedItems)
{
var previous = ToList(previousSelectedItems);
var current = ToList(currentSelectedItems);
var previous = previousSelectedItems.ToCommaSeparatedList();
var current = currentSelectedItems.ToCommaSeparatedList();

if (string.IsNullOrEmpty(previous))
{
Expand All @@ -58,7 +58,7 @@ void UpdateSelectionInfoCommand()

if(CollectionView.SelectionMode == SelectionMode.Multiple)
{
current = ToList(CollectionView?.SelectedItems);
current = CollectionView?.SelectedItems.ToCommaSeparatedList();
}
else if (CollectionView.SelectionMode == SelectionMode.Single)
{
Expand All @@ -67,16 +67,5 @@ void UpdateSelectionInfoCommand()

SelectedItemsCommand.Text = $"Selection (command): {current}";
}

static string ToList(IEnumerable<object> items)
{
if (items == null)
{
return string.Empty;
}

return items.Aggregate(string.Empty,
(s, o) => s + (s.Length == 0 ? "" : ", ") + ((CollectionViewGalleryTestItem)o).Caption);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries.SingleBoundSelection">
<ContentPage.Content>
<StackLayout Spacing="5">

<Label Text="The selected item in the CollectionView should match the 'Selected' Label below. If it does not, this test has failed."
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />

<Label Text="{Binding SelectedItem, StringFormat='{}Selected: {0}'}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />

<Button AutomationId="Reset" Text="Reset Selection to Item 0" Clicked="ResetClicked" />

<Button AutomationId="Clear" Text="Clear Selection" Clicked="ClearClicked" />

<CollectionView ItemsSource="{Binding Items}" SelectionMode="Single" SelectedItem="{Binding SelectedItem}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Image Source="{Binding Image}" HeightRequest="50" />
<Label Text="{Binding Caption}"></Label>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

</StackLayout>
</ContentPage.Content>
</ContentPage>
Loading