1
votes

I want to extend the Xamarin.Forms Picker so I can bind a collection to it. After a quick search I found this page: Picker Example with two great examples. Refusing to just copy&paste the code (for learning purposes) I went on and make my own based on the two examples.

It's almost identical except mine does not work.

When I do not provide a collection to the ItemsSource everything works fine. Whenever I do assign a collection I get the following error:

Xamarin.Forms.Xaml.XamlParseException: Position 9:32. Cannot assign property "ItemsSource": type mismatch between "Xamarin.Forms.Binding" and "System.Collections.IEnumerable"

Picker:

public class BindablePicker : Picker
{
    private static readonly BindableProperty ItemsSourceProperty =
        BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);

    private static readonly BindableProperty SelectedItemProperty =
        BindableProperty.Create("SelectedItem", typeof(object), typeof(BindablePicker));

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    public object SelectedItem
    {
        get { return GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }
    public string DisplayMember { get; set; }


    private static void OnItemsSourceChanged(BindableObject bindable, Object oldValue, Object newValue)
    {
        var newval = newValue as IEnumerable; //Had to implement this because of the non-generic .Create() method expects Object as param.
        var picker = bindable as BindablePicker;

        if (picker != null)
        {
            picker.Items.Clear();
            if (newval == null) return;

            foreach (var item in newval)
            {
                if (string.IsNullOrEmpty(picker.DisplayMember))
                {
                    picker.Items.Add(item.ToString());
                }
                else
                {
                    var prop = item.GetType()
                        .GetRuntimeProperties()
                        .FirstOrDefault(p => string.Equals(p.Name, picker.DisplayMember, StringComparison.OrdinalIgnoreCase));

                    picker.Items.Add(prop.GetValue(item).ToString());
                }
            }
        }
    }

    private void OnSelectedIndexChanged(object sender, EventArgs args)
    {
        if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
        {
            SelectedItem = null;
        }
        else
        {
            SelectedItem = ItemsSource.ItemOf(SelectedIndex); //ItemOf is an extension method I made for IEnumerable (has to be tested).
        }
    }
}

ViewModel (parts):

public class HomePageViewModel : ViewModelBase
{
    //In the app this is populated with a List<Person>.
    public IEnumerable<Person> People { get; set; }
}

XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:controls="clr-namespace:TestApp.Controls;assembly=TestApp"
         x:Class="TestApp.Views.HomePage">
  <ContentPage.Content>
     <StackLayout>

         <controls:BindablePicker ItemsSource="{Binding People}"
                                  HorizontalOptions="Center" />
     </StackLayout>
  </ContentPage.Content>
</ContentPage>

Note that the first example picker from the linked page works with the provided VM/View setup.

I'm also not finished with the picker, I still want to provide TwoWay binding to the SelectedItem property and support for ObservableCollection.

Yours,

1

1 Answers

1
votes

At first I thought I was turning mad.

Then I thought something was seriously broken.

But finally I figured it out...

private static readonly BindableProperty ItemsSourceProperty =
    BindableProperty.Create("ItemsSource", typeof(IEnumerable),  
    typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);

In order for the Xaml parser to see a BindableProperty as such, it has to be public (and static, but you got that part right).

In your case, the Xaml parser doesn't see the BindableProperty, so it fallback to the property, but it doesn't have any way to set the Binding, and as the types doesn't match, you get the exception.

Change your code to

public static readonly BindableProperty ItemsSourceProperty =
    BindableProperty.Create("ItemsSource", typeof(IEnumerable),  
    typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);