How to approach cascading Pickers

knottydevknottydev USMember ✭✭

The only examples I can find involve SelectedIndexChanged event listening and hacking the code-behind. Is there a way to do this via a ViewModel and TwoWay binding? Maybe, the Get method of a ViewModel.Collection property being based on another property set from the view via two way binding? Anyone have an example (aside from relying on SelectedIndexChanged)?

Best Answers

  • knottydevknottydev USMember ✭✭
    edited November 1 Accepted Answer

    @AdamMeaney said:
    SelectedSerialNumber is on the same viewmodel as SelectedInventoryItem. It is not a sub object on the SelectedInventoryItem.

    Got it to work! Thank you VERY much! I see now that I made a VERY obvious mistake! To summarize, it appears that the SelectedItem binding must be of the exact same type as the individual objects in the collection for ItemSource. Again, seems like a well... ya... duh!

    If you look back up to my example, the mistake is there:

            <Picker x:Name="pickerYear"
                    Title="Select a Year"
                    ItemsSource="{Binding years}"
                    ItemDisplayBinding="{Binding YearID}"
                    SelectedItem="{Binding myYear, Mode=TwoWay}"/>
    

    My ViewModel looks like this:

    public class viewModelOptions : ViewModelBase
    {
        public ModelYears years { get; set; }
        public string myYear { get; set; }
        public viewModelOptions ()
        {
            years = new ModelYears();
        }
    }
    

    So, myYear is the problem! My ItemSource is years (ModelYears) and is ObservableCollection. ModelYear is a poco with YearID as a string property. I wrongly thought/assumed that I could somehow use the SelectedItem to force it to a string / property of ModelYear. But no... the ViewModel must look like this or it silently fails to bind:

    public class viewModelOptions : ViewModelBase
    {
        public ModelYears years { get; set; }
        public ModelYear myYear { get; set; }
        public viewModelOptions ()
        {
            years = new ModelYears();
        }
    }
    

    ...because SelectedItem has to be of the same type as the individual objects in the ItemSource. ie. ModelYear objects.

Answers

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    Exists a SelectedItem property you can use in your MVVM.

  • knottydevknottydev USMember ✭✭
    edited October 30

    @AlessandroCaliaro said:
    Exists a SelectedItem property you can use in your MVVM.

    So, I have a picker which looks like this for example:

                <Picker x:Name="pickerYear"
                        Title="Select a Year"
                        ItemsSource="{Binding years}"
                        ItemDisplayBinding="{Binding YearID}"
                        SelectedItem="{Binding myYear, Mode=TwoWay}"/>
    

    Here's my viewmodel:
    public class viewModelOptions : ViewModelBase
    {
    public ModelYears years { get; set; }
    public string myYear { get; set; }
    public viewModelOptions ()
    {
    years = new ModelYears();
    }
    }

    Originally, I planned on having another collection in my ViewModel where the Get is based on the myYear. I think this is what you're suggesting? My issue is, I cannot seem to get the SelectedItem to ever hit the set method of myYear. Just assumed I was approaching this all wrong?

  • knottydevknottydev USMember ✭✭
    edited October 30

    @AlessandroCaliaro said:
    Exists a SelectedItem property you can use in your MVVM.

    Ok... so I think I already attempted the approach you're suggesting. I have a picker that looks like this:

                <Picker x:Name="pickerYear"
                        Title="Select a Year"
                        ItemsSource="{Binding years}"
                        ItemDisplayBinding="{Binding YearID}"
                        SelectedItem="{Binding myYear, Mode=TwoWay}"/>
    

    My ViewModel looks like this:

    public class viewModelOptions : ViewModelBase
    {
        public ModelYears years { get; set; }
        public string myYear { get; set; }
    public viewModelOptions ()
        {
            years = new ModelYears();
        }
    }
    

    I had planned on adding an additional collection to the ViewModel who's get method relied on myYear being != null. Problem is, I can't seem to get myYear to be updated. The getter gets called on initialization, the setter never gets called even though I'm selecting a year from the GUI. Does this make sense? I assumed i'm taking the wrong approach.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    I don't use Picker very often so I can be wrong.
    I think ItemSource should be an ObservableCollection and myYear should implement INotifyPropertyChanged (maybe with Fody to have a clean code) and should be of type ModelYears... https://developer.xamarin.com/api/type/Xamarin.Forms.Picker/

  • knottydevknottydev USMember ✭✭

    @AlessandroCaliaro said:
    I don't use Picker very often so I can be wrong.
    I think ItemSource should be an ObservableCollection and myYear should implement INotifyPropertyChanged (maybe with Fody to have a clean code) and should be of type ModelYears...

    I'm pretty new to MVVM. Thanks for your patience!

    So, in this case ModelYears is an ObservableCollection. myYear is just another property I stuck on the ViewModel to test with. ViewModelBase implements INotifyPropertyChanged as follows:

    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    That event however never seems to fire...

    Maybe some general direction would help me? Like, I'm assuming my View should use a context of a viewModel class that OnPropertyChanged which somehow magically gets fired (though in my case, never seems to). And, I would assume I setup my cascading properties in the viewModel based on when this would fire?

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    I don't know... can you post a Repo on GitHub?

  • knottydevknottydev USMember ✭✭
    edited October 31

    @AlessandroCaliaro said:
    I don't know... can you post a Repo on GitHub?

    Can't due to intellectual property rights junk. I think I've narrowed down the issue however. A little more experimenting and here's my picker:

            <Picker x:Name="pickerYear"
                    Title="Select a Year"
                    ItemsSource="{Binding years}" //Observable collection property property of ViewModel
                    ItemDisplayBinding="{Binding YearID}" //YearID is a property of the collection of years object
                    SelectedItem="{Binding pickedYear, Mode=TwoWay}"/> //pickedYear is a completely different property of the viewModel
    

    Is it potentially plausible that I cannot bind the ItemDisplayBinding to a property of a ObservableCollection (where the entity.someproperty is what i'm binding to) and bind the SelectedItem to a different property of the viewModel? I think that is why this isn't working. But, not sure what other pattern (aside from manually setting some selection indicator property on the viewmodel) would work.

  • knottydevknottydev USMember ✭✭
    edited October 31

    Also... I changed my viewModel so that pickedYear is an integer just like YearID. I think to summarize, I am trying to make Picker read/write and somehow update the ViewModel when something else is picked. I think the framework simply doesn't support this.

    To answer my question: There is no valid framework solution to support cascading pickers that doesn't involve hacking the code-behind and utilizing the changed events. Bummer...

  • pandrosopandroso MXMember

    i have a problem, i have two picker: Picker1 and Picker2
    i get the data from a REST service in a JSON format

    on the app loads, the Picker1 es automatically filled from the REST service data, the Picker2 is Disabled and empty
    When i select one item from Picker1, i get the data from the REST service and fill the Picker2

    If i select other element from Picker1, the picker2 need to be cleaned and filled again with the new data, but when i select other element from Picker1, the app crash :(

  • AdamMeaneyAdamMeaney USMember ✭✭✭✭

    I mean, in our app we have a lot of pickers that are "Select this warehouse", then "Select item from this warehouse", then "Select Data from that item".

    If that is the kind of cascading you are looking for, it certainly is possible to do it with just Xaml bindings and ViewModel properties.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    @pandroso said:
    i have a problem, i have two picker: Picker1 and Picker2
    i get the data from a REST service in a JSON format

    on the app loads, the Picker1 es automatically filled from the REST service data, the Picker2 is Disabled and empty
    When i select one item from Picker1, i get the data from the REST service and fill the Picker2

    If i select other element from Picker1, the picker2 need to be cleaned and filled again with the new data, but when i select other element from Picker1, the app crash :(

    you should post your exception

  • knottydevknottydev USMember ✭✭

    @AdamMeaney said:
    I mean, in our app we have a lot of pickers that are "Select this warehouse", then "Select item from this warehouse", then "Select Data from that item".

    If that is the kind of cascading you are looking for, it certainly is possible to do it with just Xaml bindings and ViewModel properties.

    Without manually setting something in Picker_SelectedIndexChanged() event? Maybe could you share an example of your Picker.SelectedItem or SelectedIndex binding? That might help me... I cannot seem to get that to update a ViewModel (2-Way) - which seems like the magic piece I'm missing.

  • AdamMeaneyAdamMeaney USMember ✭✭✭✭
    <Picker
        ItemsSource="{Binding SelectedInventoryItem.SerialNumbers}"
        Title="Serial Number"
        SelectedItem="{Binding SelectedSerialNumber, Mode=TwoWay}" />
    

    I don't know about the actual Picker, we have our own class, but that's how I do it.

  • knottydevknottydev USMember ✭✭

    @AdamMeaney said:

    <Picker
        ItemsSource="{Binding SelectedInventoryItem.SerialNumbers}"
        Title="Serial Number"
        SelectedItem="{Binding SelectedSerialNumber, Mode=TwoWay}" />
    

    I don't know about the actual Picker, we have our own class, but that's how I do it.

    Is SelectedSerialNumber a property of SelectedInventoryItem? This seems exactly like my approach and yet I cannot get the setter of my SelectedItem property (on the ViewModel) to be called.

  • knottydevknottydev USMember ✭✭
    edited November 1 Accepted Answer

    @AdamMeaney said:
    SelectedSerialNumber is on the same viewmodel as SelectedInventoryItem. It is not a sub object on the SelectedInventoryItem.

    Got it to work! Thank you VERY much! I see now that I made a VERY obvious mistake! To summarize, it appears that the SelectedItem binding must be of the exact same type as the individual objects in the collection for ItemSource. Again, seems like a well... ya... duh!

    If you look back up to my example, the mistake is there:

            <Picker x:Name="pickerYear"
                    Title="Select a Year"
                    ItemsSource="{Binding years}"
                    ItemDisplayBinding="{Binding YearID}"
                    SelectedItem="{Binding myYear, Mode=TwoWay}"/>
    

    My ViewModel looks like this:

    public class viewModelOptions : ViewModelBase
    {
        public ModelYears years { get; set; }
        public string myYear { get; set; }
        public viewModelOptions ()
        {
            years = new ModelYears();
        }
    }
    

    So, myYear is the problem! My ItemSource is years (ModelYears) and is ObservableCollection. ModelYear is a poco with YearID as a string property. I wrongly thought/assumed that I could somehow use the SelectedItem to force it to a string / property of ModelYear. But no... the ViewModel must look like this or it silently fails to bind:

    public class viewModelOptions : ViewModelBase
    {
        public ModelYears years { get; set; }
        public ModelYear myYear { get; set; }
        public viewModelOptions ()
        {
            years = new ModelYears();
        }
    }
    

    ...because SelectedItem has to be of the same type as the individual objects in the ItemSource. ie. ModelYear objects.

  • AdamMeaneyAdamMeaney USMember ✭✭✭✭

    Glad I could help. Feel free to mark either your or my answers (or both) so the question will be finished off.

  • knottydevknottydev USMember ✭✭

    @AdamMeaney said:
    Glad I could help. Feel free to mark either your or my answers (or both) so the question will be finished off.

    Done. And thanks again for helping out a newbie! :)

    I used to be a veteran Silverlight Dev. But, all this binding stuff is a little rusty. lol

Sign In or Register to comment.