Skip to content

Instantly share code, notes, and snippets.

@lloydkevin
Last active August 5, 2016 23:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lloydkevin/9914f0cdaef852bd4cfad32414637101 to your computer and use it in GitHub Desktop.
Save lloydkevin/9914f0cdaef852bd4cfad32414637101 to your computer and use it in GitHub Desktop.
BindablePicker Example
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();
}
}
}
// 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);
}
<controls:BindablePicker ItemsSource="{Binding Users}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedUser, Mode=TwoWay}" />
public class UserModel
{
public string Name { get; set; }
public string Id { get; set; }
}
Copy link

ghost commented Aug 5, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment