From 3ac5b51c35d9d313fee5fd67fc2a4d435f82800a Mon Sep 17 00:00:00 2001 From: humhei Date: Wed, 26 Jun 2019 23:37:08 +0800 Subject: [PATCH] [UWP] CollectionView SelectionMode (#6629) fixes #3172 fixes #6489 fixes #5844 fixes #5625 * UWP CollectionView SelectionMode * Null check * SelectItems get property * ItemTemplatePair type check * Avoid mutually trigger OnSelectionChange * Extended -> Multiple + Avoid creating new SelectedItems --- .gitignore | 1 + .../CollectionView/CollectionViewRenderer.cs | 20 ++ .../ItemsViewRenderer.cs} | 38 ++-- .../SelectableItemsViewRenderer.cs | 198 ++++++++++++++++++ .../Xamarin.Forms.Platform.UAP.csproj | 4 +- 5 files changed, 241 insertions(+), 20 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs rename Xamarin.Forms.Platform.UAP/{CollectionViewRenderer.cs => CollectionView/ItemsViewRenderer.cs} (92%) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/SelectableItemsViewRenderer.cs diff --git a/.gitignore b/.gitignore index 29c6ff80681..1637be727ad 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ tools/ !tools/mdoc/**/* caketools/ *.binlog +.ionide/** \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs new file mode 100644 index 00000000000..be2899bdd49 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/CollectionViewRenderer.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Platform.UAP; + +namespace Xamarin.Forms.Platform.UWP +{ + public class CollectionViewRenderer : SelectableItemsViewRenderer + { + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.UAP/CollectionViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs similarity index 92% rename from Xamarin.Forms.Platform.UAP/CollectionViewRenderer.cs rename to Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index 87c0096a20b..0fe4c31048d 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -14,12 +14,12 @@ namespace Xamarin.Forms.Platform.UWP { - public class CollectionViewRenderer : ViewRenderer + public class ItemsViewRenderer : ViewRenderer { IItemsLayout _layout; CollectionViewSource _collectionViewSource; - protected ItemsControl ItemsControl { get; private set; } + protected ListViewBase ListViewBase { get; private set; } protected override void OnElementChanged(ElementChangedEventArgs args) { @@ -42,7 +42,7 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE } } - protected virtual ItemsControl SelectLayout(IItemsLayout layoutSpecification) + protected virtual ListViewBase SelectLayout(IItemsLayout layoutSpecification) { switch (layoutSpecification) { @@ -59,7 +59,7 @@ protected virtual ItemsControl SelectLayout(IItemsLayout layoutSpecification) protected virtual void UpdateItemsSource() { - if (ItemsControl == null) + if (ListViewBase == null) { return; } @@ -74,7 +74,7 @@ protected virtual void UpdateItemsSource() // The ItemContentControls need the actual data item and the template so they can inflate the template // and bind the result to the data item. // ItemTemplateEnumerator handles pairing them up for the ItemContentControls to consume - + _collectionViewSource = new CollectionViewSource { Source = TemplatedItemSourceFactory.Create(itemsSource, itemTemplate), @@ -87,25 +87,25 @@ protected virtual void UpdateItemsSource() { Source = itemsSource, IsSourceGrouped = false - }; + }; } - ItemsControl.ItemsSource = _collectionViewSource.View; + ListViewBase.ItemsSource = _collectionViewSource.View; } protected virtual void UpdateItemTemplate() { - if (Element == null || ItemsControl == null) + if (Element == null || ListViewBase == null) { return; } var formsTemplate = Element.ItemTemplate; - var itemsControlItemTemplate = ItemsControl.ItemTemplate; + var itemsControlItemTemplate = ListViewBase.ItemTemplate; if (formsTemplate == null) { - ItemsControl.ItemTemplate = null; + ListViewBase.ItemTemplate = null; if (itemsControlItemTemplate != null) { @@ -120,7 +120,7 @@ protected virtual void UpdateItemTemplate() // TODO hartez 2018/06/23 13:47:27 Handle DataTemplateSelector case // Actually, DataTemplateExtensions CreateContent might handle the selector for us - ItemsControl.ItemTemplate = + ListViewBase.ItemTemplate = (Windows.UI.Xaml.DataTemplate)Windows.UI.Xaml.Application.Current.Resources["ItemsViewDefaultTemplate"]; if (itemsControlItemTemplate == null) @@ -130,7 +130,7 @@ protected virtual void UpdateItemTemplate() } } - static ItemsControl CreateGridView(GridItemsLayout gridItemsLayout) + static ListViewBase CreateGridView(GridItemsLayout gridItemsLayout) { var gridView = new FormsGridView(); @@ -153,7 +153,7 @@ static ItemsControl CreateGridView(GridItemsLayout gridItemsLayout) return gridView; } - static ItemsControl CreateHorizontalListView() + static ListViewBase CreateHorizontalListView() { // TODO hartez 2018/06/05 16:18:57 Is there any performance benefit to caching the ItemsPanelTemplate lookup? // TODO hartez 2018/05/29 15:38:04 Make sure the ItemsViewStyles.xaml xbf gets into the nuspec @@ -174,7 +174,7 @@ void LayoutOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == GridItemsLayout.SpanProperty.PropertyName) { - if (ItemsControl is FormsGridView formsGridView) + if (ListViewBase is FormsGridView formsGridView) { formsGridView.MaximumRowsOrColumns = ((GridItemsLayout)_layout).Span; } @@ -188,14 +188,14 @@ void SetUpNewElement(ItemsView newElement) return; } - if (ItemsControl == null) + if (ListViewBase == null) { - ItemsControl = SelectLayout(newElement.ItemsLayout); + ListViewBase = SelectLayout(newElement.ItemsLayout); _layout = newElement.ItemsLayout; _layout.PropertyChanged += LayoutOnPropertyChanged; - SetNativeControl(ItemsControl); + SetNativeControl(ListViewBase); } UpdateItemTemplate(); @@ -337,7 +337,7 @@ async Task AnimateTo(ListViewBase list, object targetItem, ScrollToPosition scro // TODO hartez 2018/10/05 17:23:23 The animated scroll works fine vertically if we are scrolling to a greater Y offset. // If we're scrolling back up to a lower Y offset, it just gives up and sends us to 0 (first item) // Works fine if we disable animation, but that's not very helpful - + scrollViewer.ChangeView(position.Value.X, position.Value.Y, null, false); //if (scrollToPosition == ScrollToPosition.End) @@ -350,7 +350,7 @@ async Task AnimateTo(ListViewBase list, object targetItem, ScrollToPosition scro //} //else //{ - + //} } diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/SelectableItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/SelectableItemsViewRenderer.cs new file mode 100644 index 00000000000..523fb69f449 --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/CollectionView/SelectableItemsViewRenderer.cs @@ -0,0 +1,198 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Windows.UI.Xaml.Controls; +using UWPListViewSelectionMode = Windows.UI.Xaml.Controls.ListViewSelectionMode; + +namespace Xamarin.Forms.Platform.UWP +{ + public class SelectableItemsViewRenderer : ItemsViewRenderer + { + SelectableItemsView _selectableItemsView; + + protected override void OnElementChanged(ElementChangedEventArgs args) + { + var oldListViewBase = ListViewBase; + if (oldListViewBase != null) + { + oldListViewBase.ClearValue(ListViewBase.SelectionModeProperty); + oldListViewBase.SelectionChanged -= OnNativeSelectionChanged; + } + + if (args.OldElement != null) + { + args.OldElement.SelectionChanged -= OnSelectionChanged; + } + + base.OnElementChanged(args); + _selectableItemsView = args.NewElement; + + if (_selectableItemsView != null) + { + _selectableItemsView.SelectionChanged += OnSelectionChanged; + } + + var newListViewBase = ListViewBase; + + if (newListViewBase != null) + { + newListViewBase.SetBinding(ListViewBase.SelectionModeProperty, + new Windows.UI.Xaml.Data.Binding + { + Source = _selectableItemsView, + Path = new Windows.UI.Xaml.PropertyPath("SelectionMode"), + Converter = new SelectionModeConvert(), + Mode = Windows.UI.Xaml.Data.BindingMode.TwoWay + }); + + newListViewBase.SelectionChanged += OnNativeSelectionChanged; + } + UpdateNativeSelection(); + } + + void UpdateNativeSelection() + { + switch (ListViewBase.SelectionMode) + { + case UWPListViewSelectionMode.None: + break; + case UWPListViewSelectionMode.Single: + ListViewBase.SelectionChanged -= OnNativeSelectionChanged; + if (_selectableItemsView != null) + { + if (_selectableItemsView.SelectedItem == null) + { + ListViewBase.SelectedItem = null; + } + else + { + ListViewBase.SelectedItem = + ListViewBase.Items.First(item => + { + if (item is ItemTemplatePair itemPair) + { + return itemPair.Item == _selectableItemsView.SelectedItem; + } + else + { + return item == _selectableItemsView.SelectedItem; + } + }); + + } + } + ListViewBase.SelectionChanged += OnNativeSelectionChanged; + break; + case UWPListViewSelectionMode.Multiple: + ListViewBase.SelectionChanged -= OnNativeSelectionChanged; + ListViewBase.SelectedItems.Clear(); + foreach (var nativeItem in ListViewBase.Items) + { + if (nativeItem is ItemTemplatePair itemPair && _selectableItemsView.SelectedItems.Contains(itemPair.Item)) + { + ListViewBase.SelectedItems.Add(nativeItem); + } + else if (_selectableItemsView.SelectedItems.Contains(nativeItem)) + { + ListViewBase.SelectedItems.Add(nativeItem); + } + } + ListViewBase.SelectionChanged += OnNativeSelectionChanged; + break; + case UWPListViewSelectionMode.Extended: + break; + default: + break; + } + + } + + void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + UpdateNativeSelection(); + } + + void OnNativeSelectionChanged(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e) + { + if (Element != null) + { + switch (ListViewBase.SelectionMode) + { + case UWPListViewSelectionMode.None: + break; + case UWPListViewSelectionMode.Single: + var selectedItem = + ListViewBase.SelectedItem is ItemTemplatePair itemPair ? itemPair.Item : ListViewBase.SelectedItem; + Element.SelectionChanged -= OnSelectionChanged; + Element.SetValueFromRenderer(SelectableItemsView.SelectedItemProperty, selectedItem); + Element.SelectionChanged += OnSelectionChanged; + break; + case UWPListViewSelectionMode.Multiple: + Element.SelectionChanged -= OnSelectionChanged; + + _selectableItemsView.SelectedItems.Clear(); + var selectedItems = + ListViewBase.SelectedItems + .Select(a => + { + var item = a is ItemTemplatePair itemPair1 ? itemPair1.Item : a; + return item; + }) + .ToList(); + + foreach (var item in selectedItems) + { + _selectableItemsView.SelectedItems.Add(item); + } + + Element.SelectionChanged += OnSelectionChanged; + break; + + case UWPListViewSelectionMode.Extended: + break; + + default: + break; + } + } + } + + class SelectionModeConvert : Windows.UI.Xaml.Data.IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + var formSelectionMode = (SelectionMode)value; + switch (formSelectionMode) + { + case SelectionMode.None: + return UWPListViewSelectionMode.None; + case SelectionMode.Single: + return UWPListViewSelectionMode.Single; + case SelectionMode.Multiple: + return UWPListViewSelectionMode.Multiple; + default: + return UWPListViewSelectionMode.None; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + var uwpListViewSelectionMode = (UWPListViewSelectionMode)value; + switch (uwpListViewSelectionMode) + { + case UWPListViewSelectionMode.None: + return SelectionMode.None; + case UWPListViewSelectionMode.Single: + return SelectionMode.Single; + case UWPListViewSelectionMode.Multiple: + return SelectionMode.Multiple; + case UWPListViewSelectionMode.Extended: + return SelectionMode.None; + default: + return SelectionMode.None; + } + } + } + + } +} diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj index b35f8c374a8..10632915464 100644 --- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj +++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj @@ -42,11 +42,13 @@ + + - +