Search code examples
c#xamarinxamarin.formspicker

Picker within Custom Control


I am building up a few forms (using TableView) and noticed that I was styling the cells the same. I decided that I would refactor this repeated code into a common control.

I am struggling to get the binding to work on the picker correctly. My custom control looks like this:picture of the cell

My control is a ViewCell, so that I can display it within a TableView:

public partial class PickerCell : ViewCell
{
...
}

I have been able to set the pickers ItemSource, but I cannot get the SelectedItem or DisplayItemBinding properties to work.

I've seen this post from 2015 (ancient now) but I tried and the methods were marked as obsolete and didn't work anyway.

I've also tried in the controls ctor:

ItemPicker.SetBinding(Picker.ItemsSourceProperty, "ItemSource");
ItemPicker.SetBinding(Picker.SelectedItemProperty, "SelectedItem", BindingMode.TwoWay);

but that didn't work either.

I basically just want to add a way so that I can bind to the picker from my xaml to the control. I really hope this is possible because I use this exact view cell around my application maybe 9/10 times. I really don't to be repeating myself alot and I'd normally create controls in this scenario. For instance I have a similarly styled cell for entries and that works perfectly....

Here is the code I used to set the item source:

public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
    nameof(ItemsSource),
    typeof(IList),
    typeof(PickerCell)
    propertyChanged: (bindable, oldVal, newVal) => ((PickerCell) bindable).OnItemsSourceChanged((IList) newVal)
);

public IList ItemsSource
{
    get { return (IList)GetValue(ItemsSourceProperty); }
    set { SetValue(ItemsSourceProperty, value); }
}

private void OnItemsSourceChanged(IList list)
{
    ItemPicker.ItemsSource = list;
}

I tried to implement some of the code for picker from Xamarin but to no avail.

Any ideas?


Solution

  • Here is the implementation of a custom picker cell. The only change to vanilla xamarin is that the binding display must be input as a string instead of binding: property.

    Here is all the important code:

    Bindable Property Declarations

    /// <summary>
    /// The placeholder property.
    /// </summary>
    public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(
        nameof(Placeholder),
        typeof(string),
        typeof(CustomPickerCell),
        propertyChanged: (bindable, oldVal, newVal) => ((CustomPickerCell)bindable).OnPlaceholderChanged((string)newVal)
    );
    
    /// <summary>
    /// The items source property.
    /// </summary>
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
        nameof(ItemsSource),
        typeof(IList),
        typeof(CustomPickerCell),
        propertyChanged: (bindable, oldVal, newVal) => ((CustomPickerCell)bindable).OnItemsSourceChanged((IList)newVal)
    );
    
    /// <summary>
    /// The selected item property.
    /// </summary>
    public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(
        nameof(SelectedItem),
        typeof(object),
        typeof(CustomPickerCell),
        defaultBindingMode: BindingMode.TwoWay,
        propertyChanged: (bindable, oldVal, newVal) => ((CustomPickerCell)bindable).OnSelectedItemChanged((object)newVal)
    );
    
    /// <summary>
    /// The item display binding property
    /// NOTE: Use the name of the property, you do not need to bind to this property.
    /// </summary>
    public static readonly BindableProperty ItemDisplayBindingProperty = BindableProperty.Create(
        nameof(ItemDisplayBinding),
        typeof(string),
        typeof(CustomPickerCell),
        defaultBindingMode: BindingMode.TwoWay,
        propertyChanged: (bindable, oldVal, newVal) => ((CustomPickerCell)bindable).OnItemDisplayBindingChanged((string)newVal)
    );
    

    Bindable Properties

    /// <summary>
    /// The cell's placeholder (select a ....).
    /// </summary>
    /// <value>The placeholder.</value>
    public string Placeholder
    {
        get { return (string)GetValue(PlaceholderProperty); }
        set { SetValue(PlaceholderProperty, value); }
    }
    
    /// <summary>
    /// The cell's picker item source.
    /// </summary>
    /// <value>The items source.</value>
    public IList ItemsSource
    {
        get { return (IList)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    
    /// <summary>
    /// Gets or sets the selected item.
    /// </summary>
    /// <value>The selected item.</value>
    public object SelectedItem
    {
        get { return GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }
    
    /// <summary>
    /// Gets or sets the item display binding.
    /// </summary>
    /// <value>The item display binding.</value>
    public string ItemDisplayBinding
    {
        get { return (string)GetValue(ItemDisplayBindingProperty); }
        set { SetValue(ItemDisplayBindingProperty, value); }
    }
    

    Property Changed Methods

    /// <summary>
    /// Called when PlaceholderProperty changes.
    /// </summary>
    /// <param name="newVal">New value.</param>
    private void OnPlaceholderChanged(string newVal)
    {
        ItemPicker.Title = newVal;
    }
    
    /// <summary>
    /// Called when ItemSourceProperty changes.
    /// </summary>
    private void OnItemsSourceChanged(IList list)
    {
        ItemPicker.ItemsSource = list;
    }
    
    /// <summary>
    /// Called when SelectedItemProperty changes.
    /// </summary>
    /// <param name="obj">Object.</param>
    private void OnSelectedItemChanged(object obj)
    {
        ItemPicker.SelectedItem = obj;
    }
    
    /// <summary>
    /// Called when ItemDisplayBindingProperty changes.
    /// </summary>
    /// <param name="newvalue">Newvalue.</param>
    private void OnItemDisplayBindingChanged(string newvalue)
    {
        ItemPicker.ItemDisplayBinding = new Binding(newvalue);
    }
    

    Initialisation

    public CustomPickerCell()
    {
        InitializeComponent();
    
        ItemPicker.ItemsSource = this.ItemsSource;
        ItemPicker.SelectedItem = this.SelectedItem;
    
        ItemPicker.SelectedIndexChanged += OnSelectedIndexChanged;
    }
    
    /// <summary>
    /// Calle when ItemPicker's SelectedIndexChanged event fires.
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    void OnSelectedIndexChanged(object sender, EventArgs e)
    {
        this.SelectedItem = (ItemPicker.SelectedIndex < 0 || ItemPicker.SelectedIndex > ItemPicker.Items.Count - 1) ? null : ItemsSource[ItemPicker.SelectedIndex];
    }
    

    Now all you need to do is add this cell to your xaml and you are good to go!

    Usage

    <!-- Where controls is a namespace defined in your xaml -->
    <!-- Not not to bind ItemDisplayBinding, you only need the property name as a string-->
    <controls:CustomPickerCell Title="Type" Placeholder="Select an ice cream" 
                            ItemsSource="{Binding IceCreamList}" 
                            SelectedItem="{Binding SelectedIceCream, Mode=TwoWay}" 
                            ItemDisplayBinding="Name"/>