简体   繁体   中英

Xamarin Android Picker binding issue to ObservableCollection

I have a simple Xamarin.Forms project with a Picker .

My view is bound to a view model that inherits from INotifyPropertyChanged .

I define an ObservableCollection like:

public ObservableCollection<User> UserList { get; set; }

which gets filled by an HTTP client Get method:

UserList = JsonConvert.DeserializeObject<ObservableCollection<User>>(s);

I have verified that UserList gets filled with the expected data.

Here is my Picker:

<Picker Title="User"
        Grid.Row="0"
        Grid.Column="0"
        ItemsSource="{Binding UserList}"
        ItemDisplayBinding="{Binding UserName}"
        SelectedItem="{Binding UserSelectedItem}"/>

This is my SelectedItem binding property:

public User UserSelectedItem { get; set; }

I dont have UserSelectedItem using OnPropertyChanged .

The problem: I don't get any values in my Picker even though I have verified that UserList does have the expected data in it.

EDIT

I modified my ObservableCollection to use OnPropertyChanged:

private ObservableCollection<User> _userList;
public ObservableCollection<User> UserList
{
    get
    {
        return _userList;
    }
    set
    {
        _userList = value;
        OnPropertyChanged(nameof(UserList));
    }
}

but the data is still not making it to the picker...

EDIT 2 I still cant figure this out. This is my XAML code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="PTSX.Views.JoinCrew">
    <ContentPage.Content>
        <StackLayout>
            <Label Text="Join a Crew" 
                HorizontalOptions="CenterAndExpand" />

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="AUTO"/>
                    <RowDefinition Height="200"/>
                    <RowDefinition Height="200"/>
                    <RowDefinition Height="100"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
            </Grid>
            
            
            <Picker x:Name ="UserPicker" 
                    Title="User"
                    Grid.Row="0"
                    Grid.Column="0"
                    ItemsSource="{Binding UserList}"
                    ItemDisplayBinding="{Binding UserName}"
                    SelectedItem="{Binding UserSelectedItem}"/>
            
            <Picker x:Name="CrewPicker" 
                    Title="Crew"
                    Grid.Row="1"
                    Grid.Column="0"
                    ItemsSource="{Binding CrewList}"
                    ItemDisplayBinding="{Binding CrewName}"
                    SelectedItem="{Binding CrewSelectedItem}"/>

            <Button x:Name="buttonJoinCrew"
                    Text="Join"
                    Grid.Row="2"
                    Grid.Column="0"
                    Command="{Binding JoinCrewCommand}"/>

            <Button x:Name="buttonCreateCrew"
                    Text="Add Crew"
                    Grid.Row="3"
                    Grid.Column="0"
                    Command="{Binding AddCrewCommand}"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

This is my XAML code behind:

public partial class JoinCrew : ContentPage
    {
        private JoinCrewViewModel viewModel { get; set; }
        public JoinCrew()
        {
            InitializeComponent();
            viewModel = new JoinCrewViewModel();
            BindingContext = viewModel;
        }
    }

This is my view model:

public class JoinCrewViewModel : INotifyPropertyChanged
    {
        #region Fields
        private string _crewName;
        private string _crewId;
        private string _userName;
        private string _userId;
        private IList<User> _userList;
        private IList<DailyCrew> _crewList;
        private User _userSelectedItem;
        private DailyCrew _crewSelectedItem;
        #endregion

        #region Properties
        public string CrewName
        {
            get
            {
                return _crewName;
            }
            set
            {
                _crewName = value;
                OnPropertyChanged();
            }
        }

        public string CrewId
        {
            get
            {
                return _crewId;
            }
            set
            {
                _crewId = value;
                OnPropertyChanged();
            }
        }

        public string UserName
        {
            get
            {
                return _userName;
            }
            set
            {
                _userName = value;
                OnPropertyChanged();
            }
        }

        public string UserId
        {
            get
            {
                return _userId;
            }
            set
            {
                _userId = value;
                OnPropertyChanged();
            }
        }

        public DailyCrew CrewSelectedItem
        {
            get => _crewSelectedItem;
            set
            {
                _crewSelectedItem = value;
                OnPropertyChanged();
            }
        }
        public User UserSelectedItem
        {
            get => _userSelectedItem;
            set
            {
                _userSelectedItem = value;
                OnPropertyChanged();
            }
        }

        public IList<DailyCrew> CrewList
        {
            get
            {
                return _crewList;
            }
            set
            {
                _crewList = value;
                OnPropertyChanged();
            }
        }
        public IList<User> UserList
        {
            get
            {
                return _userList;
            }
            set
            {
                _userList = value;
                OnPropertyChanged();
            }
        }

        #endregion

        #region Events
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var propertyChanged = PropertyChanged;
            propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region Delegates
        public Command JoinCrewCommand { get; }
        public Command AddCrewCommand { get; }
        #endregion

        #region Contructor

        public JoinCrewViewModel()
        {
            JoinCrewCommand = new Command(JoinCrewAction);
            AddCrewCommand = new Command(AddCrewAction);
            async Task FillUsersAsync();
        }

        private bool CanExecuteCommand(object commandParameter)
        {
            return true;
        }

        #endregion

        #region Commands
        private async void JoinCrewAction()
        {
            await JoinCrew();
        }

        #endregion

        #region Private Methods
        

        private async Task<IList<User>> FillUsersAsync()
        {
            IList<User> result = null;
            using (var client = new HttpClient())
            {
                HttpResponseMessage response = await client.GetAsync($"https://xxx/api/mobile/getuserslist");
                if (response.IsSuccessStatusCode)
                {
                    string s = await response.Content.ReadAsStringAsync();
                    result = JsonConvert.DeserializeObject<IList<User>>(s);
                }
            }
            return result;
        }
}

I can confirm like before that I have data being returned to my IList via the Http Client

I dont understand why the Picker is not getting filled. This seems like it should be pretty easy to do.

I have written a simple sample that is working perfectly on my side.

By Tapping the button the property that is bound to the Picker's ItemsSource gets loaded, and due to the OnPropertyChanged() call, the picker is notified that the collection changed and it loads the values correctly.

Please take a look at the sample and if you still have problems, share further details...

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace PickerOC
{
    public partial class MainPage : ContentPage
    {

        MainPageViewModel viewModel { get; set; }

        public MainPage()
        {
            InitializeComponent();
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();

            viewModel = new MainPageViewModel();
            BindingContext = viewModel;
        }

        private void Button_Clicked(object sender, EventArgs e)
        {
            viewModel.PickerData = new System.Collections.ObjectModel.ObservableCollection<UserX>()
            {
                new UserX("John L."), new UserX("Paul M."), new UserX("George H."), new UserX("Ringo S.")
            };
        }
    }
}

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="PickerOC.MainPage">

    <StackLayout>
        <Picker ItemsSource="{Binding PickerData}"
                ItemDisplayBinding="{Binding UserName}"/>
        <Button Text="Load Picker Data!"
                Clicked="Button_Clicked"/>
    </StackLayout>

</ContentPage>

ViewModel

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace PickerOC
{
    class MainPageViewModel : INotifyPropertyChanged
    {

        private ObservableCollection<UserX> _PickerData;
        public ObservableCollection<UserX> PickerData
        { 
            get => _PickerData;
            set
            {
                _PickerData = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string name = "")
        {
            var propertyChanged = PropertyChanged;

            propertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class UserX
    {
        public UserX(String name)
        {
            UserName = name;
        }
        public String UserName { get; set; }
    }
}

I used your posted code and made it work on my side.

Note, i mocked the FillUsersAsync with a simple asynchronous function that awaits five seconds and then returns a collection of Users.

XAML

<ContentPage.Content>
        <StackLayout>
            <Label Text="Join a Crew" 
                HorizontalOptions="CenterAndExpand" />

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="AUTO"/>
                    <RowDefinition Height="200"/>
                    <RowDefinition Height="200"/>
                    <RowDefinition Height="100"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
            </Grid>


            <Picker x:Name ="UserPicker" 
                    Title="User"
                    Grid.Row="0"
                    Grid.Column="0"
                    ItemsSource="{Binding UserList}"
                    ItemDisplayBinding="{Binding UserName}"
                    SelectedItem="{Binding UserSelectedItem}"/>

            <Picker x:Name="CrewPicker" 
                    Title="Crew"
                    Grid.Row="1"
                    Grid.Column="0"
                    ItemsSource="{Binding CrewList}"
                    ItemDisplayBinding="{Binding CrewName}"
                    SelectedItem="{Binding CrewSelectedItem}"/>

            <Button x:Name="buttonJoinCrew"
                    Text="Join"
                    Grid.Row="2"
                    Grid.Column="0"
                    Command="{Binding JoinCrewCommand}"/>

            <Button x:Name="buttonCreateCrew"
                    Text="Add Crew"
                    Grid.Row="3"
                    Grid.Column="0"
                    Command="{Binding AddCrewCommand}"/>
        </StackLayout>
    </ContentPage.Content>

Code Behind

using Xamarin.Forms;

namespace PickerOC
{
    public partial class MainPage : ContentPage
    {

        MainPageViewModel viewModel { get; set; }

        public MainPage()
        {
            InitializeComponent();
            viewModel = new MainPageViewModel();
            BindingContext = viewModel;
        }

        
    }
}

View Model

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace PickerOC
{
    class MainPageViewModel : INotifyPropertyChanged
    {
        #region Fields
        private string _crewName;
        private string _crewId;
        private string _userName;
        private string _userId;
        private IList<User> _userList;
        private IList<DailyCrew> _crewList;
        private User _userSelectedItem;
        private DailyCrew _crewSelectedItem;
        #endregion

        #region Properties
        public string CrewName
        {
            get
            {
                return _crewName;
            }
            set
            {
                _crewName = value;
                OnPropertyChanged();
            }
        }

        public string CrewId
        {
            get
            {
                return _crewId;
            }
            set
            {
                _crewId = value;
                OnPropertyChanged();
            }
        }

        public string UserName
        {
            get
            {
                return _userName;
            }
            set
            {
                _userName = value;
                OnPropertyChanged();
            }
        }

        public string UserId
        {
            get
            {
                return _userId;
            }
            set
            {
                _userId = value;
                OnPropertyChanged();
            }
        }

        public DailyCrew CrewSelectedItem
        {
            get => _crewSelectedItem;
            set
            {
                _crewSelectedItem = value;
                OnPropertyChanged();
            }
        }
        public User UserSelectedItem
        {
            get => _userSelectedItem;
            set
            {
                _userSelectedItem = value;
                OnPropertyChanged();
            }
        }

        public IList<DailyCrew> CrewList
        {
            get
            {
                return _crewList;
            }
            set
            {
                _crewList = value;
                OnPropertyChanged();
            }
        }
        public IList<User> UserList
        {
            get
            {
                return _userList;
            }
            set
            {
                _userList = value;
                OnPropertyChanged();
            }
        }

        #endregion

        #region Events
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var propertyChanged = PropertyChanged;
            propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region Delegates
        public Command JoinCrewCommand { get; }
        public Command AddCrewCommand { get; }
        #endregion

        #region Contructor

        public MainPageViewModel()
        {
            JoinCrewCommand = new Command(JoinCrewAction);
            AddCrewCommand = new Command(AddCrewAction);
            Task.Run(async () => UserList = await FillUsersAsync());
            //async Task FillUsersAsync();
        }

        private bool CanExecuteCommand(object commandParameter)
        {
            return true;
        }

        #endregion

        #region Commands
        private async void AddCrewAction()
        {
            await Task.Delay(1000);
        }

        private async void JoinCrewAction()
        {
            await Task.Delay(1000);
        }
        #endregion

        #region Private Methods


        private async Task<IList<User>> FillUsersAsync()
        {
            IList<User> result = null;
            //using (var client = new HttpClient())
            //{
            //    HttpResponseMessage response = await client.GetAsync($"https://xxx/api/mobile/getuserslist");
            //    if (response.IsSuccessStatusCode)
            //    {
            //        string s = await response.Content.ReadAsStringAsync();
            //        result = JsonConvert.DeserializeObject<IList<User>>(s);
            //    }
            //}

            await Task.Delay(5000);

            result = new List<User>()
            {
                new User() {UserName = "John L."},
                new User() {UserName = "Paul M."},
            };

            return result;
        }

        #endregion
    }

    public class User
    {

        public string UserID { get; set; }
        public string UserName { get; set; }

    }

    public class DailyCrew
    {
        public string CrewId { get; set; }
        public string CrewName { get; set; }
    }
}

I used your posted code and made it work on my side for user picker.

ViewModel.cs

public class JoinCrewViewModel : INotifyPropertyChanged
    {
        #region Fields
        private ObservableCollection<User> _userList;
        private User _userSelectedItem;
        #endregion

        #region Properties

        public User UserSelectedItem
        {
            get => _userSelectedItem;
            set
            {
                _userSelectedItem = value;
                OnPropertyChanged();
            }
        }

        
        public ObservableCollection<User> UserList
        {
            get
            {
                return _userList;
            }
            set
            {
                _userList = value;
                OnPropertyChanged();
            }
        }

        #endregion

        #region Events
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var propertyChanged = PropertyChanged;
            propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        

        #region Contructor

        public JoinCrewViewModel()
        {
           FillUsersAsync();
        }

        #endregion


        private async Task<ObservableCollection<User>> FillUsersAsync()
        {

            _userList = new ObservableCollection<User>() { 
                  new User()
                  {
                      UserName = "test",
                      UserId = "1"
                  },
                  new User()
                  {
                      UserName = "test1",
                      UserId = "2"
                  },
                  new User()
                  {
                      UserName = "test2",
                      UserId = "3"
                  },
                  new User()
                  {
                      UserName = "test3",
                      UserId = "4"
                  },
                  new User()
                  {
                      UserName = "test3",
                      UserId = "5"
                  }
            };

            _userSelectedItem = _userList[2];

            return _userList;
        }
    }

    public class User
    {
        public string UserName { get; set; }
        public string UserId { get; set; }
    }

Home.xaml

<StackLayout>
        <Label Text="Join a Crew" 
                HorizontalOptions="CenterAndExpand" />

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="AUTO"/>
                <RowDefinition Height="200"/>
                <RowDefinition Height="200"/>
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
        </Grid>


        <Picker x:Name ="UserPicker" 
                    Title="User"
                    Grid.Row="0"
                    Grid.Column="0"
                    ItemsSource="{Binding UserList}"
                    ItemDisplayBinding="{Binding UserName}"
                    SelectedItem="{Binding UserSelectedItem}"/>

    </StackLayout>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM