我尝试了几种不同的方法来做到这一点,但还没有完全弄清楚如何组合才能使其按预期工作。这是在使用 .NET 6 的 .NET MAUI 中。基本上,UI 不会更新/显示 ListView 行和值。我发现,如果我在调试期间更改 .xaml(使用热重载过程),则 ListView 的值会神奇地出现。因此,我确信 UI 不会收到 ObservableCollection 已更改的通知。当在我的 UI - MainPage.xaml 中单击
GetList
按钮时,将调用一个异步方法来从我的 API 获取数据,并使用 API 返回的数据填充可观察集合。下面是必要的代码:
更新:奇怪的是,在 XAML 中添加
<Label Text="{Binding MyObvList.Count}" />
会导致 UI 刷新,成功显示列表及其行/变量。然而,当这个标签被移除时,它又会回到原来的方式。所以,我进一步确信这是一个更新问题。
这是我的 XAML(MainPage.xaml):
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
x:Class="MyApp.MainPage"
x:Name="PageRoot"
Title="My App">
<VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center">
<Label Text="My App"
SemanticProperties.HeadingLevel="Level2"
FontSize="18"
HorizontalOptions="Center" />
<Button Text="Get List" Clicked="GetList"/>
<StackLayout Orientation="Horizontal">
<Label Text="Number" WidthRequest="100" />
<Label Text="Name" WidthRequest="100" />
<Label Text="Info" WidthRequest="100" />
</StackLayout>
<ListView ItemsSource="{Binding MyObvList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Number}" WidthRequest="100" />
<Label Text="{Binding Name}" WidthRequest="100" />
<Label Text="{Binding Info}" WidthRequest="300" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ContentPage>
隐藏代码(MainPage.xaml.cs):
namespace MyApp;
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
private ObservableCollection<MyData> _myObvList = new();
public ObservableCollection<MyData> MyObvList {
get { return _myObvList; }
set {
if(_myObvList != value) {
_myObvList = value;
OnPropertyChanged(nameof(MyObvList));
}
}
}
public MainPage()
{
InitializeComponent();
BindingContext = this;
NavigationPage.SetHasNavigationBar(this, false);
NavigationPage.SetHasBackButton(this, false);
}
async void GetList(object sender, EventArgs e)
{
try {
List<MyAPIData> apiData = await MyApi.GetDataAsync();
MainThread.BeginInvokeOnMainThread(() => {
MyObvList.Clear();
if(apiData != null && apiData.Count > 0) {
foreach(MyAPIData data in apiData) {
MyData myNewData = new();
myNewData.Name = = data.Person.FirstName + " " + (string.IsNullOrEmpty(data.Person.MiddleName) ? "" : data.Person.MiddleName + " ") + data.Person.LastName + (string.IsNullOrEmpty(data.Person.Suffix) ? "" : " " + data.Person.Suffix);
myNewData.Number = data.Number;
myNewData.Info = data.Info;
MyObvList.Add(myNewData);
}
}
}
} catch(Exception ex) {
Debug.WriteLine($"An error occurred: {ex.Message}");
}
}
}
然后这是 MyData.cs 类:
public class MyData : INotifyPropertyChanged {
private string number { get; set; }
public string Number {
get { return number; }
set {
if(number != value) {
number = value;
OnPropertyChanged(nameof(Number));
}
}
}
private string name { get; set; }
public string Name {
get { return name; }
set {
if(name != value) {
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
private string info { get; set; }
public string Info {
get { return info; }
set {
if(info != value) {
info = value;
OnPropertyChanged(nameof(Info));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
此外,当我下次单击按钮时插入
<Label Text="{Binding MyObvList.Count}" />
时,我会收到“对象引用未设置到对象的实例。”错误,因为它尝试执行MyObvList.Clear();
此操作iOS 显然是众所周知的。我已经看到了各种建议的解决方案,例如将对象设置为新的 ObservableCollection<MyData>
或 while(MyObvList.Any()) { MyObvList.RemoveAt(0); }
然而,我对这些没有任何运气,未能让列表再次填充。我尝试切换到 MVVM 模型,但遇到了与上面相同的问题。所以,这里发生了更大的事情。需要解决方案。
我没有发现代码有任何问题。
作为解决方法,添加所有数据后,执行以下操作:
OnPropertyChanged(nameof(MyObvList));
我还建议在后台线程上获取数据:
List<MyAPIData> apiData;
// "await" needed, so that code later in method doesn't run until Task.Run finishes.
await Task.Run(async () =>
{
apiData = await MyApi.GetDataAsync();
});
// NOTE: Might not need BeginInvoke here. Try it both with and without.
MainThread.BeginInvokeOnMainThread(() => {
MyObvList.Clear();
...
OnPropertyChanged(nameof(MyObvList)); // ADDED
});
按钮单击方法有可能不喜欢在 GetDataAsync 运行时被延迟。如果以上方法不起作用,请尝试以下方法:
// *queue* to run after Button click method returns.
MainThread.BeginInvokeOnMainThread(() => {
List<MyAPIData> apiData;
await Task.Run(async () =>
{
apiData = await MyApi.GetDataAsync();
});
MyObvList.Clear();
...
OnPropertyChanged(nameof(MyObvList)); // ADDED
});