Sino a non molto tempo fa avere un picker che fosse bindable, e ben funzionante, era il "sogno proibito" di ogni utilizzatore di Xamarin: oggi questo sogno è diventato realtà !

Dopo test, prove, ore a mettere giù snippets rubacchiati quà e là, oppure suggeriti da qualche avo in sogno, ho trovato il post di Karl Shifflett su Oceanware (in linkografia) che riporta un codice discretamente funzionante e che riporto nel seguito, con un esempio di utilizzo dello stesso all'interno di una ListView.

Ecco il codice del bindable: ovviamente sul post originale troverete informazioni più dettagliate.

 

public class BindablePicker : Picker {

    Boolean _disableNestedCalls;

    public String DisplayMemberPath { get; set; }

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

    public Object SelectedItem {
        get { return GetValue(SelectedItemProperty); }
        set {
            if (SelectedItem != value) {
                SetValue(SelectedItemProperty, value);
                InternalSelectedItemChanged();
            }
        }
    }

    public Object SelectedValue {
        get { return GetValue(SelectedValueProperty); }
        set {
            SetValue(SelectedValueProperty, value);
            InternalSelectedValueChanged();
        }
    }

    public String SelectedValuePath { get; set; }

    public BindablePicker() {
        this.SelectedIndexChanged += OnSelectedIndexChanged;
    }

    public event EventHandler ItemSelected;

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

    public static readonly BindableProperty SelectedItemProperty =
        BindableProperty.Create("SelectedItem", typeof(Object), typeof(BindablePicker),
            null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);

    public static readonly BindableProperty SelectedValueProperty =
        BindableProperty.Create("SelectedValue", typeof(Object), typeof(BindablePicker),
            null, BindingMode.TwoWay, propertyChanged: OnSelectedValueChanged);

    void InternalSelectedItemChanged() {
        if (_disableNestedCalls) {
            return;
        }

        var selectedIndex = -1;
        Object selectedValue = null;
        if (ItemsSource != null) {
            var index = 0;
            var hasSelectedValuePath = !String.IsNullOrWhiteSpace(SelectedValuePath);
            foreach (var item in ItemsSource) {
                if (item != null && item.Equals(SelectedItem)) {
                    selectedIndex = index;
                    if (hasSelectedValuePath) {
                        var type = item.GetType();
                        var prop = type.GetRuntimeProperty(SelectedValuePath);
                        selectedValue = prop.GetValue(item);
                    }
                    break;
                }
                index++;
            }
        }
        _disableNestedCalls = true;
        SelectedValue = selectedValue;
        SelectedIndex = selectedIndex;
        _disableNestedCalls = false;
    }

    void InternalSelectedValueChanged() {
        if (_disableNestedCalls) {
            return;
        }

        if (String.IsNullOrWhiteSpace(SelectedValuePath)) {
            return;
        }
        var selectedIndex = -1;
        Object selectedItem = null;
        var hasSelectedValuePath = !String.IsNullOrWhiteSpace(SelectedValuePath);
        if (ItemsSource != null && hasSelectedValuePath) {
            var index = 0;
            foreach (var item in ItemsSource) {
                if (item != null) {
                    var type = item.GetType();
                    var prop = type.GetRuntimeProperty(SelectedValuePath);
                    if (prop.GetValue(item) == SelectedValue) {
                        selectedIndex = index;
                        selectedItem = item;
                        break;
                    }
                }

                index++;
            }
        }
        _disableNestedCalls = true;
        SelectedItem = selectedItem;
        SelectedIndex = selectedIndex;
        _disableNestedCalls = false;
    }

    static void OnItemsSourceChanged(BindableObject bindable, Object oldValue, Object newValue) {
        if (Equals(newValue, null) && Equals(oldValue, null)) {
            return;
        }

        var picker = (BindablePicker)bindable;
        picker.Items.Clear();

        if (!Equals(newValue, null)) {
            var hasDisplayMemberPath = !String.IsNullOrWhiteSpace(picker.DisplayMemberPath);

            foreach (var item in (IEnumerable)newValue) {
                if (hasDisplayMemberPath) {
                    var type = item.GetType();
                    var prop = type.GetRuntimeProperty(picker.DisplayMemberPath);
                    picker.Items.Add(prop.GetValue(item).ToString());
                } else {
                    picker.Items.Add(item.ToString());
                }
            }

            picker._disableNestedCalls = true;
            picker.SelectedIndex = -1;
            picker._disableNestedCalls = false;

            if (picker.SelectedItem != null) {
                picker.InternalSelectedItemChanged();
            } else if (hasDisplayMemberPath && picker.SelectedValue != null) {
                picker.InternalSelectedValueChanged();
            }
        } else {
            picker._disableNestedCalls = true;
            picker.SelectedIndex = -1;
            picker.SelectedItem = null;
            picker.SelectedValue = null;
            picker._disableNestedCalls = false;
        }
    }

    void OnSelectedIndexChanged(Object sender, EventArgs e) {
        if (_disableNestedCalls) {
            return;
        }

        if (SelectedIndex < 0 || ItemsSource == null || !ItemsSource.GetEnumerator().MoveNext()) {
            _disableNestedCalls = true;
            if (SelectedIndex != -1) {
                SelectedIndex = -1;
            }
            SelectedItem = null;
            SelectedValue = null;
            _disableNestedCalls = false;
            return;
        }

        _disableNestedCalls = true;

        var index = 0;
        var hasSelectedValuePath = !String.IsNullOrWhiteSpace(SelectedValuePath);
        foreach (var item in ItemsSource) {
            if (index == SelectedIndex) {
                SelectedItem = item;
                if (hasSelectedValuePath) {
                    var type = item.GetType();
                    var prop = type.GetRuntimeProperty(SelectedValuePath);
                    SelectedValue = prop.GetValue(item);
                }

                break;
            }
            index++;
        }

        _disableNestedCalls = false;
    }

    static void OnSelectedItemChanged(BindableObject bindable, Object oldValue, Object newValue) {
        var boundPicker = (BindablePicker)bindable;
        boundPicker.ItemSelected?.Invoke(boundPicker, new SelectedItemChangedEventArgs(newValue));
        boundPicker.InternalSelectedItemChanged();
    }

    static void OnSelectedValueChanged(BindableObject bindable, Object oldValue, Object newValue) {
        var boundPicker = (BindablePicker)bindable;
        boundPicker.InternalSelectedValueChanged();
    }

}

Noi lo abbiamo usato in questo modo all'interno di una cell di una listview. Definizione Liste nel ViewModel.

private ObservableCollection _ListaUnitaDiMisura;
....		
ObservableCollection _ListaUnMis = (await _DataClient.GetGEST_UnitaMisuraAsync(false)).ToObservableCollection();

_ListaUnitaDiMisura.AddRange(from UnMis in _ListaUnMis 
      select UnMis.CodUnMis);

E quindi nella classe ViewCell che definisce la riga della ListView l'abbiamo usata in questo modo.

......
BindablePicker pickUniMin = new BindablePicker();
pickUniMin.SetBinding(BindablePicker.ItemsSourceProperty, new Binding("ListaUnitaDiMisura", BindingMode.TwoWay, null, null, null, _NuovoOrdineViewModel));
pickUniMin.SetBinding(BindablePicker.SelectedItemProperty, new Binding("CodUnMisVend", BindingMode.TwoWay));
......

Linkografia

Xamarin Forms Bindable Picker