如何将MAUI选择器与基于SQLite数据库的视图模型一起使用?

fruv7luv  于 2023-10-23  发布在  SQLite
关注(0)|答案(4)|浏览(151)

我的MAUI.net应用程序需要处理几个数据库,一个是不同的球队,另一个是参与者。我想使用选择器,以便在创建/编辑参与者的详细信息时选择其小队,但当我打开参与者查看其详细信息时,我最终得到一个空的选择器。在调试时,当我保存/加载一个参与者时,我可以看到SelectedSquad被正确设置,但是尽管有SelectedItem绑定,它也没有出现在选择器中。
我的参与者视图模型(包含有关参与者的标准信息以及从Squad数据库中提取的信息):

using MatchTracker.Pages;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Web;

namespace MatchTracker.Models.ViewModels
{
    public partial class ParticipantDetailsVM : BaseViewModel, IQueryAttributable
    {

        [ObservableProperty]
        Participant currentParticipant;

        [ObservableProperty]
        Squad selectedSquad;

        public ObservableCollection<Squad> SquadsList { get; }

        [ObservableProperty]
        public string saveButtonText;

        public ParticipantDetailsVM()
        {
            Title = "Participant Details";
            SquadsList = new ObservableCollection<Squad>();
        }

        public async void ApplyQueryAttributes(IDictionary<string, object> query)
        {
            if (int.TryParse(HttpUtility.UrlDecode(query["id"].ToString()), out int Id))
                if (Id == 0)
                {
                    SaveButtonText = "Add Participant";
                    CurrentParticipant = new Participant { Id = 0 };
                }
                else
                {
                    SaveButtonText = "Update participant";
                    CurrentParticipant = await App._ParticipantsDb.GetAsync(Id);
                }
        }

        async Task LoadSquads()
        {
            try
            {
                SquadsList.Clear();

                var all = await App._SquadsDb.GetAllAsync();
                foreach (var item in all)
                {
                    SquadsList.Add(item);
                }
            
                SelectedSquad = await App._SquadsDb.GetAsync(CurrentParticipant.SquadId);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                RefreshNeeded = false;
            }
        }

        [RelayCommand]
        async Task SaveParticipant()
        {
            try
            {
                CurrentParticipant.SquadId = SelectedSquad.Id;
                await App._ParticipantsDb.AddAsync(CurrentParticipant);
                await Shell.Current.GoToAsync(nameof(ParticipantsListView));

            } catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        public async void OnAppearing()
        {
            await LoadSquads();
        }
    }
}

我的参与者视图的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"
             x:Class="MatchTracker.Pages.ParticipantDetailsView"
             xmlns:vm="clr-namespace:MatchTracker.Models.ViewModels"
             x:DataType="vm:ParticipantDetailsVM"
             Title="{Binding Title}">
    <VerticalStackLayout>
        <TableView Intent="Data">
            <TableRoot>
                <TableSection Title="Basic Information">
                    <EntryCell x:Name="ParticipantNameField" Label="Name" Text="{Binding CurrentParticipant.Name, Mode=TwoWay}" />
                </TableSection>
            </TableRoot>
        </TableView>
        <Picker x:Name="ParticipantSquadPicker" Title="Squad" ItemsSource="{Binding SquadsList}" SelectedItem="{Binding SelectedSquad, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}"/>
        <Label x:Name="Debug" Text="{Binding CurrentParticipant.SquadId}"/>
        <Button x:Name="SaveButton" Text="{Binding SaveButtonText}" Command="{Binding SaveParticipantCommand}"/>
    </VerticalStackLayout>
</ContentPage>

链接到上一个XAML文件的.cs文件:

using MatchTracker.Models.ViewModels;

namespace MatchTracker.Pages;

public partial class ParticipantDetailsView : ContentPage
{
    public ParticipantDetailsView(ParticipantDetailsVM vm)
    {
        InitializeComponent();
        BindingContext = vm;
    }
    protected override void OnAppearing()
    {
        base.OnAppearing();
        ((ParticipantDetailsVM)BindingContext)?.OnAppearing();
    }
}
b1zrtrql

b1zrtrql1#

如果你要使用ObservableProperty,把你的领域,使它成为一个属性,你需要使用该属性,即使用它作为一个属性与资本S时,分配您的外地队。
因此,您的ViewModel应该看起来像这样才能正确绑定

public partial class ParticipantViewModel : ObservableObject
{
    public ParticipantViewModel(Participant participant)
    {
        Name = participant.Name;
        InitAsync(participant);
    }
    private async Task InitAsync(Participant participant)
    {
        if (participant.SquadId != 0) Squad = await App._SquadsDb.GetAsync(participant.SquadId);
        Squads = await App._SquadsDb.GetAllAsync();
    }

    [ObservableProperty]
    private string name;

    [ObservableProperty]
    private Squad squad;

    [ObservableProperty]
    private List<Squad> squads;
}
qpgpyjmq

qpgpyjmq2#

简单解决方案

更改此选项:

if (participant.SquadId != 0) squad = await App._SquadsDb.GetAsync(participant.SquadId);
squads = await App._SquadsDb.GetAllAsync();

变成这样:

if (participant.SquadId != 0) Squad = await App._SquadsDb.GetAsync(participant.SquadId);
Squads = await App._SquadsDb.GetAllAsync();

注意auto-generated properties赋值的不同之处。您的UI不会更新,因为您将值分配给 backing fields 而不是 properties。只有当你设置属性的值时,observable属性的自动生成的setter方法才会调用PropertyChanged事件,当你直接操作后台字段的值时,这不会发生。
由于您使用的是async void初始化器,因此这些支持字段仅在 * 构造函数完成执行后才被设置,这意味着在此之后不再计算绑定表达式。发生这种情况是因为构造函数总是同步运行的,所以当构造函数已经完成时,您的异步方法仍然在执行,并且更改了后台字段的值,尽管绑定表达式已经执行。因此,在没有收到有关这些更改的通知的情况下,选择器无法知道有关SquadSquads的这些更改值的任何信息。

改进方案

与其使用async void初始化器方法,不如创建一个Task,可以await

// leave constructor empty or remove it altogether
public ParticipantViewModel() { } 

// caller should await initializer
public async Task InitAsync(Participant participant)
{
    Name = participant.Name;
    if (participant.SquadId != 0) Squad = await App._SquadsDb.GetAsync(participant.SquadId);
    Squads = await App._SquadsDb.GetAllAsync();
}

然后像这样调用它(例如,在protected override async void OnAppearing()方法中):

ParticipantVM = new ParticipantViewModel();
BindingContext = ParticipantVM;
await ParticipantVM.InitAsync(Participant);
yvgpqqbh

yvgpqqbh3#

请尝试更改类ParticipantViewModel.cs的以下代码:

[ObservableProperty]
    private List<Squad> squads;

[ObservableProperty]
    private ObservableCollection<Squad> squads;

此外,我们还使用开始以小写字母开头的变量Squads,而不是squads

public partial class ParticipantViewModel : ObservableObject
{
    public ParticipantViewModel(Participant participant) {
        name = participant.Name;
        InitAsync(participant);
    }
    private async void InitAsync(Participant participant)
    {  
        // use `Squads ` not `squads`
        if (participant.SquadId != 0) Squad = await App._SquadsDb.GetAsync(participant.SquadId);

        // here please cast the returned type to type ObservableCollection<T>
        Squads = await App._SquadsDb.GetAllAsync();
    }

    [ObservableProperty]
    private string name;

    [ObservableProperty]
    private Squad squad;

    [ObservableProperty]
    private ObservableCollection<Squad> squads;
}

有关详细信息,请查看文档ObservableObject.

brccelvz

brccelvz4#

谢谢你的回答,我终于找到了解决问题的方法:

  • 对于第一个问题,选择器没有显示任何数据,因为@Julian注意到这个问题与自动生成的属性有关,我的属性标记为[ObservableProperty],但我使用的是后台字段(selectedSquad)而不是属性本身(SelectedSquad),将字符串改为小写字母解决了这个问题。
  • 第二个问题,无法加载带有预选数据的选择器,通过以下方式解决:在我的ViewModel中,而不是
var all = await App._SquadsDb.GetAllAsync();
foreach (var item in all)
{
    SquadsList.Add(item);
}
SelectedSquad = await App._SquadsDb.GetAsync(CurrentParticipant.SquadId);

我不得不使用

var all = await App._SquadsDb.GetAllAsync();
foreach (var item in all)
{
    SquadsList.Add(item);
    if (item.Id == CurrentParticipant.SquadId) SelectedSquad = item;
}

结果表明,即使项目列表和所选项目来自相同的数据库,应用程序也不会将该项目视为列表的一部分。

相关问题