Reputation: 2629
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 Xaml
page:
<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 the
ViewModel`:
public Category SelectedCategory { get; set; }
public ObservableCollection<Category> Categories { get; set; }
Upvotes: 4
Views: 4196
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 SelectedIndexChanged
method 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 ObservableCollection
has 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 ItemSource
if an item is changed.
Upvotes: 0
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