Skip to content

Instantly share code, notes, and snippets.

@joacar
Created August 4, 2016 12:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joacar/07d49a15cab3bbb7c70e894e25110a05 to your computer and use it in GitHub Desktop.
Save joacar/07d49a15cab3bbb7c70e894e25110a05 to your computer and use it in GitHub Desktop.
BindablePicker for Xamarin.Forms that handles Add, Remove, Reset,Clear and Move actions.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using Xamarin.Forms;
namespace Clistr.Controls
{
public class BindablePicker : Picker
{
public static readonly BindableProperty DisplayMemberPathProperty =
BindableProperty.Create(
nameof(DisplayMemberPath),
typeof(string),
typeof(BindablePicker),
default(string));
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(
nameof(ItemsSource),
typeof(IList),
typeof(BindablePicker),
default(IList),
propertyChanged: OnItemsSourceChanged);
public static readonly BindableProperty SelectedItemProperty =
BindableProperty.Create(
nameof(SelectedItem),
typeof(object),
typeof(BindablePicker),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: OnSelectedItemChanged);
public BindablePicker()
{
SelectedIndexChanged += OnSelectedIndexChanged;
}
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
{
var picker = (BindablePicker)bindable;
picker.SetSelectedItem(newValue);
}
private void SetSelectedItem(object selectedItem)
{
var displayMember = GetDisplayMember(selectedItem);
var 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;
}
private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var picker = (BindablePicker)bindable;
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();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
BindItems();
return;
case NotifyCollectionChangedAction.Remove:
RemoveItems(e);
break;
case NotifyCollectionChangedAction.Add:
AddItems(e);
break;
case NotifyCollectionChangedAction.Move:
// TODO Make more intelligent decision
BindItems();
break;
case NotifyCollectionChangedAction.Replace:
// TODO Make more intelligent decision
BindItems();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void AddItems(NotifyCollectionChangedEventArgs e)
{
var index = e.NewStartingIndex < 0 ? Items.Count : e.NewStartingIndex;
foreach (var newItem in e.NewItems)
{
Items.Insert(index++, GetDisplayMember(newItem));
}
}
private void RemoveItems(NotifyCollectionChangedEventArgs e)
{
var index = e.OldStartingIndex < Items.Count ? e.OldStartingIndex : Items.Count;
// TODO: How do we determine the order of which the items were removed
foreach (var _ in e.OldItems)
{
Items.RemoveAt(index--);
}
}
private void OnSelectedIndexChanged(object sender, EventArgs e)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = ItemsSource[SelectedIndex];
}
}
private void BindItems()
{
Items.Clear();
foreach (var item in ItemsSource)
{
Items.Add(GetDisplayMember(item));
}
}
private static bool IsPrimitive(object item)
{
// TODO What is missing
return item is string || item is int || item is double || item is decimal || item is Enum ||
item is DateTime;
}
protected virtual string GetDisplayMember(object item)
{
// TODO How to handle null properly. Here or earlier in the process
if (item == null)
{
return null;
}
// TODO How to handle Nullable types
if (IsPrimitive(item) || string.IsNullOrEmpty(DisplayMemberPath))
{
return item.ToString();
}
// Find the property name and returns its value
var type = item.GetType();
var property = type.GetRuntimeProperty(DisplayMemberPath);
if (property == null)
{
throw new ArgumentException($"No property with name '{DisplayMemberPath}' was found on '{type.FullName}'");
}
var displayMember = property.GetValue(item, null).ToString();
return displayMember;
}
}
}
@aybarsyalcin
Copy link

Thank you. You saved my life

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