Trying to get the value of a picker's selectedItem within a listview (MVVM)

Need some direction/help here guys. Similar to a basic contacts app - I'm building a customer entry screen and need a drop down for contact types (phone, email, etc). I figured out how to populate the picker(s) by explicitly defining the path and source but I can't seem to get SelectedItem to do anything. My expectation is set the default value of the picker with whats in the record and also be able to change that value.

<ListView x:Name="ContactListView" ItemsSource="{Binding Customer.ContactList}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout>
<Picker
ItemsSource="{Binding Path=BindingContext.ContactTypes, Source={x:Reference ContactListView} }"
ItemDisplayBinding="{Binding TypeName}"
SelectedItem="{Binding TypeName, Mode=TwoWay}"
/>
<Entry Text="{Binding Value}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

The ViewModel attached to this page has an Customer object which has a nested contact list in it (defined at the list view). Each contact has a picker with multiple types and a value field so they can type in the corresponding data.

Answers

  • Gigex42Gigex42 USMember ✭✭✭✭

    Could you show us a little bit of your code maybe?

    I did the exact same thing in my app and it worked fine.

    When picking an item from the picker does the setter method get called?

  • LandLuLandLu Member, Xamarin Team Xamurai

    @chrisjmccrum I notice you set the Picker's ItemsSource to your ContentPage's BindingContext.ContactTypes not each Contact model. It seems your page's binding context has a ContactTypes list property. You can use the same way to bind your SelectedItem in your view model:

    <Picker ItemsSource="{Binding Path=BindingContext.ContactTypes, Source={x:Reference ContactListView} }"
            ItemDisplayBinding="{Binding TypeName}"
            SelectedItem="{Binding Path=BindingContext.TypeName, Source={x:Reference ContactListView} }" />
    

    Define a property in your page's binding context:

    ContactType typeName;
    public ContactType TypeName
    {
        set
        {
            typeName = value;
            //Do not forget to implement the INotifyPropertyChanged interface
            OnPropertyChanged("TypeName");
        }
        get
        {
            return typeName;
        }
    }
    

    But I really recommend you to configure this in your single Contact model, because your picker's default binding context is each model of your Customer.ContactList. If you bind each picker's SelectedItem to one property of your view model, when one picker changed the other changed too. So do your picker's ItemsSource.

  • Gigex42Gigex42 USMember ✭✭✭✭

    I think what @chrisjmccrum did was only loading the list of contacttypes one time from the main viewmodel.
    Now every Item in the listview has a picker which takes that list as the source.

    Now every item also has a property of contacttype. When the user changes the picker of one item the itemproperty contacttype gets changed.

    Is your property in your itemmodel TypeName a string?
    Try changing this to a new class called Type and this one can have a property TypeName as string.
    I think I had the same problem and there seemed to be a problem with binding directly to a string.

  • chrisjmccrumchrisjmccrum Member ✭✭
    edited August 2018

    @LandLu said:
    @chrisjmccrum I notice you set the Picker's ItemsSource to your ContentPage's BindingContext.ContactTypes not each Contact model. It seems your page's binding context has a ContactTypes list property. You can use the same way to bind your SelectedItem in your view model:

    <Picker ItemsSource="{Binding Path=BindingContext.ContactTypes, Source={x:Reference ContactListView} }"
            ItemDisplayBinding="{Binding TypeName}"
            SelectedItem="{Binding Path=BindingContext.TypeName, Source={x:Reference ContactListView} }" />
    

    Define a property in your page's binding context:

    ContactType typeName;
    public ContactType TypeName
    {
        set
        {
            typeName = value;
            //Do not forget to implement the INotifyPropertyChanged interface
            OnPropertyChanged("TypeName");
        }
        get
        {
            return typeName;
        }
    }
    

    But I really recommend you to configure this in your single Contact model, because your picker's default binding context is each model of your Customer.ContactList. If you bind each picker's SelectedItem to one property of your view model, when one picker changed the other changed too. So do your picker's ItemsSource.

    This what I'm trying to achieve but dynamically. Each customer has an indefinite amount of Contacts (List) with a Contact TypeName Property and Contact Value Property. If it was only one Contact I would define a single property in the page's binding context as you suggested. Unless you think I should still do this still even tho I have List and somehow I can pass the Id of the Contact when I'm updating when selecting a ContactType. Not sure if this is the proper way of doing it tho or even how I would do that.

    public class Customer
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string AddressLine { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
        public List<Contact> ContactList { get; set; }
    }
    
    public class ContactType
    {
        public string TypeName { get; set; }
        public int SortOrderId { get; set; }
    }
    
    public class Contact : ContactType
    {
        public int Id { get; set; }
        public string Value { get; set; }
    }
    

    above is a basic version of the Customer Class that I'm using on the page with its relationship to ContactTypes. As @Gigex42 said - when the Page ViewModel loads, I store a list of possible ContactTypes (one time) that the user can choose from and it's shared in each picker as the item source. The list of possible ContactTypes from the database are stored in a local variable - here's the static representation of it:

    public IList<ContactType> ContactTypes { get; } = new List<ContactType>
    {
        new ContactType(){ TypeName = "Home Phone", SortOrderId = 1  },
        new ContactType(){ TypeName = "Cell Phone", SortOrderId = 2 },
        new ContactType(){ TypeName = "Work Phone", SortOrderId = 3 },
        new ContactType(){ TypeName = "Email", SortOrderId = 4 },
        new ContactType(){ TypeName = "Fax", SortOrderId = 5 }
    };
    

    I need the picker to show the options from ContactTypes, and when an Item is Selected, it updates the TypeName property of the current record iteration in ContactList.

  • Gigex42Gigex42 USMember ✭✭✭✭

    Now that I have read your code again

    SelectedItem="{Binding TypeName, Mode=TwoWay}"

    Cant you just write {Binding .,Mode=TwoWay} instead of TypeName?
    You actualy bind your picker with a list of ContactType.
    Now you also have to "return" this via the binding.

  • chrisjmccrumchrisjmccrum Member ✭✭
    edited August 2018

    @Gigex42 said:
    Now that I have read your code again

    SelectedItem="{Binding TypeName, Mode=TwoWay}"

    Cant you just write {Binding .,Mode=TwoWay} instead of TypeName?
    You actualy bind your picker with a list of ContactType.
    Now you also have to "return" this via the binding.

    I can do that but then it just becomes a standalone picker not connected to the Customer Data. The point in the picker is to choose a ContactType for each Contact which also has the TypeName property. How do I set the default TypeName value of each picker in the list to the corresponding Contact's TypeName property? Also when a different TypeName Property is selected (picked) how do I update that Contact's TypeName Property?

Sign In or Register to comment.