Xamarin Android 到 ObservableCollection 的选择器绑定问题

Xamarin Android Picker binding issue to ObservableCollection

我有一个简单的 Xamarin.Forms 项目 Picker.

我的视图绑定到继承自 INotifyPropertyChanged 的视图模型。

我定义了一个 ObservableCollection 比如:

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

HTTP 客户端 Get 方法填充:

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

我已经验证 UserList 填充了预期的数据。

这是我的选择器:

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

这是我的 SelectedItem 绑定 属性:

public User UserSelectedItem { get; set; }

我没有 UserSelectedItem 使用 OnPropertyChanged

问题: 我没有在我的 Picker 中得到任何值,即使我已经验证 UserList 确实有预期的数据。

编辑

我修改了 ObservableCollection 以使用 OnPropertyChanged:

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

但数据仍未进入选择器...

编辑 2 我还是想不通。 这是我的 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="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>

这是我的 XAML 背后的代码:

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

这是我的视图模型:

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;
        }
}

我可以像以前一样确认我有数据通过 Http 客户端返回到我的 IList

我不明白为什么选择器没有被填满。这看起来应该很容易做到。

我写了一个简单的示例,在我这边运行得很好。

通过点击按钮,绑定到 Picker 的 ItemsSource 的 属性 被加载,并且由于 OnPropertyChanged() 调用,选择器收到集合已更改的通知并正确加载值。

请查看示例,如果仍有问题,请分享更多详细信息...

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>

视图模型

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; }
    }
}

我使用了您发布的代码并使它在我这边用于用户选择器。

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>

我使用了您发布的代码并使它在我这边工作。

请注意,我用一个简单的异步函数模拟了 FillUsersAsync,该函数等待五秒钟,然后 returns 一组用户。

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>

代码隐藏

using Xamarin.Forms;

namespace PickerOC
{
    public partial class MainPage : ContentPage
    {

        MainPageViewModel viewModel { get; set; }

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

        
    }
}

查看模型

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; }
    }
}