summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoakim Carselind <joakim.carselind@cub.se>2016-11-09 09:23:52 +0100
committerJason Smith <jason.smith@xamarin.com>2016-11-16 12:03:13 -0800
commit693cc75068fed11c2760a1e8ef2b05310359bbea (patch)
tree987b8caf75450aaf1b59440076f753ceab5b141b
parentd97dfe91b28a30e9a350155262b1149eed8d462f (diff)
downloadxamarin-forms-693cc75068fed11c2760a1e8ef2b05310359bbea.tar.gz
xamarin-forms-693cc75068fed11c2760a1e8ef2b05310359bbea.tar.bz2
xamarin-forms-693cc75068fed11c2760a1e8ef2b05310359bbea.zip
Squashed commit of the following:
commit 8d784ec7459335ca33003844a793c3dd266c5861 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Tue Aug 23 00:30:25 2016 +0200 Added DisplayConverter property of type IValueConverter to perform conversion from object to string commit afb606f05c16b14e24785fad017540dd83dbf373 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Tue Aug 23 00:07:55 2016 +0200 Use IsValueType commit 4742c22ed33309f40a55c536b161292eb5db40f8 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Fri Aug 19 18:58:40 2016 +0200 Fixed bug with nested property expression commit 70a121e6172a61dbcf8835137bf58bd972cf2065 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Fri Aug 19 18:43:14 2016 +0200 Added more tests commit 49c7876bda4185c699f5fd9b3a66763efca9623c Author: Joakim Carselind <joakim.carselind@cub.se> Date: Thu Aug 18 13:28:36 2016 +0200 Siimplified setting SelectedItem. Added property to provide full control over how to display the objects by DisplayFunc. Added tests commit 5c1d5e149dc21c58cebf7cdbc6677d1ccec04ed4 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Thu Aug 11 17:15:36 2016 +0200 Trying to fix formatting with tabs instead of spaces commit d64663ce3ef6b223a04d477274e93ec87bd38ff4 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Thu Aug 11 17:10:39 2016 +0200 Formatting. Handle Reset,Move,Replace collection changed action equal by re binding Items collection commit 8d4641810cb3b11fb6b47f8215bb5950a9641ba2 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Thu Aug 11 16:33:03 2016 +0200 Removed inline documentation. Fixed formatting commit 28010a1b31da02879fd2d549d5b02458766544d5 Author: Joakim Carselind <joakim.carselind@cub.se> Date: Thu Aug 11 16:29:37 2016 +0200 Removed SelectedValue since SelectedIndex and SelectedItem make it redundant commit ac9d65816fe6db7b467c304e6dc3168a84d3166b Author: Joakim Carselind <joakim.carselind@cub.se> Date: Thu Aug 11 14:51:20 2016 +0200 Initial attempt on bindable picker
-rw-r--r--Xamarin.Forms.Core.UnitTests/PickerTests.cs488
-rw-r--r--Xamarin.Forms.Core/Picker.cs241
2 files changed, 700 insertions, 29 deletions
diff --git a/Xamarin.Forms.Core.UnitTests/PickerTests.cs b/Xamarin.Forms.Core.UnitTests/PickerTests.cs
index 859f9025..01431cdd 100644
--- a/Xamarin.Forms.Core.UnitTests/PickerTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/PickerTests.cs
@@ -1,42 +1,119 @@
using System;
-
using NUnit.Framework;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
namespace Xamarin.Forms.Core.UnitTests
{
+ internal class ContextFixture
+ {
+ public class NestedClass
+ {
+ public string Nested { get; set; }
+ }
+
+ public NestedClass Nested { get; set; }
+
+ public string DisplayName { get; set; }
+
+ public string ComplexName { get; set; }
+
+ public ContextFixture(string displayName, string complexName)
+ {
+ DisplayName = displayName;
+ ComplexName = complexName;
+ }
+
+ public ContextFixture()
+ {
+ }
+ }
+
+ internal class BindingContext
+ {
+ public ObservableCollection<object> Items { get; set; }
+
+ public object SelectedItem { get; set; }
+ }
+
+ internal class PickerTestValueConverter : IValueConverter
+ {
+ public bool ConvertCalled { get; private set; }
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ ConvertCalled = true;
+ var cf = (ContextFixture)value;
+ return cf.DisplayName;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
[TestFixture]
public class PickerTests : BaseTestFixture
{
[Test]
public void TestSetSelectedIndexOnNullRows()
{
- var picker = new Picker ();
+ var picker = new Picker();
- Assert.IsEmpty (picker.Items);
- Assert.AreEqual (-1, picker.SelectedIndex);
+ Assert.IsEmpty(picker.Items);
+ Assert.AreEqual(-1, picker.SelectedIndex);
picker.SelectedIndex = 2;
- Assert.AreEqual (-1, picker.SelectedIndex);
+ Assert.AreEqual(-1, picker.SelectedIndex);
+ }
+
+ [Test]
+ public void TestSelectedIndexInRange()
+ {
+ var picker = new Picker
+ {
+ Items = { "John", "Paul", "George", "Ringo" },
+ SelectedIndex = 2
+ };
+
+ Assert.AreEqual(2, picker.SelectedIndex);
+
+ picker.SelectedIndex = 42;
+ Assert.AreEqual(3, picker.SelectedIndex);
+
+ picker.SelectedIndex = -1;
+ Assert.AreEqual(-1, picker.SelectedIndex);
+
+ picker.SelectedIndex = -42;
+ Assert.AreEqual(-1, picker.SelectedIndex);
}
[Test]
- public void TestSelectedIndexInRange ()
+ public void TestSelectedIndexInRangeDefaultSelectedIndex()
{
- var picker = new Picker { Items = { "John", "Paul", "George", "Ringo" } };
+ var picker = new Picker
+ {
+ Items = { "John", "Paul", "George", "Ringo" }
+ };
+
+ Assert.AreEqual(-1, picker.SelectedIndex);
+
+ picker.SelectedIndex = -5;
+ Assert.AreEqual(-1, picker.SelectedIndex);
picker.SelectedIndex = 2;
- Assert.AreEqual (2, picker.SelectedIndex);
+ Assert.AreEqual(2, picker.SelectedIndex);
picker.SelectedIndex = 42;
- Assert.AreEqual (3, picker.SelectedIndex);
+ Assert.AreEqual(3, picker.SelectedIndex);
picker.SelectedIndex = -1;
- Assert.AreEqual (-1, picker.SelectedIndex);
+ Assert.AreEqual(-1, picker.SelectedIndex);
picker.SelectedIndex = -42;
- Assert.AreEqual (-1, picker.SelectedIndex);
+ Assert.AreEqual(-1, picker.SelectedIndex);
}
[Test]
@@ -44,16 +121,387 @@ namespace Xamarin.Forms.Core.UnitTests
{
var picker = new Picker { Items = { "John", "Paul", "George", "Ringo" }, SelectedIndex = 3 };
- Assert.AreEqual (3, picker.SelectedIndex);
+ Assert.AreEqual(3, picker.SelectedIndex);
+
+ picker.Items.RemoveAt(3);
+ picker.Items.RemoveAt(2);
+
+ Assert.AreEqual(1, picker.SelectedIndex);
+
+ picker.Items.Clear();
+ Assert.AreEqual(-1, picker.SelectedIndex);
+ }
+
+ [Test]
+ public void TestSelectedIndexOutOfRangeUpdatesSelectedItem()
+ {
+ var picker = new Picker
+ {
+ ItemsSource = new ObservableCollection<string>
+ {
+ "Monkey",
+ "Banana",
+ "Lemon"
+ },
+ SelectedIndex = 0
+ };
+ Assert.AreEqual("Monkey", picker.SelectedItem);
+ picker.SelectedIndex = 42;
+ Assert.AreEqual("Lemon", picker.SelectedItem);
+ picker.SelectedIndex = -42;
+ Assert.IsNull(picker.SelectedItem);
+ }
+
+ [Test]
+ public void TestUnsubscribeINotifyCollectionChanged()
+ {
+ var list = new ObservableCollection<string>();
+ var picker = new Picker
+ {
+ ItemsSource = list
+ };
+ Assert.AreEqual(0, picker.Items.Count);
+ var newList = new ObservableCollection<string>();
+ picker.ItemsSource = newList;
+ list.Add("item");
+ Assert.AreEqual(0, picker.Items.Count);
+ }
+
+ [Test]
+ public void TestEmptyCollectionResetItems()
+ {
+ var list = new ObservableCollection<string>
+ {
+ "John",
+ "George",
+ "Ringo"
+ };
+ var picker = new Picker
+ {
+ ItemsSource = list
+ };
+ Assert.AreEqual(3, picker.Items.Count);
+ picker.ItemsSource = new ObservableCollection<string>();
+ Assert.AreEqual(0, picker.Items.Count);
+ }
+
+ [Test]
+ public void TestDisplayFunc()
+ {
+ Func<object, string> customDisplayFunc = o =>
+ {
+ var f = (ContextFixture)o;
+ return $"{f.DisplayName} ({f.ComplexName})";
+ };
+ var obj = new ContextFixture("Monkey", "Complex Monkey");
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ DisplayFunc = customDisplayFunc,
+ ItemsSource = new ObservableCollection<object>
+ {
+ obj
+ },
+ SelectedIndex = 1
+ };
+ Assert.AreEqual("Monkey (Complex Monkey)", picker.Items[0]);
+ }
+
+ [Test]
+ public void TestSetItemsSourceProperty()
+ {
+ var items = new ObservableCollection<object>
+ {
+ new { Name = "John" },
+ "Paul",
+ "Ringo",
+ 0,
+ new DateTime(1970, 1, 1),
+ };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ ItemsSource = items
+ };
+ Assert.AreEqual(5, picker.Items.Count);
+ Assert.AreEqual("John", picker.Items[0]);
+ Assert.AreEqual("0", picker.Items[3]);
+ }
+
+ [Test]
+ public void TestDisplayConverter()
+ {
+ var obj = new ContextFixture("John", "John Doe");
+ var converter = new PickerTestValueConverter();
+ var picker = new Picker
+ {
+ DisplayConverter = converter,
+ ItemsSource = new ObservableCollection<object>
+ {
+ obj
+ }
+ };
+ Assert.IsTrue(converter.ConvertCalled);
+ Assert.AreEqual("John", picker.Items[0]);
+ }
+
+ [Test]
+ public void TestDisplayMemberPathShouldThrowArgumentExceptionInvalidPath()
+ {
+ var obj = new ContextFixture("Monkey", "Complex Monkey");
+ Func<Picker> picker = () => new Picker
+ {
+ DisplayMemberPath = "Name",
+ ItemsSource = new ObservableCollection<object>
+ {
+ obj
+ },
+ SelectedIndex = 1
+ };
+ Assert.Throws<ArgumentException>(() => picker());
+ }
+
+ [Test]
+ public void TestItemsSourceCollectionChangedAppend()
+ {
+ var items = new ObservableCollection<object>
+ {
+ new { Name = "John" },
+ "Paul",
+ "Ringo"
+ };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ ItemsSource = items,
+ SelectedIndex = 0
+ };
+ Assert.AreEqual(3, picker.Items.Count);
+ Assert.AreEqual("John", picker.Items[0]);
+ items.Add(new { Name = "George" });
+ Assert.AreEqual(4, picker.Items.Count);
+ Assert.AreEqual("George", picker.Items[picker.Items.Count - 1]);
+ }
+
+ [Test]
+ public void TestItemsSourceCollectionChangedClear()
+ {
+ var items = new ObservableCollection<object>
+ {
+ new { Name = "John" },
+ "Paul",
+ "Ringo"
+ };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ ItemsSource = items,
+ SelectedIndex = 0
+ };
+ Assert.AreEqual(3, picker.Items.Count);
+ items.Clear();
+ Assert.AreEqual(0, picker.Items.Count);
+ }
+
+ [Test]
+ public void TestItemsSourceCollectionChangedInsert()
+ {
+ var items = new ObservableCollection<object>
+ {
+ new { Name = "John" },
+ "Paul",
+ "Ringo"
+ };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ ItemsSource = items,
+ SelectedIndex = 0
+ };
+ Assert.AreEqual(3, picker.Items.Count);
+ Assert.AreEqual("John", picker.Items[0]);
+ items.Insert(1, new { Name = "George" });
+ Assert.AreEqual(4, picker.Items.Count);
+ Assert.AreEqual("George", picker.Items[1]);
+ }
+
+ [Test]
+ public void TestItemsSourceCollectionChangedReAssign()
+ {
+ var items = new ObservableCollection<object>
+ {
+ new { Name = "John" },
+ "Paul",
+ "Ringo"
+ };
+ var bindingContext = new { Items = items };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ BindingContext = bindingContext
+ };
+ picker.SetBinding(Picker.ItemsSourceProperty, "Items");
+ Assert.AreEqual(3, picker.Items.Count);
+ items = new ObservableCollection<object>
+ {
+ "Peach",
+ "Orange"
+ };
+ picker.BindingContext = new { Items = items };
+ Assert.AreEqual(2, picker.Items.Count);
+ Assert.AreEqual("Peach", picker.Items[0]);
+ }
+
+ [Test]
+ public void TestItemsSourceCollectionChangedRemove()
+ {
+ var items = new ObservableCollection<object>
+ {
+ new { Name = "John" },
+ "Paul",
+ "Ringo"
+ };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Name",
+ ItemsSource = items,
+ SelectedIndex = 0
+ };
+ Assert.AreEqual(3, picker.Items.Count);
+ Assert.AreEqual("John", picker.Items[0]);
+ items.RemoveAt(1);
+ Assert.AreEqual(2, picker.Items.Count);
+ Assert.AreEqual("Ringo", picker.Items[1]);
+ }
+
+ [Test]
+ public void TestItemsSourceCollectionOfStrings()
+ {
+ var items = new ObservableCollection<string>
+ {
+ "John",
+ "Paul",
+ "Ringo"
+ };
+ var picker = new Picker
+ {
+ ItemsSource = items,
+ SelectedIndex = 0
+ };
+ Assert.AreEqual(3, picker.Items.Count);
+ Assert.AreEqual("John", picker.Items[0]);
+ }
+
+ [Test]
+ public void TestSelectedItemDefault()
+ {
+ var bindingContext = new BindingContext
+ {
+ Items = new ObservableCollection<object>
+ {
+ new ContextFixture("John", "John")
+ }
+ };
+ var picker = new Picker
+ {
+ BindingContext = bindingContext
+ };
+ picker.SetBinding(Picker.ItemsSourceProperty, "Items");
+ picker.SetBinding(Picker.SelectedItemProperty, "SelectedItem");
+ Assert.AreEqual(1, picker.Items.Count);
+ Assert.AreEqual(-1, picker.SelectedIndex);
+ Assert.AreEqual(bindingContext.SelectedItem, picker.SelectedItem);
+ }
- picker.Items.RemoveAt (3);
- picker.Items.RemoveAt (2);
+ [Test]
+ public void TestNestedDisplayMemberPathExpression()
+ {
+ var obj = new ContextFixture
+ {
+ Nested = new ContextFixture.NestedClass
+ {
+ Nested = "NestedProperty"
+ }
+ };
+ var picker = new Picker
+ {
+ DisplayMemberPath = "Nested.Nested",
+ ItemsSource = new ObservableCollection<object>
+ {
+ obj
+ },
+ SelectedIndex = 0
+ };
+ Assert.AreEqual("NestedProperty", picker.Items[0]);
+ }
+ [Test]
+ public void TestItemsSourceEnums()
+ {
+ var picker = new Picker
+ {
+ ItemsSource = new ObservableCollection<TextAlignment>
+ {
+ TextAlignment.Start,
+ TextAlignment.Center,
+ TextAlignment.End
+ },
+ SelectedIndex = 0
+ };
+ Assert.AreEqual("Start", picker.Items[0]);
+ }
- Assert.AreEqual (1, picker.SelectedIndex);
+ [Test]
+ public void TestSelectedItemSet()
+ {
+ var obj = new ContextFixture("John", "John");
+ var bindingContext = new BindingContext
+ {
+ Items = new ObservableCollection<object>
+ {
+ obj
+ },
+ SelectedItem = obj
+ };
+ var picker = new Picker
+ {
+ BindingContext = bindingContext,
+ DisplayMemberPath = "DisplayName"
+ };
+ picker.SetBinding(Picker.ItemsSourceProperty, "Items");
+ picker.SetBinding(Picker.SelectedItemProperty, "SelectedItem");
+ Assert.AreEqual(1, picker.Items.Count);
+ Assert.AreEqual(0, picker.SelectedIndex);
+ Assert.AreEqual(obj, picker.SelectedItem);
+ }
- picker.Items.Clear ();
- Assert.AreEqual (-1, picker.SelectedIndex);
+ [Test]
+ public void TestSelectedItemChangeSelectedIndex()
+ {
+ var obj = new ContextFixture("John", "John");
+ var bindingContext = new BindingContext
+ {
+ Items = new ObservableCollection<object>
+ {
+ obj
+ },
+ };
+ var picker = new Picker
+ {
+ BindingContext = bindingContext,
+ DisplayMemberPath = "DisplayName"
+ };
+ picker.SetBinding(Picker.ItemsSourceProperty, "Items");
+ picker.SetBinding(Picker.SelectedItemProperty, "SelectedItem");
+ Assert.AreEqual(1, picker.Items.Count);
+ Assert.AreEqual(-1, picker.SelectedIndex);
+ Assert.AreEqual(null, picker.SelectedItem);
+ picker.SelectedItem = obj;
+ Assert.AreEqual(0, picker.SelectedIndex);
+ Assert.AreEqual(obj, picker.SelectedItem);
+ picker.SelectedIndex = -1;
+ Assert.AreEqual(-1, picker.SelectedIndex);
+ Assert.AreEqual(null, picker.SelectedItem);
}
- }
-}
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Picker.cs b/Xamarin.Forms.Core/Picker.cs
index d5f60e9d..3816bedf 100644
--- a/Xamarin.Forms.Core/Picker.cs
+++ b/Xamarin.Forms.Core/Picker.cs
@@ -1,6 +1,9 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Globalization;
+using System.Reflection;
using Xamarin.Forms.Platform;
namespace Xamarin.Forms
@@ -13,23 +16,64 @@ namespace Xamarin.Forms
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(Picker), default(string));
public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(Picker), -1, BindingMode.TwoWay,
- propertyChanged: (bindable, oldvalue, newvalue) =>
- {
- EventHandler eh = ((Picker)bindable).SelectedIndexChanged;
- if (eh != null)
- eh(bindable, EventArgs.Empty);
- }, coerceValue: CoerceSelectedIndex);
+ propertyChanged: OnSelectedIndexChanged,
+ coerceValue: CoerceSelectedIndex);
+
+ public static readonly BindableProperty SelectedValueMemberPathProperty = BindableProperty.Create(nameof(SelectedValueMemberPath), typeof(string), typeof(Picker));
+
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(Picker), default(IList), propertyChanged: OnItemsSourceChanged);
+
+ public static readonly BindableProperty DisplayMemberPathProperty = BindableProperty.Create(nameof(DisplayMemberPath), typeof(string), typeof(Picker), propertyChanged: OnDisplayMemberPathChanged);
+
+ public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(Picker), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
readonly Lazy<PlatformConfigurationRegistry<Picker>> _platformConfigurationRegistry;
+ public static readonly BindableProperty DisplayFuncProperty =
+ BindableProperty.Create(
+ nameof(DisplayFunc),
+ typeof(Func<object, string>),
+ typeof(Picker));
+
+ public static readonly BindableProperty DisplayConverterProperty =
+ BindableProperty.Create(
+ nameof(DisplayConverter),
+ typeof(IValueConverter),
+ typeof(Picker),
+ default(IValueConverter));
+
+
public Picker()
{
- Items = new ObservableList<string>();
((ObservableList<string>)Items).CollectionChanged += OnItemsCollectionChanged;
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Picker>>(() => new PlatformConfigurationRegistry<Picker>(this));
}
- public IList<string> Items { get; }
+ public string DisplayMemberPath
+ {
+ get { return (string)GetValue(DisplayMemberPathProperty); }
+ set { SetValue(DisplayMemberPathProperty, value); }
+ }
+
+ public Func<object, string> DisplayFunc
+ {
+ get { return (Func<object, string>)GetValue(DisplayFuncProperty); }
+ set { SetValue(DisplayFuncProperty, value); }
+ }
+
+ public IValueConverter DisplayConverter
+ {
+ get { return (IValueConverter)GetValue(DisplayConverterProperty); }
+ set { SetValue(DisplayConverterProperty, value); }
+ }
+
+ public IList<string> Items { get; } = new ObservableList<string>();
+
+ public IList ItemsSource
+ {
+ get { return (IList)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
public int SelectedIndex
{
@@ -37,6 +81,18 @@ namespace Xamarin.Forms
set { SetValue(SelectedIndexProperty, value); }
}
+ public object SelectedItem
+ {
+ get { return GetValue(SelectedItemProperty); }
+ set { SetValue(SelectedItemProperty, value); }
+ }
+
+ public string SelectedValueMemberPath
+ {
+ get { return (string)GetValue(SelectedValueMemberPathProperty); }
+ set { SetValue(SelectedValueMemberPathProperty, value); }
+ }
+
public Color TextColor
{
get { return (Color)GetValue(TextColorProperty); }
@@ -51,15 +107,182 @@ namespace Xamarin.Forms
public event EventHandler SelectedIndexChanged;
+ protected virtual string GetDisplayMember(object item)
+ {
+ if (DisplayConverter != null)
+ {
+ var display = DisplayConverter.Convert(item, typeof(string), null, CultureInfo.CurrentUICulture) as string;
+ if (display == null)
+ {
+ throw new ArgumentException("value must be converted to string");
+ }
+ return display;
+ }
+ if (DisplayFunc != null)
+ {
+ return DisplayFunc(item);
+ }
+ if (item == null)
+ {
+ return null;
+ }
+ bool isValueType = item.GetType().GetTypeInfo().IsValueType;
+ if (isValueType || string.IsNullOrEmpty(DisplayMemberPath) || item is string)
+ {
+ // For a mix of objects in ItemsSourc to be handled correctly in conjunction with DisplayMemberPath
+ // we need to handle value types and string so that GetPropertyValue doesn't throw exception if the property
+ // doesn't exist on the item object
+ return item.ToString();
+ }
+ return GetPropertyValue(item, DisplayMemberPath) as string;
+ }
+
+ void AddItems(NotifyCollectionChangedEventArgs e)
+ {
+ int index = e.NewStartingIndex < 0 ? Items.Count : e.NewStartingIndex;
+ foreach (object newItem in e.NewItems)
+ {
+ Items.Insert(index++, GetDisplayMember(newItem));
+ }
+ }
+
+ void BindItems()
+ {
+ Items.Clear();
+ foreach (object item in ItemsSource)
+ {
+ Items.Add(GetDisplayMember(item));
+ }
+ UpdateSelectedItem();
+ }
+
static object CoerceSelectedIndex(BindableObject bindable, object value)
{
var picker = (Picker)bindable;
return picker.Items == null ? -1 : ((int)value).Clamp(-1, picker.Items.Count - 1);
}
+ void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Move:
+ case NotifyCollectionChangedAction.Replace:
+ case NotifyCollectionChangedAction.Reset:
+ // Clear Items collection and re-populate it with values from ItemsSource
+ BindItems();
+ return;
+ case NotifyCollectionChangedAction.Remove:
+ RemoveItems(e);
+ break;
+ case NotifyCollectionChangedAction.Add:
+ AddItems(e);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ static object GetPropertyValue(object item, string memberPath)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+ if (string.IsNullOrEmpty(memberPath))
+ {
+ throw new ArgumentNullException(nameof(memberPath));
+ }
+ // Find the property by walking the display member path to find any nested properties
+ string[] propertyPathParts = memberPath.Split('.');
+ object propertyValue = item;
+ foreach (string propertyPathPart in propertyPathParts)
+ {
+ PropertyInfo propInfo = propertyValue.GetType().GetTypeInfo().GetDeclaredProperty(propertyPathPart);
+ if (propInfo == null)
+ {
+ throw new ArgumentException(
+ $"No property with name '{propertyPathPart}' was found on '{propertyValue.GetType().FullName}'");
+ }
+ propertyValue = propInfo.GetValue(propertyValue);
+ }
+ return propertyValue;
+ }
+
+ static void OnDisplayMemberPathChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var picker = (Picker)bindable;
+ if (picker.ItemsSource?.Count > 0)
+ {
+ picker.BindItems();
+ }
+ }
+
void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SelectedIndex = SelectedIndex.Clamp(-1, Items.Count - 1);
+ UpdateSelectedItem();
+ }
+
+ static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var picker = (Picker)bindable;
+ // Check if the ItemsSource value has changed and if so, unsubscribe from collection changed
+ var observable = oldValue as INotifyCollectionChanged;
+ if (observable != null)
+ {
+ observable.CollectionChanged -= picker.CollectionChanged;
+ }
+ observable = newValue as INotifyCollectionChanged;
+ if (observable != null)
+ {
+ observable.CollectionChanged += picker.CollectionChanged;
+ picker.BindItems();
+ }
+ else
+ {
+ // newValue is null so clear the items collection
+ picker.Items.Clear();
+ }
+ }
+
+ static void OnSelectedIndexChanged(object bindable, object oldValue, object newValue)
+ {
+ var picker = (Picker)bindable;
+ EventHandler eh = picker.SelectedIndexChanged;
+ eh?.Invoke(bindable, EventArgs.Empty);
+ picker.UpdateSelectedItem();
+ }
+
+ static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var picker = (Picker)bindable;
+ picker.UpdateSelectedIndex(newValue);
+ }
+
+ void RemoveItems(NotifyCollectionChangedEventArgs e)
+ {
+ int index = e.OldStartingIndex < Items.Count ? e.OldStartingIndex : Items.Count;
+ // TODO: How do we determine the order of which the items were removed
+ foreach (object _ in e.OldItems)
+ {
+ Items.RemoveAt(index--);
+ }
+ }
+
+ void UpdateSelectedIndex(object selectedItem)
+ {
+ string displayMember = GetDisplayMember(selectedItem);
+ int index = Items.IndexOf(displayMember);
+ // TODO Should we prevent call to FindObject since the object is already known
+ // by setting a flag, or otherwise indicate, that we, internally, forced a SelectedIndex changed
+ SelectedIndex = index;
+ }
+
+ void UpdateSelectedItem()
+ {
+ // coerceSelectedIndex ensures that SelectedIndex is in range [-1,ItemsSource.Count)
+ SelectedItem = SelectedIndex == -1 ? null : ItemsSource?[SelectedIndex];
}
public IPlatformElementConfiguration<T, Picker> On<T>() where T : IConfigPlatform
@@ -67,4 +290,4 @@ namespace Xamarin.Forms
return _platformConfigurationRegistry.Value.On<T>();
}
}
-} \ No newline at end of file
+}