BindableProperty not updated on ViewModel
In Xamarin.Forms, I have implemented a custom Picker
.
ItemsSource
installed correctly. However, when I change the selected item, it does not update the property in my ViewModel.
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());
}
}
}
Page Xaml
:
<controls:BindablePicker Title="Category"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
Grid.Row="2"/>
Properties were ViewModel
not implemented NotifyPropertyChanged
in properties, since they only need to be updated from the 'View to the
ViewModel`:
public Category SelectedCategory { get; set; }
public ObservableCollection<Category> Categories { get; set; }
source to share
Apart from adding Mode=TwoWay
to my binding, I had to change a few things in my collector so that it can work with the actual objects I was binding to.
The property Items
Xamarin Picker
is IList<string>
since all my elements are appended to it as a string, it keeps the same indexed value.
For this, the ItemsSource parameter is changed to IList
:
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
I also modified the method SelectedIndexChanged
so that it does not retrieve the item from Items
, but from ItemsSource
, which in my case is IList<Category>
:
private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = ItemsSource[SelectedIndex];
}
}
In mine, ViewModel
I no longer use ObservableCollection
for mine Categories
, but add these elements to IList<Category>
.
ObservableCollection
doesn't make sense since mine is BindablePicker
linked to ItemsSource
, items are added to the inner IList<string>
. when you add an item to the collection, it will not update. Now I am updating the whole ItemSource
if the item is changed.
source to share
When creating your BindableProperty:
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), propertyChanged: OnSelectedItemChanged);
without specifying defaultBindingMode
, the parameter is BindingMode
set to a value OneWay
, which means the binding is updated from the source (your view model) to the 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 this is the default you want for your collector, but only want to update the source in that view, you can specify the BindingMode for that Binding instance only:
<controls:BindablePicker Title="Category"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
Grid.Row="2"/>
source to share