[UWP] [iOS] Verify selected items exist when updating native selections (#7902)

* Add selection synchronization tests

* Resync native selection after ItemsSource is updated

* Update native selection on iOS when changing ItemsSource; prevent crash when
selection contains items not in source; fixes #6963

* Automated test
This commit is contained in:
E.Z. Hart 2019-10-10 09:16:38 -06:00 коммит произвёл GitHub
Родитель c137d02ed0
Коммит 88dfa6c2ed
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 220 добавлений и 6 удалений

Просмотреть файл

@ -0,0 +1,41 @@
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.Github, 6963, "[Bug] CollectionView multiple pre-selection throws ArgumentOutOfRangeException when SelectedItems is bound to an ObservableCollection initialized inside the constructor.",
PlatformAffected.iOS | PlatformAffected.UWP)]
public class Issue6963 : TestNavigationPage
{
protected override void Init()
{
#if APP
FlagTestHelpers.SetCollectionViewTestFlag();
PushAsync(new GalleryPages.CollectionViewGalleries.SelectionGalleries.SelectionSynchronization());
#endif
}
#if UITEST
[Test]
public void SelectedItemsNotInSourceDoesNotCrash()
{
// If this page didn't crash, then we're good
RunningApp.WaitForElement("FirstLabel");
}
#endif
}
}

Просмотреть файл

@ -22,6 +22,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Issue5354.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Issue6963.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue7253.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue7621.xaml.cs">
<SubType>Code</SubType>

Просмотреть файл

@ -30,6 +30,8 @@
new SelectionChangedCommandParameter(), Navigation),
GalleryBuilder.NavButton("Filterable Single Selection", () =>
new FilterSelection(), Navigation),
GalleryBuilder.NavButton("Selection Synchronization", () =>
new SelectionSynchronization(), Navigation),
}
}
};

Просмотреть файл

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries.SelectionSynchronization">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="TestTemplate">
<Label Text="{Binding .}" Margin="0,3,0,3"></Label>
</DataTemplate>
<Style x:Key="CV" TargetType="CollectionView">
<Setter Property="HeightRequest" Value="250"/>
<Setter Property="ItemTemplate" Value="{StaticResource TestTemplate}"></Setter>
<Setter Property="Margin" Value="5,2,5,5"></Setter>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<StackLayout>
<Label AutomationId="FirstLabel" Text="Set ItemsSource then SelectedItems"/>
<Label Text="Should have items 2 and 3 selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Multiple"
ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}">
</CollectionView>
<Label Text="Set SelectedItems then ItemsSource"/>
<Label Text="Should have items 2 and 3 selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Multiple"
SelectedItems="{Binding SelectedItems}" ItemsSource="{Binding Items}">
</CollectionView>
<Label Text="Set ItemsSource then SelectedItem"/>
<Label Text="Should have item 2 selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Single"
ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
</CollectionView>
<Label Text="Set SelectedItem then ItemsSource"/>
<Label Text="Should have item 2 selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Single"
SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Items}">
</CollectionView>
<Label Text="Set SelectedItems (not in source) then ItemsSource"/>
<Label Text="Should have nothing selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Multiple"
SelectedItems="{Binding SelectedItemsNotInSource}" ItemsSource="{Binding Items}">
</CollectionView>
<Label Text="Set ItemsSource then SelectedItems (not in source)"/>
<Label Text="Should have nothing selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Multiple"
ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItemsNotInSource}" >
</CollectionView>
<Label Text="Set SelectedItem (not in source) then ItemsSource"/>
<Label Text="Should have nothing selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Single"
SelectedItem="{Binding SelectedItemNotInSource}" ItemsSource="{Binding Items}">
</CollectionView>
<Label Text="Set ItemsSource then SelectedItem (not in source)"/>
<Label Text="Should have nothing selected"/>
<CollectionView Style="{StaticResource CV}" SelectionMode="Single"
ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItemNotInSource}">
</CollectionView>
<Label Text="Switch out ItemSource for one with only some of the SelectedItems"/>
<Button x:Name="SwitchSource" Text="Switch Source" Clicked="SwitchSourceClicked"/>
<Label Text="After hitting the button, should only have Item 3 selected"/>
<CollectionView x:Name="CVSwitchSource" Style="{StaticResource CV}" SelectionMode="Multiple"
ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}">
</CollectionView>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>

Просмотреть файл

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SelectionSynchronization : ContentPage
{
public SelectionSynchronization()
{
InitializeComponent();
BindingContext = new SelectionSyncModel();
}
void SwitchSourceClicked(object sender, EventArgs e)
{
var newSource = new List<string> { "Item -1", "Item 0", "Item 1", "Item 3", "Item 4", "Item 5" };
CVSwitchSource.ItemsSource = newSource;
}
}
[Preserve(AllMembers = true)]
public class SelectionSyncModel
{
public SelectionSyncModel()
{
Items = new List<string>() {
"Item 1", "Item 2", "Item 3", "Item 4"
};
SelectedItem = "Item 2";
SelectedItems = new ObservableCollection<object> { "Item 3", "Item 2" };
SelectedItemNotInSource = "Foo";
SelectedItemsNotInSource = new ObservableCollection<object> { "Foo", "Bar", "Baz" };
}
public List<string> Items { get; set; }
public string SelectedItem { get; set; }
public ObservableCollection<object> SelectedItems { get; set; }
public string SelectedItemNotInSource { get; set; }
public ObservableCollection<object> SelectedItemsNotInSource { get; set; }
}
}

Просмотреть файл

@ -67,6 +67,7 @@ namespace Xamarin.Forms.Platform.UWP
_ignoreNativeSelectionChange = true;
base.UpdateItemsSource();
UpdateNativeSelection();
_ignoreNativeSelectionChange = false;
}
@ -89,7 +90,7 @@ namespace Xamarin.Forms.Platform.UWP
else
{
ListViewBase.SelectedItem =
ListViewBase.Items.First(item =>
ListViewBase.Items.FirstOrDefault(item =>
{
if (item is ItemTemplateContext itemPair)
{

Просмотреть файл

@ -39,7 +39,7 @@ namespace Xamarin.Forms.Platform.iOS
if (changedProperty.Is(ItemsView.ItemsSourceProperty))
{
ItemsViewController.UpdateItemsSource();
UpdateItemsSource();
}
else if (changedProperty.Is(ItemsView.ItemTemplateProperty))
{
@ -128,6 +128,11 @@ namespace Xamarin.Forms.Platform.iOS
_layout.ItemsUpdatingScrollMode = Element.ItemsUpdatingScrollMode;
}
protected virtual void UpdateItemsSource()
{
ItemsViewController.UpdateItemsSource();
}
protected abstract ItemsViewController CreateController(ItemsView newElement, ItemsViewLayout layout);
NSIndexPath DetermineIndex(ScrollToRequestEventArgs args)

Просмотреть файл

@ -30,7 +30,11 @@ namespace Xamarin.Forms.Platform.iOS
internal void SelectItem(object selectedItem)
{
var index = GetIndexForItem(selectedItem);
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
if (index.Section > -1 && index.Item > -1)
{
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
}
}
// Called by Forms to clear the native selection

Просмотреть файл

@ -19,11 +19,11 @@ namespace Xamarin.Forms.Platform.iOS
if (changedProperty.IsOneOf(SelectableItemsView.SelectedItemProperty, SelectableItemsView.SelectedItemsProperty))
{
SelectableItemsViewController.UpdateNativeSelection();
UpdateNativeSelection();
}
else if (changedProperty.Is(SelectableItemsView.SelectionModeProperty))
{
SelectableItemsViewController.UpdateSelectionMode();
UpdateSelectionMode();
}
}
@ -41,8 +41,24 @@ namespace Xamarin.Forms.Platform.iOS
return;
}
SelectableItemsViewController.UpdateSelectionMode();
UpdateSelectionMode();
UpdateNativeSelection();
}
protected virtual void UpdateNativeSelection()
{
SelectableItemsViewController.UpdateNativeSelection();
}
protected virtual void UpdateSelectionMode()
{
SelectableItemsViewController.UpdateSelectionMode();
}
protected override void UpdateItemsSource()
{
base.UpdateItemsSource();
UpdateNativeSelection();
}
}
}