Skip to content

Instantly share code, notes, and snippets.

@hjerpbakk
Created July 17, 2014 19:26
Show Gist options
  • Save hjerpbakk/286cfbdd5dfd379a9155 to your computer and use it in GitHub Desktop.
Save hjerpbakk/286cfbdd5dfd379a9155 to your computer and use it in GitHub Desktop.
A Picker-control for Xamarin.Forms which enables data binding through an ItemsSource and the SelectedItem.
using System;
using Xamarin.Forms;
using System.Collections;
namespace YourNamespace.Views.Controls {
public class BindablePicker : Picker
{
public BindablePicker()
{
this.SelectedIndexChanged += OnSelectedIndexChanged;
}
public static BindableProperty ItemsSourceProperty =
BindableProperty.Create<BindablePicker, IEnumerable>(o => o.ItemsSource, default(IEnumerable), propertyChanged: OnItemsSourceChanged);
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object),propertyChanged: OnSelectedItemChanged);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject bindable, IEnumerable oldvalue, IEnumerable newvalue)
{
var picker = bindable as BindablePicker;
picker.Items.Clear();
if (newvalue != null)
{
//now it works like "subscribe once" but you can improve
foreach (var item in newvalue)
{
picker.Items.Add(item.ToString());
}
}
}
private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = Items[SelectedIndex];
}
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var picker = bindable as BindablePicker;
if (newvalue != null)
{
picker.SelectedIndex = picker.Items.IndexOf(newvalue.ToString());
}
}
}
}
@kahanu
Copy link

kahanu commented Feb 21, 2015

Nice! Thanks.

@jmganesh
Copy link

jmganesh commented Mar 2, 2015

Could anyone please guide as to how do we use this code above? is there any sample which explains the use of above code?
Thanks

@Odonno
Copy link

Odonno commented Apr 30, 2015

Yeah, exactly what I need.

@jmganesh If you still need the use code, here it is :

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:userControls="clr-namespace:myAssembly.UserControls;assembly=myAssembly"
             x:Class="myAssembly.Views.ContentPage">

    <userControls:BindablePicker ItemsSource="{Binding Path=myCollection}" Selecteditem="{Binding Path=myItem}" />

</ContentPage>

And of course, you need to bind to a ViewModel that contain your Collection and your SelectedItem (with PropertyChanged implementation).

@EugeneChua91
Copy link

@Odonno, I not really understand how this Bindable Picker works in xaml and ViewModel. Mind to share a very complete basic example on the Binding Path = myCollection and Binding Path = myItem? Thanks.

@davidlebr1
Copy link

@EugeneChua91 You can put Path= or not it will be the same thing. It's just for clarity that we can put Path=. You need to Bind to a collection from your ViewModel.

@adrocca
Copy link

adrocca commented Aug 16, 2015

If I want use it using xamarin shared forms getting information from web services example: 2 fields id and description. What must I do?

@xtellurian
Copy link

Works well, cheers!

@khanzzirfan
Copy link

Can someone review the code below, if I have implemented bindings correctly

    private ObservableCollection<string> _myCollection;
    public ObservableCollection<string> myCollection {
        get
        {
            return _myCollection;
        }
        set
        {
            _myCollection = value;
            OnPropertyChanged("myCollection");
        }
    }



    private string _myitem;
    public string myItem
    {
        get
        {
            return _myitem;
        }
        set
        {
            _myitem = value;
            OnPropertyChanged("myItem");
        }
    }

@khanzzirfan
Copy link

Hi,
Currently the below code works from IOS simulator and showing picker values and save data.
myCollection .Clear();
var timepickerlist = GetTimeList().ToList();
foreach (var t in timepickerlist)
{
myCollection.Add(t);
}

But the above code does not work in Android emulators. I have to modify the above code as
myCollection = new ObservableCollection(timepickerlist);

Could someone point me if there is a better way to achieve the above code and populating observable collection.

Regards,
Irfan

@julesx
Copy link

julesx commented Feb 3, 2016

There are a ton of BindablePicker implementations for XF, but none of them handle collection changed events after render. Someone please!!

//now it works like "subscribe once" but you can improve

how??

@DrEmigius
Copy link

You should set the BindingMode of SelectedItemProperty to "TwoWay", so the SelectedItem can also be set by the picker.
Otherwise OnSelectedIndexChanged will never update the SelectedItem binding target even though the value of the picker has changed.

Greetings,
Dennis

@johnslaby
Copy link

Thanks, this is a really helpful solution.

@rump1992
Copy link

Thanks for this! Saved me with this. Generic version of create() is no longer supported. Any suggestions how to rewrite?

@StillLearnin
Copy link

StillLearnin commented Jun 13, 2016

Generic version of create() is no longer supported. Any suggestions how to rewrite?

        public static BindableProperty ItemsSourceProperty =
            BindableProperty.Create<BindablePicker, IEnumerable>(o => o.ItemsSource, default(IEnumerable), propertyChanged: OnItemsSourceChanged);

Same question here...

@robece
Copy link

robece commented Jun 17, 2016

I did some modifications to avoid the use of the deprecated Create<> method:

   public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(List<string>), typeof(BindablePicker), null, BindingMode.OneWay, null, propertyChanged: OnItemsSourceChanged);

    private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var picker = bindable as BindablePicker;
        picker.Items.Clear();
        if ((List<string>) newValue != null)
        {
            //now it works like "subscribe once" but you can improve
            foreach (var item in (List<string>)newValue)
            {
                picker.Items.Add(item.ToString());
            }
        }
    }

@scastria
Copy link

I think this implementation has a bug! Line 53 in the code listing at the top sets the SelectedItem to Items[SelectedIndex]. This is wrong as Items[] is the base class Picker collection which are STRINGS. The user of this BindablePicker class will expect the SelectedItem to be one of the items that he supplied in the ItemsSource property which is an object. Therefore, Line 53 should set SelectedItem to ItemsSource[SelectedIndex] which also requires changing ItemsSource from IEnumerable to IList.

See this Xamarin forum post for more details:

http://forums.xamarin.com/discussion/70172/bindable-picker-control-not-updating-bound-property#latest

@erportico
Copy link

erportico commented Aug 22, 2016

Hi guys,
I have a problem when I try to bind. The error is:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

I can't understand why.
Thank you in advance,

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