Xamarin 从视图模型传递列表数据以进行视图绑定
Xamarin passing list data from view model for view binding
Xamarin 的新手,在我下面的代码示例中,我试图弄清楚如何从我的视图模型中传递玩家以在视图中进行绑定。我已经确认 GetPlayers() 获得了正确的数据。只是不确定我需要对 PlayersViewModel 进行哪些更新才能传递列表播放器以进行绑定。提前致谢!
PlayersViewModel.cs:
namespace App.ViewModels
{
public class PlayersViewModel : BaseViewModel
{
public ICommand GetPlayersCommand { get; set; }
public PlayersViewModel()
{
Title = "Players";
GetPlayersCommand = new Command(async () => await GetPlayers());
GetPlayersCommand.Execute(null);
}
private readonly string _yearDateFormat = "yyyy";
public List<Player> Players { get; private set; } = new List<Player>();
async Task GetPlayers()
{
IsRefreshing = true;
List<Player> players = await ApiManager.GetPlayersAsync(DateTime.Now.Year.ToString(_yearDateFormat));
IsRefreshing = false;
}
}
}
PlayersPage.xaml.cs
App.Views
{
[DesignTimeVisible(false)]
public partial class PlayersPage : ContentPage
{
readonly PlayersViewModel viewModel;
public PlayersPage()
{
InitializeComponent();
BindingContext = viewModel = new PlayersViewModel();
}
}
}
PlayersPage.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"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
mc:Ignorable="d"
x:Class="App.Views.PlayersPage"
Title="{Binding Title}"
x:Name="BrowsePlayersPage">
<ListView x:Name="PlayersView" ItemsSource="{Binding Players}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FirstName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
您正在绑定到从未设置为任何内容的 Players
属性。
尝试在构造函数中或任何您喜欢的地方调用 InitData()
:
public PlayersViewModel()
{
Title = "Players";
GetPlayersCommand = new Command(async () => await GetPlayers());
GetPlayersCommand.Execute(null);
InitData();
}
private async void InitData()
{
Players = await GetPlayers();
}
sabsab 的回答确实说明了错误的本质(OP 没有设置 Players
,所以它是空的。)
但是,如果您在构建 PlayersViewModel 时尝试执行长时间 运行 操作,您可能会遇到微妙的问题。
使用以下两种策略之一是最安全的:
显示没有球员的页面,然后在他们可用时添加他们。 (略有改动,可以等一下一次性全部加完。)
在创建 PlayersViewModel 之前执行缓慢的异步操作。将其作为参数传递给构造函数。
解决方案 1:
public partial class PlayersPage : ContentPage
{
public PlayersPage()
{
InitializeComponent();
BindingContext = PlayersViewModel.CreateDeferred();
}
}
public class PlayersViewModel : BindableObject
{
/// <summary>
/// Players starts empty. It is filled on a background thread.
/// This allows page to display quickly, though at first it lacks Players.
/// </summary>
/// <returns></returns>
public static PlayersViewModel CreateDeferred()
{
PlayersViewModel vm = new PlayersViewModel();
// Empty list for initial display.
vm.Players = new ObservableCollection<Player>();
// This version runs on Main Thread.
Device.BeginInvokeOnMainThread(async () => await vm.LoadPlayers());
// Returns before LoadPlayers is finished.
return vm;
}
public ObservableCollection<Player> Players { get; set; }
/// <summary>
/// "private". Only call this via static method.
/// </summary>
private PlayersViewModel()
{
}
/// <summary>
/// This touches Players, which is UI-visible, so only call from Main Thread.
/// aka "GetPlayers"; renamed to "Load"Players, because it does not "return" players to caller.
/// </summary>
/// <returns></returns>
async Task LoadPlayers()
{
Players.Clear();
for (int iPlayer = 0; iPlayer < 2; iPlayer++)
{
// Simulate query time. Remove from production code.
await Task.Delay(2000);
Player player = new Player();
Players.Add(player);
}
}
}
解决方案 1 的备选方案,在获取所有玩家之前不会显示。这使用解决方案 2 中的“PrefetchPlayers”:
// Alternative version, that waits until all players are fetched.
async Task LoadPlayers()
{
List<Player> players = await PrefetchPlayers();
Players = new ObservableCollection<Player>(players);
OnPropertyChanged(nameof(Players));
}
解决方案 2:
public partial class App : Application
{
public App()
{
InitializeComponent();
// This shows when app starts, while your data is loading.
//TODO MainPage = new MyLoadingPage();
}
protected override async void OnStart()
{
List<Player> players = await PlayersViewModel.PrefetchPlayers();
var page = new PlayersPage();
page.SetPlayers(players);
MainPage = page;
}
}
public partial class PlayersPage : ContentPage
{
public PlayersPage()
{
InitializeComponent();
// No Players added yet.
BindingContext = new PlayersViewModel();
}
public void SetPlayers(List<Player> players)
{
((PlayersViewModel)BindingContext).Players = new ObservableCollection<Player>(players);
}
}
public class PlayersViewModel : BindableObject
{
public static async Task<List<Player>> PrefetchPlayers()
{
var players = new List<Player>();
for (int iPlayer = 0; iPlayer < 2; iPlayer++)
{
// Simulate query time. Remove from production code.
await Task.Delay(2000);
Player player = new Player();
players.Add(player);
}
return players;
}
public ObservableCollection<Player> Players { get; set; }
public PlayersViewModel()
{
// Optional. Start with an empty list.
Players = new ObservableCollection<Player>();
}
}
Xamarin 的新手,在我下面的代码示例中,我试图弄清楚如何从我的视图模型中传递玩家以在视图中进行绑定。我已经确认 GetPlayers() 获得了正确的数据。只是不确定我需要对 PlayersViewModel 进行哪些更新才能传递列表播放器以进行绑定。提前致谢!
PlayersViewModel.cs:
namespace App.ViewModels
{
public class PlayersViewModel : BaseViewModel
{
public ICommand GetPlayersCommand { get; set; }
public PlayersViewModel()
{
Title = "Players";
GetPlayersCommand = new Command(async () => await GetPlayers());
GetPlayersCommand.Execute(null);
}
private readonly string _yearDateFormat = "yyyy";
public List<Player> Players { get; private set; } = new List<Player>();
async Task GetPlayers()
{
IsRefreshing = true;
List<Player> players = await ApiManager.GetPlayersAsync(DateTime.Now.Year.ToString(_yearDateFormat));
IsRefreshing = false;
}
}
}
PlayersPage.xaml.cs
App.Views
{
[DesignTimeVisible(false)]
public partial class PlayersPage : ContentPage
{
readonly PlayersViewModel viewModel;
public PlayersPage()
{
InitializeComponent();
BindingContext = viewModel = new PlayersViewModel();
}
}
}
PlayersPage.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"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
mc:Ignorable="d"
x:Class="App.Views.PlayersPage"
Title="{Binding Title}"
x:Name="BrowsePlayersPage">
<ListView x:Name="PlayersView" ItemsSource="{Binding Players}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FirstName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
您正在绑定到从未设置为任何内容的 Players
属性。
尝试在构造函数中或任何您喜欢的地方调用 InitData()
:
public PlayersViewModel()
{
Title = "Players";
GetPlayersCommand = new Command(async () => await GetPlayers());
GetPlayersCommand.Execute(null);
InitData();
}
private async void InitData()
{
Players = await GetPlayers();
}
sabsab 的回答确实说明了错误的本质(OP 没有设置 Players
,所以它是空的。)
但是,如果您在构建 PlayersViewModel 时尝试执行长时间 运行 操作,您可能会遇到微妙的问题。
使用以下两种策略之一是最安全的:
显示没有球员的页面,然后在他们可用时添加他们。 (略有改动,可以等一下一次性全部加完。)
在创建 PlayersViewModel 之前执行缓慢的异步操作。将其作为参数传递给构造函数。
解决方案 1:
public partial class PlayersPage : ContentPage
{
public PlayersPage()
{
InitializeComponent();
BindingContext = PlayersViewModel.CreateDeferred();
}
}
public class PlayersViewModel : BindableObject
{
/// <summary>
/// Players starts empty. It is filled on a background thread.
/// This allows page to display quickly, though at first it lacks Players.
/// </summary>
/// <returns></returns>
public static PlayersViewModel CreateDeferred()
{
PlayersViewModel vm = new PlayersViewModel();
// Empty list for initial display.
vm.Players = new ObservableCollection<Player>();
// This version runs on Main Thread.
Device.BeginInvokeOnMainThread(async () => await vm.LoadPlayers());
// Returns before LoadPlayers is finished.
return vm;
}
public ObservableCollection<Player> Players { get; set; }
/// <summary>
/// "private". Only call this via static method.
/// </summary>
private PlayersViewModel()
{
}
/// <summary>
/// This touches Players, which is UI-visible, so only call from Main Thread.
/// aka "GetPlayers"; renamed to "Load"Players, because it does not "return" players to caller.
/// </summary>
/// <returns></returns>
async Task LoadPlayers()
{
Players.Clear();
for (int iPlayer = 0; iPlayer < 2; iPlayer++)
{
// Simulate query time. Remove from production code.
await Task.Delay(2000);
Player player = new Player();
Players.Add(player);
}
}
}
解决方案 1 的备选方案,在获取所有玩家之前不会显示。这使用解决方案 2 中的“PrefetchPlayers”:
// Alternative version, that waits until all players are fetched.
async Task LoadPlayers()
{
List<Player> players = await PrefetchPlayers();
Players = new ObservableCollection<Player>(players);
OnPropertyChanged(nameof(Players));
}
解决方案 2:
public partial class App : Application
{
public App()
{
InitializeComponent();
// This shows when app starts, while your data is loading.
//TODO MainPage = new MyLoadingPage();
}
protected override async void OnStart()
{
List<Player> players = await PlayersViewModel.PrefetchPlayers();
var page = new PlayersPage();
page.SetPlayers(players);
MainPage = page;
}
}
public partial class PlayersPage : ContentPage
{
public PlayersPage()
{
InitializeComponent();
// No Players added yet.
BindingContext = new PlayersViewModel();
}
public void SetPlayers(List<Player> players)
{
((PlayersViewModel)BindingContext).Players = new ObservableCollection<Player>(players);
}
}
public class PlayersViewModel : BindableObject
{
public static async Task<List<Player>> PrefetchPlayers()
{
var players = new List<Player>();
for (int iPlayer = 0; iPlayer < 2; iPlayer++)
{
// Simulate query time. Remove from production code.
await Task.Delay(2000);
Player player = new Player();
players.Add(player);
}
return players;
}
public ObservableCollection<Player> Players { get; set; }
public PlayersViewModel()
{
// Optional. Start with an empty list.
Players = new ObservableCollection<Player>();
}
}