1
votes

I have created a custom picker control PCPicker (with unobtrusive validations in mind for the future) with xaml like so:

<Label x:Name="ControlLabel"
               Style="{DynamicResource InputLabel}"
               Text="{Binding LabelText}"/>
        <Picker x:Name="ControlPicker"
                ItemsSource="{Binding Src}"
                Title="{Binding PlaceHolderText}"
                Style="{DynamicResource PCPickerStyle}"
                SelectedIndex="{Binding Index,Mode=TwoWay}"
                SelectedItem="{Binding SelectedOption,Mode=OneWayToSource}"
                />

The code behind is like so:

[XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class PCPicker : ContentView
    {
        public static readonly BindableProperty LabelTextProperty =
            BindableProperty.Create(
                propertyName: nameof(LabelText),
                returnType: typeof(string),
                declaringType: typeof(PCPicker),
                defaultValue: "",
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: LabelTextPropertyChanged);

        public string LabelText
        {
            get { return GetValue(LabelTextProperty).ToString(); }
            set { SetValue(LabelTextProperty, value); }
        }

        private static void LabelTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (PCPicker)bindable;
            control.ControlLabel.Text = newValue.ToString();
        }

        public static readonly BindableProperty PlaceHolderTextProperty =
            BindableProperty.Create(
                propertyName: nameof(PlaceHolderText),
                returnType: typeof(string),
                declaringType: typeof(PCPicker),
                defaultValue: "",
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: PlaceHolderTextPropertyChanged);

        public string PlaceHolderText
        {
            get { return GetValue(PlaceHolderTextProperty).ToString(); }
            set { SetValue(PlaceHolderTextProperty, value); }
        }

        private static void PlaceHolderTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (PCPicker)bindable;
            control.ControlPicker.Title = newValue.ToString();
        }

        public static readonly BindableProperty SelectedOptionProperty =
            BindableProperty.Create(
                propertyName: nameof(SelectedOption),
                returnType: typeof(object),
                declaringType: typeof(PCPicker),
                defaultValue: null,
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: SelectedOptionChanged);

        public object SelectedOption
        {
            get { return GetValue(SelectedOptionProperty); }
            set { SetValue(SelectedOptionProperty, value); }
        }

        private static void SelectedOptionChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (PCPicker)bindable;
            control.ControlPicker.SelectedItem = newValue;
        }


        public static readonly BindableProperty SrcProperty =
            BindableProperty.Create(
                propertyName: nameof(Src),
                returnType: typeof(IList),
                declaringType: typeof(PCPicker),
                defaultValue: null,
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: PickerSourceChanged);

        public IList Src
        {
            get { return (IList)GetValue(SrcProperty); }
            set { SetValue(SrcProperty, value); }
        }

        private static void PickerSourceChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (PCPicker)bindable;
            control.ControlPicker.ItemsSource = (IList)newValue;
        }

        public static readonly BindableProperty IndexProperty =
            BindableProperty.Create(
                propertyName: nameof(Index),
                returnType: typeof(int),
                declaringType: typeof(PCPicker),
                defaultValue: -1,
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: SelectedIndexChanged);


        public int Index
        {
            get { return (int)GetValue(IndexProperty); }
            set { SetValue(IndexProperty, value); }
        }

        private static void SelectedIndexChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (PCPicker)bindable;
            control.ControlPicker.SelectedIndex = (int)newValue;
        }

        public BindingBase DisplayMember
        {
            get { return ControlPicker.ItemDisplayBinding; }
            set { ControlPicker.ItemDisplayBinding = value; }
        }

        public PCPicker()
        {
            InitializeComponent();
        }

    }

Usage:

<cc:PCPicker BindingContext="{x:Binding M}" 
                             LabelText="* Location" 
                             PlaceHolderText="Select Cat"
                             Src="{Binding Cats}"
                             SelectedOption="{Binding SelectedCat, Mode=TwoWay}"
                             Index="{Binding Position, Mode=TwoWay}"
                             DisplayMember="{Binding Name}"/>

With the above I can get the Display Binding and the ItemSource binding to work. However, the SelectedItem value binding is always null. I put in the SelectedIndex binding as a test, but that is also always 0. I have made sure that the binding Mode is TwoWay, but I still always get a null SelectedItem. Any help will be much appreciated.

1
Provide the code in your viewmodel .Lucas Zhang

1 Answers

0
votes

Did you implement INotifyPropertyChanged in your ViewModel?

ViewModels generally implement the INotifyPropertyChanged interface, which means that the class fires a PropertyChanged event whenever one of its properties changes. The data binding mechanism in Xamarin.Forms attaches a handler to this PropertyChanged event so it can be notified when a property changes and keep the target updated with the new value.

So you can improve your videmodel as following

public class MyViewModel: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    ObservableCollection<Cat> Cats { get; set; }

    private Cat selectedCat;
    public Cat SelectedCat
    {
        get
        {
            return selectedCat;
        }
        set
        {
            if (selectedCat != value)
            {
                selectedCat = value;
                NotifyPropertyChanged();

                // do something you want 

            }
        }
    }

    private int position=0;
    public int Position
    {
        get
        {
            return position;
        }
        set
        {
            if (position != value)
            {
                position = value;
                NotifyPropertyChanged();
            }
        }
    }

    public MyViewModel()
    {
        //... 


    }

}