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
This commit is contained in:
Родитель
6169b972a2
Коммит
34165550f5
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59172.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)FlagTestHelpers.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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,10 @@
|
|||
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),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,8 +35,8 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionG
|
|||
|
||||
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))
|
||||
{
|
||||
|
@ -58,7 +58,7 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionG
|
|||
|
||||
if(CollectionView.SelectionMode == SelectionMode.Multiple)
|
||||
{
|
||||
current = ToList(CollectionView?.SelectedItems);
|
||||
current = CollectionView?.SelectedItems.ToCommaSeparatedList();
|
||||
}
|
||||
else if (CollectionView.SelectionMode == SelectionMode.Single)
|
||||
{
|
||||
|
@ -67,16 +67,5 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionG
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class SingleBoundSelection : ContentPage
|
||||
{
|
||||
BoundSelectionModel _vm;
|
||||
|
||||
public SingleBoundSelection()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = new BoundSelectionModel();
|
||||
BindingContext = _vm;
|
||||
}
|
||||
|
||||
private void ResetClicked(object sender, EventArgs e)
|
||||
{
|
||||
_vm.SelectedItem = _vm.Items[0];
|
||||
}
|
||||
|
||||
private void ClearClicked(object sender, EventArgs e)
|
||||
{
|
||||
_vm.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,6 +56,9 @@
|
|||
<EmbeddedResource Update="GalleryPages\CollectionViewGalleries\EmptyViewGalleries\EmptyViewSwapGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\MultipleBoundSelection.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\PreselectedItemsGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
|
|
|
@ -12,12 +12,15 @@ namespace Xamarin.Forms
|
|||
|
||||
public static readonly BindableProperty SelectedItemProperty =
|
||||
BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(SelectableItemsView), default(object),
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: SelectedItemPropertyChanged);
|
||||
|
||||
static readonly BindablePropertyKey SelectedItemsPropertyKey =
|
||||
BindableProperty.CreateReadOnly(nameof(SelectedItems), typeof(IList<object>), typeof(SelectableItemsView), null);
|
||||
|
||||
public static readonly BindableProperty SelectedItemsProperty = SelectedItemsPropertyKey.BindableProperty;
|
||||
public static readonly BindableProperty SelectedItemsProperty =
|
||||
BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(SelectableItemsView), null,
|
||||
defaultBindingMode: BindingMode.OneWay,
|
||||
propertyChanged: SelectedItemsPropertyChanged,
|
||||
coerceValue: CoerceSelectedItems,
|
||||
defaultValueCreator: DefaultValueCreator);
|
||||
|
||||
public static readonly BindableProperty SelectionChangedCommandProperty =
|
||||
BindableProperty.Create(nameof(SelectionChangedCommand), typeof(ICommand), typeof(SelectableItemsView));
|
||||
|
@ -26,10 +29,10 @@ namespace Xamarin.Forms
|
|||
BindableProperty.Create(nameof(SelectionChangedCommandParameter), typeof(object),
|
||||
typeof(SelectableItemsView));
|
||||
|
||||
static readonly IList<object> s_empty = new List<object>(0);
|
||||
|
||||
public SelectableItemsView()
|
||||
{
|
||||
var selectionList = new SelectionList(this);
|
||||
SetValue(SelectedItemsPropertyKey, selectionList);
|
||||
}
|
||||
|
||||
public object SelectedItem
|
||||
|
@ -41,6 +44,7 @@ namespace Xamarin.Forms
|
|||
public IList<object> SelectedItems
|
||||
{
|
||||
get => (IList<object>)GetValue(SelectedItemsProperty);
|
||||
set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
|
||||
}
|
||||
|
||||
public ICommand SelectionChangedCommand
|
||||
|
@ -67,9 +71,39 @@ namespace Xamarin.Forms
|
|||
{
|
||||
}
|
||||
|
||||
static object CoerceSelectedItems(BindableObject bindable, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return new SelectionList((SelectableItemsView)bindable);
|
||||
}
|
||||
|
||||
if(value is SelectionList)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return new SelectionList((SelectableItemsView)bindable, value as IList<object>);
|
||||
}
|
||||
|
||||
static object DefaultValueCreator(BindableObject bindable)
|
||||
{
|
||||
return new SelectionList((SelectableItemsView)bindable);
|
||||
}
|
||||
|
||||
static void SelectedItemsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var selectableItemsView = (SelectableItemsView)bindable;
|
||||
var oldSelection = (IList<object>)oldValue ?? s_empty;
|
||||
var newSelection = (IList<object>)newValue ?? s_empty;
|
||||
|
||||
selectableItemsView.SelectedItemsPropertyChanged(oldSelection, newSelection);
|
||||
}
|
||||
|
||||
internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
|
||||
{
|
||||
SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
|
||||
|
||||
OnPropertyChanged(SelectedItemsProperty.PropertyName);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +1,55 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
// Used by the SelectableItemsView to keep track of (and respond to changes in) the SelectedItems property
|
||||
internal class SelectionList : IList<object>
|
||||
{
|
||||
readonly SelectableItemsView _selectableItemsView;
|
||||
List<object> _internal;
|
||||
static readonly IList<object> s_empty = new List<object>(0);
|
||||
readonly SelectableItemsView _selectableItemsView;
|
||||
readonly IList<object> _internal;
|
||||
IList<object> _shadow;
|
||||
bool _externalChange;
|
||||
|
||||
public SelectionList(SelectableItemsView selectableItemsView)
|
||||
public SelectionList(SelectableItemsView selectableItemsView, IList<object> items = null)
|
||||
{
|
||||
_selectableItemsView = selectableItemsView ?? throw new ArgumentNullException(nameof(selectableItemsView));
|
||||
_internal = new List<object>();
|
||||
_internal = items ?? new List<object>();
|
||||
_shadow = Copy();
|
||||
|
||||
if (items is INotifyCollectionChanged incc)
|
||||
{
|
||||
incc.CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public object this[int index] { get => _internal[index]; set => _internal[index] = value; }
|
||||
|
||||
public int Count => _internal.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(object item)
|
||||
{
|
||||
var oldItems = Copy();
|
||||
|
||||
_externalChange = true;
|
||||
_internal.Add(item);
|
||||
_externalChange = false;
|
||||
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(oldItems, Copy());
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(_shadow, _internal);
|
||||
_shadow.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var oldItems = Copy();
|
||||
_externalChange = true;
|
||||
_internal.Clear();
|
||||
_externalChange = false;
|
||||
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(oldItems, s_empty);
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(_shadow, s_empty);
|
||||
_shadow.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(object item)
|
||||
|
@ -61,22 +74,24 @@ namespace Xamarin.Forms
|
|||
|
||||
public void Insert(int index, object item)
|
||||
{
|
||||
var oldItems = Copy();
|
||||
|
||||
_externalChange = true;
|
||||
_internal.Insert(index, item);
|
||||
_externalChange = false;
|
||||
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(oldItems, Copy());
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(_shadow, _internal);
|
||||
_shadow.Insert(index, item);
|
||||
}
|
||||
|
||||
public bool Remove(object item)
|
||||
{
|
||||
var oldItems = Copy();
|
||||
|
||||
_externalChange = true;
|
||||
var removed = _internal.Remove(item);
|
||||
_externalChange = false;
|
||||
|
||||
if (removed)
|
||||
{
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(oldItems, Copy());
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(_shadow, _internal);
|
||||
_shadow.Remove(item);
|
||||
}
|
||||
|
||||
return removed;
|
||||
|
@ -84,11 +99,12 @@ namespace Xamarin.Forms
|
|||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
var oldItems = Copy();
|
||||
|
||||
_externalChange = true;
|
||||
_internal.RemoveAt(index);
|
||||
_externalChange = false;
|
||||
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(oldItems, Copy());
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(_shadow, _internal);
|
||||
_shadow.RemoveAt(index);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
|
@ -107,14 +123,19 @@ namespace Xamarin.Forms
|
|||
return items;
|
||||
}
|
||||
|
||||
public void ClearQuietly()
|
||||
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
_internal.Clear();
|
||||
}
|
||||
if (_externalChange)
|
||||
{
|
||||
// If this change was initiated by a renderer or direct manipulation of ColllectionView.SelectedItems,
|
||||
// we don't need to send a selection change notification
|
||||
return;
|
||||
}
|
||||
|
||||
public void AddQuietly(object item)
|
||||
{
|
||||
_internal.Add(item);
|
||||
// This change is coming from a bound viewmodel property
|
||||
// Emit a selection change notification, then bring the shadow copy up-to-date
|
||||
_selectableItemsView.SelectedItemsPropertyChanged(_shadow, _internal);
|
||||
_shadow = Copy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,17 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
CollectionView.SelectItem(index, true, UICollectionViewScrollPosition.None);
|
||||
}
|
||||
|
||||
// Called by Forms to clear the native selection
|
||||
internal void ClearSelection()
|
||||
{
|
||||
var selectedItemIndexes = CollectionView.GetIndexPathsForSelectedItems();
|
||||
|
||||
foreach (var index in selectedItemIndexes)
|
||||
{
|
||||
CollectionView.DeselectItem(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormsSelectItem(NSIndexPath indexPath)
|
||||
{
|
||||
var mode = SelectableItemsView.SelectionMode;
|
||||
|
@ -87,6 +98,11 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
SelectItem(selectedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
// SelectedItem has been set to null; if an item is selected, we need to de-select it
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
return;
|
||||
case SelectionMode.Multiple:
|
||||
|
|
Загрузка…
Ссылка в новой задаче