Xamarin.Forms ListView 绑定问题
Xamarin.Forms ListView Binding Issue
我们是 Xamarin 的新手。我们在将 Web 服务的响应数据绑定到 ListView 时遇到问题。
我们进行了调试,我们可以看到 Web 服务成功响应了数据,但它从未被填充。
有什么想法吗?
这一定是我们遗漏的一件小事。我们已经设法用其他视图(在项目的其他部分)显示数据中的单个条目,但不在 IEnumerable<>
或 List<>
中
代码如下:
查看 - RoundsPage.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:viewModels="clr-namespace:AthlosifyMobile.ViewModels"
x:Class="AthlosifyMobile.Views.RoundsPage">
<ContentPage.BindingContext>
<viewModels:RoundsViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Entry Text="{Binding AccessToken}" />
<Button Command="{Binding GetRoundsCommand}" Text="Get all rounds" />
<Label Text="Rounds: "></Label>
<ListView ItemsSource="{Binding Rounds}" HasUnevenRows="true" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Round 1:"></Label>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding DailyHandicap}"></Label>
<Label Text="{Binding PlayedUTC}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>`
ViewModel - RoundsViewModel.cs :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using AthlosifyMobile.Annotations;
using Xamarin.Forms;
using AthlosifyMobile.Services;
using AthlosifyMobile.Models;
namespace AthlosifyMobile.ViewModels
{
public class RoundsViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Round> _rounds;
public string AccessToken { get; set; }
public IEnumerable<Round> Rounds
{
get
{
return _rounds;
}
set
{
_rounds = value;
OnPropertyChanged();
}
}
public ICommand GetRoundsCommand
{
get
{
return new Command(async() =>
{
Rounds = await _apiServices.GetRoundsAsync(AccessToken);
});
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
型号 - Course.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AthlosifyMobile.Models
{
public class Round : EntityBase
{
public Guid RoundID { get; set; }
public Guid UserID { get; set; }
public Guid RoundCategoryID { get; set; }
public Guid CourseID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public int DailyHandicap { get; set; }
public DateTime PlayedUTC { get; set; }
public RoundCategory RoundCategory { get; set; }
public Course Course { get; set; }
public ICollection<RoundHole> RoundHoles { get; set; }
}
public abstract class EntityBase
{
public DateTime CreatedUTC { get; set; }
public DateTime LastModifiedUTC { get; set; }
}
}
服务 - apiservices.cs:
using AthlosifyMobile.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace AthlosifyMobile.Services
{
public async Task<IEnumerable<Round>> GetRoundsAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://localhost:5609/api/Rounds");
var list = JsonConvert.DeserializeObject<IEnumerable<Round>>(json);
return list;
}
}
}
您需要诊断这是否是将视图连接到 ViewModel 的问题,或者您的数据服务是否无法正常工作。无论哪种方式,您都应该采取一些措施来解决这个问题!
首先,您使用的是 IEnumerable
,您应该对绑定列表视图使用 ObservableCollection<T>. You should always be using
ObservableCollection`。这在 xamarin 文档 here 中进行了解释(当内容更改和更新时,它们会自动通知视图)。
所以你应该做这个改变:
private ObservableCollection<Round> _rounds;
public ObservableCollection<Round> Rounds
{
get { return _rounds; }
set
{
_rounds = value;
OnPropertyChanged("Rounds");
}
}
接下来您应该验证绑定是否正确。如果您不熟悉 xamarin,我不会推荐您直接使用实时数据的方法。首先,您应该尝试向视图模型添加一些静态对象并尝试绑定它们!
断开您的 API 代码并调用创建一些 Round
对象的方法。这是一个示例方法(我在设计 ListViews UI 时一直使用这些方法)。
public RoundsViewModel()
{
_rounds = CreateSampleData();
}
private ObservableCollection<Round> CreateSampleData()
{
ObservableCollection<Round> dummyData = new ObservableCollection<Round>();
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
return dummyData;
}
此时您将在 ListView 中看到项目,这意味着您的 API 代码/INotifyPropertyChanged
的实现有问题。如果您没有看到任何东西,那么您可能遇到了绑定问题,需要验证您的视图是否确实连接到视图模型。
Mvvm 助手
看到这些代码中的一些让我为您感到非常抱歉,您绝对应该考虑使用 MVVM 助手,例如 Prism 或 MVVMCross。我个人使用 Prism,它提供了一个 ViewModelBase
,所有 ViewModels 都从中继承。这意味着所有 INotifyPropertyChanged
代码都对您隐藏(更少样板)。它还具有依赖服务,这意味着将视图连接到视图模型就像在 app.cs.
中注册一样简单。
如果您对 Prism 感兴趣,请观看 this video with Brian Lagunas 看看 Prism 能为您做些什么!
我们是 Xamarin 的新手。我们在将 Web 服务的响应数据绑定到 ListView 时遇到问题。
我们进行了调试,我们可以看到 Web 服务成功响应了数据,但它从未被填充。
有什么想法吗?
这一定是我们遗漏的一件小事。我们已经设法用其他视图(在项目的其他部分)显示数据中的单个条目,但不在 IEnumerable<>
或 List<>
代码如下:
查看 - RoundsPage.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:viewModels="clr-namespace:AthlosifyMobile.ViewModels"
x:Class="AthlosifyMobile.Views.RoundsPage">
<ContentPage.BindingContext>
<viewModels:RoundsViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Entry Text="{Binding AccessToken}" />
<Button Command="{Binding GetRoundsCommand}" Text="Get all rounds" />
<Label Text="Rounds: "></Label>
<ListView ItemsSource="{Binding Rounds}" HasUnevenRows="true" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Round 1:"></Label>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding DailyHandicap}"></Label>
<Label Text="{Binding PlayedUTC}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>`
ViewModel - RoundsViewModel.cs :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using AthlosifyMobile.Annotations;
using Xamarin.Forms;
using AthlosifyMobile.Services;
using AthlosifyMobile.Models;
namespace AthlosifyMobile.ViewModels
{
public class RoundsViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Round> _rounds;
public string AccessToken { get; set; }
public IEnumerable<Round> Rounds
{
get
{
return _rounds;
}
set
{
_rounds = value;
OnPropertyChanged();
}
}
public ICommand GetRoundsCommand
{
get
{
return new Command(async() =>
{
Rounds = await _apiServices.GetRoundsAsync(AccessToken);
});
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
型号 - Course.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AthlosifyMobile.Models
{
public class Round : EntityBase
{
public Guid RoundID { get; set; }
public Guid UserID { get; set; }
public Guid RoundCategoryID { get; set; }
public Guid CourseID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public int DailyHandicap { get; set; }
public DateTime PlayedUTC { get; set; }
public RoundCategory RoundCategory { get; set; }
public Course Course { get; set; }
public ICollection<RoundHole> RoundHoles { get; set; }
}
public abstract class EntityBase
{
public DateTime CreatedUTC { get; set; }
public DateTime LastModifiedUTC { get; set; }
}
}
服务 - apiservices.cs:
using AthlosifyMobile.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace AthlosifyMobile.Services
{
public async Task<IEnumerable<Round>> GetRoundsAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://localhost:5609/api/Rounds");
var list = JsonConvert.DeserializeObject<IEnumerable<Round>>(json);
return list;
}
}
}
您需要诊断这是否是将视图连接到 ViewModel 的问题,或者您的数据服务是否无法正常工作。无论哪种方式,您都应该采取一些措施来解决这个问题!
首先,您使用的是 IEnumerable
,您应该对绑定列表视图使用 ObservableCollection<T>. You should always be using
ObservableCollection`。这在 xamarin 文档 here 中进行了解释(当内容更改和更新时,它们会自动通知视图)。
所以你应该做这个改变:
private ObservableCollection<Round> _rounds;
public ObservableCollection<Round> Rounds
{
get { return _rounds; }
set
{
_rounds = value;
OnPropertyChanged("Rounds");
}
}
接下来您应该验证绑定是否正确。如果您不熟悉 xamarin,我不会推荐您直接使用实时数据的方法。首先,您应该尝试向视图模型添加一些静态对象并尝试绑定它们!
断开您的 API 代码并调用创建一些 Round
对象的方法。这是一个示例方法(我在设计 ListViews UI 时一直使用这些方法)。
public RoundsViewModel()
{
_rounds = CreateSampleData();
}
private ObservableCollection<Round> CreateSampleData()
{
ObservableCollection<Round> dummyData = new ObservableCollection<Round>();
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
return dummyData;
}
此时您将在 ListView 中看到项目,这意味着您的 API 代码/INotifyPropertyChanged
的实现有问题。如果您没有看到任何东西,那么您可能遇到了绑定问题,需要验证您的视图是否确实连接到视图模型。
Mvvm 助手
看到这些代码中的一些让我为您感到非常抱歉,您绝对应该考虑使用 MVVM 助手,例如 Prism 或 MVVMCross。我个人使用 Prism,它提供了一个 ViewModelBase
,所有 ViewModels 都从中继承。这意味着所有 INotifyPropertyChanged
代码都对您隐藏(更少样板)。它还具有依赖服务,这意味着将视图连接到视图模型就像在 app.cs.
如果您对 Prism 感兴趣,请观看 this video with Brian Lagunas 看看 Prism 能为您做些什么!