Xamarin中ListView子项绑定自定义列表属性问题求助
问题说明
ViewModel 内定义了 ObservableCollection<Player> 类型的 Players 属性,将 ListView 的 ItemsSource 绑定该属性后,在 ItemTemplate 中使用 {Binding Players.Name} 会报错:*"ObservableCollection1类型不存在Name属性"*。但移除 ItemTemplate,将 ItemsSource绑定为{Binding Players[0].Name}` 时,ListView 却能显示所有 Player 的 Name,需要排查问题原因。
相关代码
ViewModel 代码
public class MainPageViewModel : INotifyPropertyChanged { public MainPageViewModel() { } private ObservableCollection<Player> players; public ObservableCollection<Player> Players { get { return players; } set { players = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Players")); } } private List<string> playerNames = new List<string>(); public List<string> PlayerNames { get { return playerNames; } set { playerNames = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PlayerNames")); } } private GameType selectedGame; public GameType SelectedGame { get { return selectedGame; } set { selectedGame = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedGame")); } } private string gameName; public string GameName { get { return gameName; } set { gameName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("GameName")); } } public event PropertyChangedEventHandler PropertyChanged; }
Player 代码
public class Player : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get { return name; } set { name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); } } private string currentScore = "0"; public string CurrentScore { get { return currentScore; } set { currentScore = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentScore")); } } private string gamesWon = "0"; public string GamesWon { get { return gamesWon; } set { gamesWon = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("GamesWon")); } } //public override string ToString() //{ // return Name; //} }
ListView 代码(原错误版本)
<ListView x:Name="listviewPlayers" HorizontalOptions="End" ItemsSource="{Binding Players}"> <ListView.ItemTemplate> <DataTemplate> <Label Text="{Binding Players.Name}" /> </DataTemplate> </ListView.ItemTemplate> </ListView>
ViewModel 更新代码
public void InitGame(Game game) { //initialize points and setup first player viewModel.Players = game.Players; viewModel.SelectedGame = game.SelectedGame; viewModel.GameName = game.SelectedGame.Name; contentMain.BindingContext = viewModel; }
问题原因与解决方案
核心问题
当 ListView 的 ItemsSource 绑定到 ObservableCollection<Player> 后,ItemTemplate 内部的数据上下文已经自动切换为集合中的单个 Player 对象,此时不需要再通过 Players.Name 来访问属性——Players 是 ViewModel 中的集合属性,单个 Player 对象并没有这个属性,所以会报错。
而使用 {Binding Players[0].Name} 的方式,本质是让 ListView 的所有条目都绑定到 ViewModel 中 Players 集合的第一个元素的 Name 属性,因此所有行都会显示同一个名字,这是错误的用法,并非预期的"显示所有 Player 的 Name"。
修正后的 ListView 代码
将 ItemTemplate 中的绑定改为直接访问 Player 对象的 Name 属性即可:
<ListView x:Name="listviewPlayers" HorizontalOptions="End" ItemsSource="{Binding Players}"> <ListView.ItemTemplate> <DataTemplate> <Label Text="{Binding Name}" /> </DataTemplate> </ListView.ItemTemplate> </ListView>
这样 ListView 会自动遍历 Players 集合中的每个 Player 对象,将每个条目的 Label 绑定到对应 Player 的 Name 属性,实现正确的显示效果。
内容的提问来源于stack exchange,提问作者Mike

