Xamarin 从视图模型传递列表数据以进行视图绑定

Posted

技术标签:

【中文标题】Xamarin 从视图模型传递列表数据以进行视图绑定【英文标题】:Xamarin passing list data from view model for view binding 【发布时间】:2021-12-10 00:36:31 【问题描述】:

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>

【问题讨论】:

【参考方案1】:

您正在绑定到从未设置为任何内容的Players 属性。

尝试在构造函数中或任何你喜欢的地方调用InitData()

public PlayersViewModel()

    Title = "Players";
    GetPlayersCommand = new Command(async () => await GetPlayers());
    GetPlayersCommand.Execute(null);

    InitData();


private async void InitData()

    Players = await GetPlayers();

【讨论】:

感谢您的评论有助于解释问题。 这是一个答案,不是评论...欢迎您【参考方案2】:

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 和 MVVM 有更多经验时,请定义一些学习点。我尝试使用解决方案 1 代码作为起始模板并得到以下异常:“System.MissingMethodException Message=Default constructor not found for type App.Views.PlayersPage”Thoughts? 我认为错误可能与 MainPage = new PlayersPage(new PlayersViewModel(players));在应用程序()?随着数据的加载,但不会被转储到我的主页上并且导航中断。您如何在特定导航页面(“PlayersPage”)而不是 MainPage 上指定此负载? @OtoNoOto - 为避免出现问题,我更改了两种解决方案,以便构造函数不使用参数。解决方案 2 中的 OnStart 现在展示了如何让玩家展示。它在PlayersPage 上调用了一个新添加的方法SetPlayers @OtoNoOto - 注意:如果您使用的是Shell,您可能有来自ShellContent 的PlayersPage,因此在ContentPage 变量中。我将像这样模拟:ContentPage page = new PlayersPage(); 在这种情况下,将其“投射”到 PlayersPage:((PlayersPage)page).SetPlayers(players);。只有当您收到编译器或智能感知投诉它无法在 page 上找到 SetPlayers 时,您才需要这个。 ToolmakerSteve 感谢您提供的所有详细的 cmets 和解释。我会将您的评论标记为答案,但为了社区的利益,我想声明我是 Xamarin 的新手,所以不是最佳实践等的最佳判断。但是,您的 cmets 让我开始走上正确的道路。

以上是关于Xamarin 从视图模型传递列表数据以进行视图绑定的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin-如何以编程方式扩展列表视图?

Django - 将模型名称从 url 传递给视图

将 managedobject 从 uitableview 传递到文本字段以在核心数据模型中进行编辑

MVC 4如何将模型属性列表从视图传递给Action

Xamarin Forms Switch Toggled 事件未与视图模型绑定

Xamarin - 点击时如何从列表视图中的 ImageCell 获取数据绑定字符串值?