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

Xamarin应用Shell跳转至TaskPage后无法返回上一级页面问题求解

问题描述

操作流程如下:

  1. 从列表选中一个category
  2. 加载Tasks page
  3. 基于上一页选中的categoryId加载对应Tasks,此时可以正常返回Category page ✔️
  4. 从列表选中一个Task
  5. 加载Task Page
  6. 页面正常加载任务详情,但无法返回上一级Tasks page ❌

复现动图

问题复现动图

相关代码

CategoriesViewModel

public class CategoriesViewModel : BaseViewModel
{
    public ObservableCollection<CategoryModel> Categories { get; } = new ObservableCollection<CategoryModel>();

    public Command LoadCategoriesCommand { get; }
    public Command<CategoryModel> SelectedCategory { get; }

    public CategoriesViewModel()
    {
        Title = "Categories";

        LoadCategoriesCommand = new Command(async () => await LoadCategories());
        SelectedCategory = new Command<CategoryModel>(OnSelectedCategory);
    }

    private async Task LoadCategories()
    {
        IsBusy = true;
        try
        {
            Categories.Clear();

            var categories = await DatabaseService.GetCategoriesAsync();
            foreach (var category in categories)
            {
                this.Categories.Add(category);
            }
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            IsBusy = false;
        }
    }

    private async void OnSelectedCategory(CategoryModel category)
    {
        if (category == null)
            return;

        await Shell.Current.GoToAsync($"{nameof(TasksPage)}?{nameof(TasksViewModel.CategoryId)}={category.CategoryId}");
    }

    public void OnAppearing()
    {
        IsBusy = true;
    }
}

TasksViewModel

[QueryProperty(nameof(CategoryId), nameof(CategoryId))]
public class TasksViewModel : BaseViewModel
{
    public ObservableCollection<TaskModel> Tasks { get; } = new ObservableCollection<TaskModel>();

    private int categoryId;
    public int CategoryId
    {
        get { return categoryId; }
        set
        {
            categoryId = value;
        }
    }

    public Command LoadTasksCommand { get; set; }
    public Command<TaskModel> SelectedTask { get; set; }

    public TasksViewModel()
    {
        Title = "Tasks";
        LoadTasksCommand = new Command(async () => await LoadTasks());
        SelectedTask = new Command<TaskModel>(OnSelectedTask);
    }

    private async Task LoadTasks()
    {
        IsBusy = true;
        try
        {
            this.Tasks.Clear();

            var tasks = await DatabaseService.GetTasksAsync(CategoryId);
            foreach (var task in tasks)
            {
                this.Tasks.Add(task);
            }
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            IsBusy = false;
        }
    }

    private async void OnSelectedTask(TaskModel task)
    {
        if (task == null)
            return;

        await Shell.Current.GoToAsync($"{nameof(TaskPage)}?{nameof(TaskViewModel.TaskId)}={task.TaskId}");
    }

    public void OnAppearing()
    {
        IsBusy = true;
    }
}

TaskViewModel

[QueryProperty(nameof(TaskId), nameof(TaskId))]
public class TaskViewModel : BaseViewModel
{
    private int taskId;

    public int TaskId
    {
        get { return taskId; }
        set
        {
            taskId = value;
        }
    }

    private string taskTitle;

    public string TaskTitle
    {
        get { return taskTitle; }
        set
        {
            taskTitle = value;
            OnPropertyChanged(nameof(TaskTitle));
        }
    }

    private string description;

    public string Description
    {
        get { return description; }
        set
        {
            description = value;
            OnPropertyChanged(nameof(Description));
        }
    }

    public Command LoadTaskCommand { get; }

    public TaskViewModel()
    {
        LoadTaskCommand = new Command(async () => await LoadTask());
    }

    private async Task LoadTask()
    {
        IsBusy = true;
        try
        {
            var task = await DatabaseService.GetTaskAsync(TaskId);
            this.TaskTitle = task.Title;
            this.Description = task.Description;
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            IsBusy = false;
        }
    }

    public void OnAppearing()
    {
        IsBusy = true;
    }
}

更新1

我尝试将TasksViewModel中的跳转代码:

await Shell.Current.GoToAsync($"{nameof(TaskPage)}?{nameof(TaskViewModel.TaskId)}={task.TaskId}");

替换为:

await Shell.Current.Navigation.PushAsync(new AboutPage());

问题仍然复现。

更新2

补充TaskPage.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:vm="clr-namespace:SomeProject.ViewModels"
             x:Class="SomeProject.Views.Task.TaskPage"
             Title="{Binding TaskTitle}">
    <ContentPage.Content>
        <RefreshView x:DataType="vm:TaskViewModel"
                     Command="{Binding LoadTaskCommand}"
                     IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
            <StackLayout>
                <Label Text="{Binding Description}" />
            </StackLayout>
        </RefreshView>
    </ContentPage.Content>
</ContentPage>

以及TaskPage.xaml.cs代码:

public partial class TaskPage : ContentPage
{
    TaskViewModel _viewModel;
    public TaskPage()
    {
        InitializeComponent();
        BindingContext = _viewModel = new TaskViewModel();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        _viewModel.OnAppearing();
    }
}

更新3

补充路由注册代码:

public AppShell()
{
    InitializeComponent();
    Routing.RegisterRoute(nameof(CategoriesView), typeof(CategoriesView));
    Routing.RegisterRoute(nameof(TasksPage), typeof(TasksPage));
    Routing.RegisterRoute(nameof(TaskPage), typeof(TaskPage));
}
修复方案

问题原因

所有ViewModel的OnAppearing方法仅将IsBusy属性设为true,但未触发对应的加载命令,导致IsBusy始终为true。由于页面中RefreshView的IsRefreshing属性与IsBusy双向绑定,RefreshView会一直处于刷新状态,拦截所有用户交互事件,最终导致导航返回按钮点击无响应。

修复步骤

  1. 修改CategoriesViewModel的OnAppearing方法,触发分类加载命令,保证加载完成后IsBusy会被重置为false:
public void OnAppearing()
{
    IsBusy = true;
    LoadCategoriesCommand.Execute(null);
}
  1. 修改TasksViewModel的OnAppearing方法,触发任务列表加载命令:
public void OnAppearing()
{
    IsBusy = true;
    LoadTasksCommand.Execute(null);
}
  1. 修改TaskViewModel的OnAppearing方法,触发任务详情加载命令:
public void OnAppearing()
{
    IsBusy = true;
    LoadTaskCommand.Execute(null);
}

验证与额外检查

  • 若暂时不需要进入页面自动刷新,可直接注释掉OnAppearing中设置IsBusy = true的代码,或临时移除TaskPage中的RefreshView,即可快速验证返回功能是否恢复。
  • 请确认BaseViewModel中的IsBusy属性正确实现了INotifyPropertyChanged属性变更通知,否则RefreshView无法感知IsBusy的状态变更,也会导致刷新状态卡住。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 05:15:00