You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MAUI应用MVVM模式下数据丢失问题及持久化方法咨询

MAUI MVVM应用数据丢失与界面不更新问题

问题描述

搭建采用MVVM架构的MAUI应用,划分Views、Viewmodels、Models文件夹,Shell导航可正常切换视图。DailyPage对应DailyViewModel,ViewModel用ObservableRangeCollection<ExampleModel>维护DailyList并初始化测试数据,通过Community Toolkit弹窗添加新项。弹窗的BindingContext为新建的DailyViewModel实例,点击Add按钮时AddObjectCommand执行,调试显示项已添加,但界面列表未更新,每次操作后集合仅保留初始测试数据,切换视图数据也会丢失。

问题原因分析

  1. ViewModel实例不共享:弹窗在XAML中手动新建了DailyViewModel实例,而DailyPage的ViewModel是独立的另一个实例,两者的DailyList集合完全无关。弹窗添加的数据仅存入自身实例的集合,页面绑定的集合没有任何变化,因此界面不更新。
  2. 页面ViewModel未持久化:Shell导航默认每次导航到DailyPage时,都会创建新的DailyViewModel实例,之前添加的数据随旧实例被回收,切换视图后重新显示初始测试数据。
  3. DailyList属性未触发属性变更通知:虽然ObservableRangeCollection本身会通知集合内元素的变化,但DailyList的setter未调用ViewModelBase的SetProperty方法(若后续需要替换整个集合,会导致界面无法感知)。

解决方案

方案1:共享ViewModel实例给弹窗

核心思路:让弹窗使用和页面相同的DailyViewModel实例,确保操作的是同一个DailyList集合。

  1. 修改弹窗XAML:移除弹窗中手动创建ViewModel的代码:
<!-- 删除这一行 -->
<!-- <toolkit:Popup.BindingContext>
    <viewmodels:DailyViewModel/>
</toolkit:Popup.BindingContext> -->
  1. 修改DailyPage后台代码:打开弹窗时,将页面的BindingContext(即当前DailyViewModel实例)赋值给弹窗:
async void onAddEntryClicked(object sender, EventArgs e)
{
    var entryPopup = new AddEntryPopup();
    // 共享页面的ViewModel实例
    entryPopup.BindingContext = this.BindingContext;
    await this.ShowPopupAsync(entryPopup);
}

方案2:使用单例数据服务维护全局共享数据

核心思路:创建单例类统一管理共享数据,所有ViewModel都操作该单例中的集合,彻底解决实例不共享问题。

  1. 创建单例DataService:
public class DataService
{
    private static readonly DataService _instance = new DataService();
    public static DataService Instance => _instance;

    public ObservableRangeCollection<ExampleModel> DailyList { get; }

    private DataService()
    {
        // 初始化测试数据
        DailyList = new ObservableRangeCollection<ExampleModel>
        {
            new ExampleModel{ Name = "Test1" },
            new ExampleModel{ Name = "Test2" },
            new ExampleModel{ Name = "Test3" },
            new ExampleModel{ Name = "Test4" }
        };
    }
}
  1. 修改DailyViewModel:直接使用单例服务的集合:
public class DailyViewModel : ViewModelBase
{
    // 绑定到单例服务的集合
    public ObservableRangeCollection<ExampleModel> DailyList => DataService.Instance.DailyList;

    public AsyncCommand RefreshCommand { get; }

    public DailyViewModel()
    {
        Title = "Daily Page";
        AddObjectCommand= new MvvmHelpers.Commands.Command(AddObject);
        RefreshCommand = new AsyncCommand(Refresh);
    }

    string addName = "-";
    public string AddName
    {
        get => addName;
        set => SetProperty(ref addName, value);
    }

    public ICommand AddObjectCommand { get; }

    void AddObject()
    {
        ExampleModel ex = new ExampleModel() { Name = AddName };
        // 操作单例服务的集合
        DataService.Instance.DailyList.Add(ex);
    }
}
  1. 弹窗修改:弹窗的BindingContext可以设置为任意DailyViewModel实例(或直接绑定单例服务),因为所有实例都操作同一个集合。

方案3:通过依赖注入注册ViewModel为单例

核心思路:利用MAUI的依赖注入系统,将DailyViewModel注册为单例,确保整个应用中只存在一个实例,Shell导航和弹窗都会获取同一个实例。

  1. 修改MauiProgram.cs:注册DailyViewModel为单例:
var builder = MauiApp.CreateBuilder();
builder
    .UseMauiApp<App>()
    .ConfigureFonts(fonts =>
    {
        fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
    });

// 注册DailyViewModel为单例
builder.Services.AddSingleton<DailyViewModel>();
// 注册弹窗(可选,若需要注入ViewModel)
builder.Services.AddTransient<AddEntryPopup>();

return builder.Build();
  1. 修改DailyPage构造函数:通过构造函数注入ViewModel:
public partial class DailyPage : ContentPage
{
    public DailyPage(DailyViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }

    async void onAddEntryClicked(object sender, EventArgs e)
    {
        var entryPopup = new AddEntryPopup();
        // 弹窗使用同一个ViewModel实例
        entryPopup.BindingContext = this.BindingContext;
        await this.ShowPopupAsync(entryPopup);
    }
}
  1. 可选:弹窗构造函数注入:
public partial class AddEntryPopup : Popup
{
    public AddEntryPopup(DailyViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

额外优化:完善DailyList属性的变更通知

若后续需要替换整个DailyList集合,需确保属性变更能通知界面,修改DailyViewModel中的DailyList属性:

ObservableRangeCollection<ExampleModel> _dailyList;
public ObservableRangeCollection<ExampleModel> DailyList
{
    get => _dailyList;
    set => SetProperty(ref _dailyList, value);
}

内容的提问来源于stack exchange,提问作者Nico Sz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:05:41