diff options
author | Joakim Carselind <joakim.carselind@cub.se> | 2016-11-09 09:23:52 +0100 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-11-16 12:03:13 -0800 |
commit | 693cc75068fed11c2760a1e8ef2b05310359bbea (patch) | |
tree | 987b8caf75450aaf1b59440076f753ceab5b141b /Xamarin.Forms.Core | |
parent | d97dfe91b28a30e9a350155262b1149eed8d462f (diff) | |
download | xamarin-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
Diffstat (limited to 'Xamarin.Forms.Core')
-rw-r--r-- | Xamarin.Forms.Core/Picker.cs | 241 |
1 files changed, 232 insertions, 9 deletions
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 +} |