Last active
August 5, 2016 23:15
-
-
Save lloydkevin/9914f0cdaef852bd4cfad32414637101 to your computer and use it in GitHub Desktop.
BindablePicker Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Reflection; | |
using Xamarin.Forms; | |
namespace JetStreamXamarin.Controls | |
{ | |
/// <summary> | |
/// Source from: | |
/// https://oceanware.wordpress.com/2016/06/13/xamarin-forms-bindable-picker/ | |
/// | |
/// Most of the credit for this control goes these two blog posts: | |
/// https://forums.xamarin.com/discussion/63565/xamarin-forms-picker-data-binding-using-mvvm | |
/// https://forums.xamarin.com/discussion/30801/xamarin-forms-bindable-picker | |
/// | |
/// I added in SelectedValue & SelectedValuePath and internal control logic | |
/// </summary> | |
public class BindablePicker : Picker | |
{ | |
Boolean _disableNestedCalls; | |
public String DisplayMemberPath { get; set; } | |
public IEnumerable ItemsSource | |
{ | |
get { return (IEnumerable)GetValue(ItemsSourceProperty); } | |
set { SetValue(ItemsSourceProperty, value); } | |
} | |
public Object SelectedItem | |
{ | |
get { return GetValue(SelectedItemProperty); } | |
set | |
{ | |
if (SelectedItem != value) | |
{ | |
SetValue(SelectedItemProperty, value); | |
InternalSelectedItemChanged(); | |
} | |
} | |
} | |
public Object SelectedValue | |
{ | |
get { return GetValue(SelectedValueProperty); } | |
set | |
{ | |
SetValue(SelectedValueProperty, value); | |
InternalSelectedValueChanged(); | |
} | |
} | |
public String SelectedValuePath { get; set; } | |
public BindablePicker() | |
{ | |
this.SelectedIndexChanged += OnSelectedIndexChanged; | |
} | |
public event EventHandler<SelectedItemChangedEventArgs> ItemSelected; | |
public static readonly BindableProperty ItemsSourceProperty = | |
BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(BindablePicker), | |
null, propertyChanged: OnItemsSourceChanged); | |
public static readonly BindableProperty SelectedItemProperty = | |
BindableProperty.Create("SelectedItem", typeof(Object), typeof(BindablePicker), | |
null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged); | |
public static readonly BindableProperty SelectedValueProperty = | |
BindableProperty.Create("SelectedValue", typeof(Object), typeof(BindablePicker), | |
null, BindingMode.TwoWay, propertyChanged: OnSelectedValueChanged); | |
void InternalSelectedItemChanged() | |
{ | |
if (_disableNestedCalls) | |
{ | |
return; | |
} | |
var selectedIndex = -1; | |
Object selectedValue = null; | |
if (ItemsSource != null) | |
{ | |
var index = 0; | |
var hasSelectedValuePath = !String.IsNullOrWhiteSpace(SelectedValuePath); | |
foreach (var item in ItemsSource) | |
{ | |
if (item != null && item.Equals(SelectedItem)) | |
{ | |
selectedIndex = index; | |
if (hasSelectedValuePath) | |
{ | |
var type = item.GetType(); | |
var prop = type.GetRuntimeProperty(SelectedValuePath); | |
selectedValue = prop.GetValue(item); | |
} | |
break; | |
} | |
index++; | |
} | |
} | |
_disableNestedCalls = true; | |
SelectedValue = selectedValue; | |
SelectedIndex = selectedIndex; | |
_disableNestedCalls = false; | |
} | |
void InternalSelectedValueChanged() | |
{ | |
if (_disableNestedCalls) | |
{ | |
return; | |
} | |
if (String.IsNullOrWhiteSpace(SelectedValuePath)) | |
{ | |
return; | |
} | |
var selectedIndex = -1; | |
Object selectedItem = null; | |
var hasSelectedValuePath = !String.IsNullOrWhiteSpace(SelectedValuePath); | |
if (ItemsSource != null && hasSelectedValuePath) | |
{ | |
var index = 0; | |
foreach (var item in ItemsSource) | |
{ | |
if (item != null) | |
{ | |
var type = item.GetType(); | |
var prop = type.GetRuntimeProperty(SelectedValuePath); | |
if (prop.GetValue(item) == SelectedValue) | |
{ | |
selectedIndex = index; | |
selectedItem = item; | |
break; | |
} | |
} | |
index++; | |
} | |
} | |
_disableNestedCalls = true; | |
SelectedItem = selectedItem; | |
SelectedIndex = selectedIndex; | |
_disableNestedCalls = false; | |
} | |
static void OnItemsSourceChanged(BindableObject bindable, Object oldValue, Object newValue) | |
{ | |
if (Equals(newValue, null) && Equals(oldValue, null)) | |
{ | |
return; | |
} | |
var picker = (BindablePicker)bindable; | |
// this fixed my problem, I believe. | |
picker._disableNestedCalls = true; | |
picker.Items.Clear(); | |
picker._disableNestedCalls = false; | |
if (!Equals(newValue, null)) | |
{ | |
var hasDisplayMemberPath = !String.IsNullOrWhiteSpace(picker.DisplayMemberPath); | |
foreach (var item in (IEnumerable)newValue) | |
{ | |
if (hasDisplayMemberPath) | |
{ | |
var type = item.GetType(); | |
var prop = type.GetRuntimeProperty(picker.DisplayMemberPath); | |
picker.Items.Add(prop.GetValue(item).ToString()); | |
} | |
else | |
{ | |
picker.Items.Add(item.ToString()); | |
} | |
} | |
picker._disableNestedCalls = true; | |
picker.SelectedIndex = -1; | |
picker._disableNestedCalls = false; | |
if (picker.SelectedItem != null) | |
{ | |
picker.InternalSelectedItemChanged(); | |
} | |
else if (hasDisplayMemberPath && picker.SelectedValue != null) | |
{ | |
picker.InternalSelectedValueChanged(); | |
} | |
} | |
else | |
{ | |
picker._disableNestedCalls = true; | |
picker.SelectedIndex = -1; | |
picker.SelectedItem = null; | |
picker.SelectedValue = null; | |
picker._disableNestedCalls = false; | |
} | |
} | |
void OnSelectedIndexChanged(Object sender, EventArgs e) | |
{ | |
if (_disableNestedCalls) | |
{ | |
return; | |
} | |
if (SelectedIndex < 0 || ItemsSource == null || !ItemsSource.GetEnumerator().MoveNext()) | |
{ | |
_disableNestedCalls = true; | |
if (SelectedIndex != -1) | |
{ | |
SelectedIndex = -1; | |
} | |
SelectedItem = null; | |
SelectedValue = null; | |
_disableNestedCalls = false; | |
return; | |
} | |
_disableNestedCalls = true; | |
var index = 0; | |
var hasSelectedValuePath = !String.IsNullOrWhiteSpace(SelectedValuePath); | |
foreach (var item in ItemsSource) | |
{ | |
if (index == SelectedIndex) | |
{ | |
SelectedItem = item; | |
if (hasSelectedValuePath) | |
{ | |
var type = item.GetType(); | |
var prop = type.GetRuntimeProperty(SelectedValuePath); | |
SelectedValue = prop.GetValue(item); | |
} | |
break; | |
} | |
index++; | |
} | |
_disableNestedCalls = false; | |
} | |
static void OnSelectedItemChanged(BindableObject bindable, Object oldValue, Object newValue) | |
{ | |
var boundPicker = (BindablePicker)bindable; | |
boundPicker.ItemSelected?.Invoke(boundPicker, new SelectedItemChangedEventArgs(newValue)); | |
boundPicker.InternalSelectedItemChanged(); | |
} | |
static void OnSelectedValueChanged(BindableObject bindable, Object oldValue, Object newValue) | |
{ | |
var boundPicker = (BindablePicker)bindable; | |
boundPicker.InternalSelectedValueChanged(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// using FreshMVVM | |
protected override async void ViewIsAppearing(object sender, EventArgs e) | |
{ | |
base.ViewIsAppearing(sender, e); | |
Users = GetUserModels(); | |
SelectedUser = Users.Count == 1 ? Users.First() : Users.FirstOrDefault(x => x.Id == _preferences.GetCurrentUser().Id); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<controls:BindablePicker ItemsSource="{Binding Users}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedUser, Mode=TwoWay}" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class UserModel | |
{ | |
public string Name { get; set; } | |
public string Id { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Please see my blog post again.
<controls:BindablePicker
DisplayMemberPath="Name"
SelectedValuePath="Abbreviation"
ItemsSource="{Binding Path=Countries}"
SelectedValue="{Binding Path=Person.Country, Mode=TwoWay}" />
If you use DisplayMemberPath you have to follow the above code. Use SelectedValue not SelectedItem.
Karl