【问题标题】:C# Xamarin Forms Populating CollectionView from ViewModel is always nullC# Xamarin Forms 从 ViewModel 填充 CollectionView 始终为空
【发布时间】:2022-01-16 02:26:38
【问题描述】:

我正在尝试从 ViewModel 填充集合视图,但是当我尝试将数据绑定到集合视图时,ViewModel 为空。

xaml.cs 文件

ObservableCollection<ReportsClass> newKidList = new ObservableCollection<ReportsClass>();

        public ReportsViewModel viewmodel { get; set; }

        public ReportsPage()
        {

            InitializeComponent();

            viewmodel = new ReportsViewModel();

            this.BindingContext = viewmodel;

            PreviousDateRange.CornerRadius = 20;
            NextDateRange.CornerRadius = 20;

            DateTime firstDate = currentDate.StartOfWeek(DayOfWeek.Sunday);
            DateTime secondDate = currentDate.AddDays(7).StartOfWeek(DayOfWeek.Saturday);

            DateRange.Text = firstDate.ToString("MMMM d") + " - " + secondDate.ToString("MMMM d");

            Kids.SetBinding(ItemsView.ItemsSourceProperty, nameof(viewmodel.kids));


        }

这是我的视图模型

public class ReportsViewModel
    {

        public ObservableCollection<ReportsClass> kids { get; set; }

        FirebaseStorageHelper firebaseStorageHelper = new FirebaseStorageHelper();

        WebServiceClass webServiceClass = new WebServiceClass();

        DateTime currentDate = DateTime.Now;

        public ReportsViewModel()
        {
            GetKids();
        }

        public async void GetKids()
        {
            var parentId = await SecureStorage.GetAsync("parentid");

            kids = await webServiceClass.Reports(Convert.ToInt32(parentId), currentDate.StartOfWeek(DayOfWeek.Sunday), currentDate.AddDays(7).StartOfWeek(DayOfWeek.Saturday));

        }
    }

这是获取视图模型数据的方法

public async Task<ObservableCollection<ReportsClass>> Reports(int parentid, DateTime startDate, DateTime endDate)
        {
            var content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("parentid", parentid.ToString()),
                new KeyValuePair<string, string>("startDate", startDate.ToString("yyyy-MM-dd H:mm:ss")),
                new KeyValuePair<string, string>("endDate", endDate.ToString("yyyy-MM-dd"))
            });

            var response = await client.PostAsync(string.Format("https://example.com/api/index.php?action=reports"), content);

            var responseString = await response.Content.ReadAsStringAsync();

            ObservableCollection<ReportsClass> items = JsonConvert.DeserializeObject<ObservableCollection<ReportsClass>>(responseString);

            return items;

        }

我做错了什么?我这样做的目的是为了更新collectionview中的一个项目

这是我的 ReportsClass

public class ReportsClass
{

    public ReportsClass(string firstName)
    {
        first_name = firstName;
    }

    public string first_name { get; set; }

}

【问题讨论】:

  • 了解如何在表单上使用 xaml 绑定。那里更容易
  • 这个例子?
  • 不要从构造函数调用异步代码。在您的虚拟机上使用 INPC

标签: c# xamarin.forms


【解决方案1】:

选项 A:

  1. 修复Kids.SetBinding 的语法,不获取null。请参阅 CLASS ReportsViewModel,而不是 INSTANCE viewmodel
    Kids.SetBinding(ItemsView.ItemsSourceProperty, nameof(ReportsViewModel.kids));
  1. 孩子们仍然不会出现在列表中。要修复,kids 需要 OnPropertyChanged
    public ObservableCollection<ItemModel> kids {
        get => _kids;
        set {
            _kids = value;
            OnPropertyChanged();
        }
    }
    private ObservableCollection<ItemModel> _kids;
  1. 参见选项 B 中的其他代码。根据需要进行调整。

  2. 当您需要 XAML 来查看动态更改时,您需要 OnPropertyChanged。这是INotifyPropertyChanged 的实现。将此调用添加到 ReportsClass 的属性(XAML 绑定到):

// Inheriting from `BindableObject` is one way to obtain OnPropertyChanged method.
public class ReportsClass : Xamarin.Forms.BindableObject
{

    public ReportsClass(string firstName)
    {
        first_name = firstName;
    }

    public string first_name {
        get => _first_name;
        set {
            _first_name = value;
            // This tells XAML there was a change.
            // Makes "{Binding first_name}" work dynamically.
            OnPropertyChanged();
        }
    }
    private string _first_name;

}

选项 B:

在任何地方都没有找到正确的答案,所以这里有一个完整的示例,以供将来参考:

  1. 删除Kids.SetBinding(...)。 (可以如 OPTION A 所示进行修复,但在 XAML 中更容易正确,所以下面我在 XAML 中显示它。)

  2. Bindings 从 Page 到 VM。请参阅下面的xaml

  3. 使用执行OnPropertyChanged 的setter 创建ObservableCollection。这会在列表准备好时通知 XAML,因此页面会更新。 (正如 Jason 提到的,这是 INotifyPropertyChanged 的实现。)

  4. 使用Device.BeginInvokeOnMainThread(async () 创建一个async 上下文,即排队 在构造函数返回后运行。 (这解决了 Jason 提到的问题,即构造函数不是 async 上下文,因此不应直接调用 async 方法,例如 QueryItemsAsync 或您的 GetKids。)这更可靠。

PageWithQueryData.xaml:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TestXFUWP.PageWithQueryData">
    <ContentPage.Content>
        <StackLayout>
            <CollectionView ItemsSource="{Binding Items}">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout>
                            <Label Text="{Binding Name}" />
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
                <CollectionView.EmptyView>
                    <Grid>
                        <Label Text="Loading ..." FontSize="24" TextColor="Blue" BackgroundColor="LightBlue" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />
                    </Grid>
                </CollectionView.EmptyView>
            </CollectionView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

PageWithQueryData.xaml.cs:

public partial class PageWithQueryData : ContentPage
{
    public PageWithQueryData()
    {
        InitializeComponent();

        // ... other initialization work here ...
        // BUT remove `Kids.Binding(...);` line. See XAML: `ItemsSource="{Binding Items}"`.

        BindingContext = new VMWithQueryData();
    }
}

VMWithQueryData.cs:

class VMWithQueryData : Xamarin.Forms.BindableObject
{
    public VMWithQueryData()
    {
        // Start an async task to query.
        Xamarin.Forms.Device.BeginInvokeOnMainThread(async () => {
            await QueryItemsAsync();
        });

        // Alternative implementation: Start a background task to query.
        //QueryItemsInBackground();
    }

    public ObservableCollection<ItemModel> Items {
        get => _items;
        set {
            _items = value;
            OnPropertyChanged();
        }
    }
    private ObservableCollection<ItemModel> _items;


    private async Task QueryItemsAsync()
    {
        var names = new List<string> { "One", "Two", "Three" };
        bool queryOneAtATime = false;// true;
        if (queryOneAtATime) {
            // Show each item as it is available.
            Items = new ObservableCollection<ItemModel>();
            foreach (var name in names) {
                // Simulate slow query - replace with query that returns one item.
                await Task.Delay(1000);
                Items.Add(new ItemModel(name));
            }
        } else {
            // Load all the items, then show them.
            // Simulate slow query - replace with query that returns all data.
            await Task.Delay(3000);
            var items = new ObservableCollection<ItemModel>();
            foreach (var name in names) {
                items.Add(new ItemModel(name));
            }
            Items = items;
        }
    }

    // Alternative implementation, using a background thread.
    private void QueryItemsInBackground()
    {
        Task.Run(() => {
            var names = new List<string> { "One", "Two", "Three" };
            bool queryOneAtATime = false;// true;
            if (queryOneAtATime) {
                // Show each item as it is available.
                Items = new ObservableCollection<ItemModel>();
                foreach (var name in names) {
                    // Simulate slow query - replace with query that returns one item.
                    System.Threading.Thread.Sleep(1000);
                    Items.Add(new ItemModel(name));
                }
            } else {
                // Load all the items, then show them.
                // Simulate slow query - replace with query that returns all data.
                System.Threading.Thread.Sleep(3000);
                var items = new ObservableCollection<ItemModel>();
                foreach (var name in names) {
                    items.Add(new ItemModel(name));
                }
                Items = items;
            }
        });
    }
}

ItemModel.cs:

public class ItemModel
{
    public ItemModel(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

这也演示了&lt;CollectionView.EmptyView&gt; 在查询数据时向用户显示消息。

为了完整起见,我提供了一个替代的QueryItemsInBackground,它使用后台线程而不是async 方法。两种方法都行之有效。

注意从Xamarin.Forms.BindableObject 继承。这是实现 INotifyPropertyChanged 的​​一种方法。您可以使用任何其他 MVVM 库或技术。

【讨论】:

  • 另一种方法是在创建页面之前查询数据。然后将数据传递给静态“创建”方法。 Shown here.
  • 嗨,是的,现在我的 collectionview 正在被填充,但是当我尝试这个时:viewmodel.kids[0].first_name = "Joe";用户界面上的 first_name 不会改变
  • @user979331 - 这意味着需要进行更改以通知 xaml 更改。添加以质疑ReportsClass 的来源。最重要的是,类声明 public class ReportsClass : ?anything-you-inherit-from-here?first_name 的声明 - 它需要类似于 ItemsOnPropertyChanged
  • 我添加了我的 ReportsClass
  • @user979331 - 我将固定的 ReportsClass 添加到我的答案中。选项 A,步骤 (4)。
【解决方案2】:

将这行代码移到构造函数的末尾

this.BindingContext = viewmodel;

【讨论】:

  • 首先你需要设置绑定到每个View by SetBinding end 在你完成设置你刚刚设置this.BindingContext的所有东西之后。据我所见,它不会破坏你的构造函数,因为没有调用 viewmodel 的行
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-29
  • 1970-01-01
  • 1970-01-01
  • 2021-05-05
  • 2019-11-08
  • 1970-01-01
  • 2020-08-25
相关资源
最近更新 更多