.NET MAUI ListView - ObservableCollection - 在异步方法期间不更新

问题描述 投票:0回答:1

我尝试了几种不同的方法来做到这一点,但还没有完全弄清楚如何组合才能使其按预期工作。这是在使用 .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 模型,但遇到了与上面相同的问题。所以,这里发生了更大的事情。需要解决方案。

.net xaml listview maui observablecollection
1个回答
0
投票

我没有发现代码有任何问题。

作为解决方法,添加所有数据后,执行以下操作:

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
});
© www.soinside.com 2019 - 2024. All rights reserved.