JMan
JMan

Reputation: 2629

BindableProperty not updated on ViewModel

In Xamarin.Forms I implemented a custom Picker. The ItemsSource is set correctly. However when i change the selected item it does not update the property on my ViewModel.

The BindablePicker:

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());
        }
    }
}

The Xamlpage:

<controls:BindablePicker Title="Category"
        ItemsSource="{Binding Categories}"
        SelectedItem="{Binding SelectedCategory}"
        Grid.Row="2"/>

The ViewModel properties, didn't implement the NotifyPropertyChanged on the properties since they only need to be updated from the ´Viewto theViewModel`:

public Category SelectedCategory { get; set; }
public ObservableCollection<Category> Categories { get; set; }

Upvotes: 4

Views: 4196

Answers (2)

JMan
JMan

Reputation: 2629

Beside adding the Mode=TwoWay to my binding a had to change some things in my picker so it could work with the actual objects i had it bound to.

The Items property of the Xamarin Picker is an IList<string> since all my items are added to it as a string it keeps the same indexed value.

Therefor the ItemsSource is changed to an IList:

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

I also modified the SelectedIndexChangedmethod so it doesn't retrieve the item from the Items but from the ItemsSource, wich is in my case an IList<Category>:

private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
    {
        if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
        {
            SelectedItem = null;
        }
        else
        {
            SelectedItem = ItemsSource[SelectedIndex];
        }
    }

In my ViewModel i no longer use the ObservableCollection for my Categories but add these items to an IList<Category>.

The ObservableCollectionhas no use since when my BindablePicker binds to the ItemsSource the items are added to the internal IList<string>. when adding an item to the collection it will not be updated. I now update the entire ItemSourceif an item is changed.

Upvotes: 0

Stephane Delcroix
Stephane Delcroix

Reputation: 16232

When creating your BindableProperty:

public static BindableProperty SelectedItemProperty =
    BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), propertyChanged: OnSelectedItemChanged);

without specifying the defaultBindingMode, the BindingMode is set to OneWay, meaning the Binding is updated from source (your view model) to target (your view).

This can be fixed by changing the defaultBindingMode:

public static BindableProperty SelectedItemProperty =
    BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);

or, if it's the default you want for your picker, but want to update the source only in this view, you can specify the BindingMode for this instance of the Binding only:

<controls:BindablePicker Title="Category"
    ItemsSource="{Binding Categories}"
    SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
    Grid.Row="2"/>

Upvotes: 5

Related Questions