【问题标题】:Navigation to route does not take place导航到路线不发生
【发布时间】:2021-10-11 15:18:47
【问题描述】:

我在 Visual Studio 2019 中使用 Xamarin.Forms Shell 创建了一个新应用程序。我正在尝试让导航正常工作,但我不确定为什么在调用 Shell.Current.GoToAsync 时没有任何反应。

使用 Shell Tab Bar 模板自带的 UWP 应用进行测试,我在共享库中有以下app.xaml.cs

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
            MainPage = new AppShell();
        }
    }

AppShell XAML 和代码隐藏中,我为我的登录路径创建了一个ShellItem,应用程序在该路径中成功启动。我也有一个TabBar,其中有一条通往MangaLibraryPage 的路线。我也注册了我的路线。

AppShell.xaml

<?xml version="1.0" encoding="UTF-8"?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms" 
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MangaLater.Views"
       Title="MangaLater"
       x:Class="MangaLater.AppShell">

    <ShellItem Route="login" FlyoutItemIsVisible="False">
        <ShellContent ContentTemplate="{DataTemplate local:LoginPage}" />
    </ShellItem>
        
    <TabBar>
        <ShellContent Route="MangaLibraryPage" ContentTemplate="{DataTemplate local:MangaLibraryPage}" />
    </TabBar>


</Shell>

AppShell.cs

    public partial class AppShell : Xamarin.Forms.Shell
    {
        public AppShell()
        {
            InitializeComponent();
            Routing.RegisterRoute("login", typeof(LoginPage));
            Routing.RegisterRoute(nameof(MangaLibraryPage), typeof(MangaLibraryPage));
        }
    }

LoginPage 非常简单 - 它只有一个带有标签和按钮的网格。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                     x:Class="MangaLater.Views.LoginPage"
                     xmlns:vm="clr-namespace:MangaLater.Views"
                     Shell.FlyoutBehavior="Disabled"
                     Shell.NavBarIsVisible="False">
    
    <ContentPage.Content>
        <Grid Padding="0, 60, 0, 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"></ColumnDefinition>
                <ColumnDefinition Width="4*"></ColumnDefinition>
                <ColumnDefinition Width="1*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <StackLayout x:Name="LoginStackLayout"
                         Grid.Column="1">
                <Label Text="Login"
                       HorizontalOptions="Center"
                       Margin="0, 0, 0, 24"
                       FontAttributes="Bold"
                       FontSize="36"></Label>
                <Button Text="Sign in with Google" 
                            Grid.ColumnSpan="2"
                            BackgroundColor="#4285F4"
                            TextColor="White"
                            FontAttributes="Bold"
                            Margin="16, 8"></Button>
            </StackLayout>
        </Grid>
    </ContentPage.Content>
</ContentPage>

我已将视图模型全部连接起来并与导航一起使用,但最近发生的一些变化破坏了它,所以我一直在慢慢删除代码,试图找出导致导航不起作用的原因。

此时我没有视图模型,我所拥有的只是试图强制导航到新页面的代码隐藏。

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class LoginPage : ContentPage
    {
        public LoginPage()
        {
            InitializeComponent();
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();
            await Shell.Current.GoToAsync($"//{nameof(MangaLibraryPage)}");
        }
    }

当应用程序启动时,OnAppearing 会在 LoginPage 上调用两次。我上面的代码中没有任何地方明确调用,所以我不确定为什么OnAppearing 被多次调用。 GoToAsync 调用被命中并且不会抛出任何异常,但导航永远不会发生。该应用程序停留在Loginpage。这里有什么突出我做错了吗? MangaLibraryPay 的构造函数永远不会被命中,表明导航永远不会实例化它。

【问题讨论】:

  • 作为测试,制作一个按钮来执行 GoToAsync。注释掉 OnAppearing 中的行。在页面的 OnAppearing 中执行此操作可能存在一些问题。如果它在按钮上起作用,那么我们就知道 shell 导航是正确的。
  • 刚刚试了一下 - 添加了 OnClick 事件处理程序,并且使用相同的 GoToAsync 调用的导航什么也没做。断点被击中,我可以跳过代码,但导航本质上是一个无操作
  • 更新 - 我没有删除 OnAppearing 覆盖。一旦我删除了该方法,OnClick 处理程序确实会导航。它似乎与OnAppearing 覆盖有关

标签: c# xamarin.forms uwp


【解决方案1】:

当我最初在原始帖子中运行最小示例时,我不确定为什么会出现这种情况,但确实如此。我能够通过使用OnClick 处理程序使其工作。一旦我删除了OnClick 处理程序并将代码放回OnAppearing 方法,它也能正常工作。不是 100% 清楚为什么要删除 OnAppearing 方法,将其放入 OnClick 然后删除 OnClick 并重新覆盖 OnAppearing 修复它。

一旦我让它工作了,我就恢复了我的所有原始代码并再次经历了同样的行为。我的原始代码有一个 ContentPage 的子类,如下所示:

    public abstract class MangaContentPage<TViewModel> : ContentPage where TViewModel : BaseViewModel
    {
        protected TViewModel ViewModel { get; private set; }

        protected IServiceProvider ServiceProvider { get; private set; }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            await BeforeViewModelInitialized();

            this.ServiceProvider = Application.Current.GetServiceProvider();
            this.ViewModel = this.ServiceProvider.GetRequiredService<TViewModel>();

            await this.ViewModel.Initialize();

            this.BindingContext = this.ViewModel;

            await OnViewModelInitialized();
        }

        protected virtual Task BeforeViewModelInitialized()
        {
            return Task.CompletedTask;
        }

        protected virtual Task OnViewModelInitialized()
        {
            return Task.CompletedTask;
        }
    }

OnViewModelInitialized 方法随后在我的子类中实现。

public partial class LoginPage : MangaContentPage<LoginPageViewModel>
    {
        public LoginPage()
        {
            InitializeComponent();
        }

        protected override Task BeforeViewModelInitialized()
        {
            var authService = base.ServiceProvider.GetRequiredService<IAuthorizationService>();
            UserAuthenticatedResponse authResponse = authService.IsUserAuthenticated();

            if (authResponse.IsAuthenticated)
            {
                return Shell.Current.GoToAsync($"//{nameof(MangaLibraryPage)}");
            }

            return base.BeforeViewModelInitialized();
        }
    }

导航在这种方法中停止工作。我能够通过使用Dispatcher 包装对OnViewModelInitialized() 的调用并强制它在主线程上运行来解决它。看着来自@ToolmakerSteve 的建议的OnClick 处理程序工作正常,而OnAppearing 工作没有发生上游异步让我想知道这是否在后台线程中丢失并且永远不会回到主UI 线程。事实证明一定是这样的。

我的MangaContentPage&lt;TViewModel&gt; 被修改为Dispatcher 并解决了这个问题。

    public abstract class MangaContentPage<TViewModel> : ContentPage where TViewModel : BaseViewModel
    {
        protected TViewModel ViewModel { get; private set; }

        protected IServiceProvider ServiceProvider { get; private set; }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            Dispatcher.BeginInvokeOnMainThread(() => this.BeforeViewModelInitialized());

            this.ServiceProvider = Application.Current.GetServiceProvider();
            this.ViewModel = this.ServiceProvider.GetRequiredService<TViewModel>();

            await this.ViewModel.Initialize();

            this.BindingContext = this.ViewModel;

            Dispatcher.BeginInvokeOnMainThread(async () => await OnViewModelInitialized());
        }

        protected virtual Task BeforeViewModelInitialized()
        {
            return Task.CompletedTask;
        }

        protected virtual Task OnViewModelInitialized()
        {
            return Task.CompletedTask;
        }
    }

现在我的基类可以通过 DI 解析视图模型及其依赖关系,初始化它们并让页面能够在执行所有繁重的 ViewModel 工作之前中止并导航到新页面。

【讨论】:

  • “如果这在后台线程中丢失了” - 对;所有影响 UI 的更改都应在主线程上完成。我不知道为什么“有时”的东西在后台线程上起作用,但我自己也经历过。我还遇到了在OnAppearing 中运行的其他问题。我认为 OnAppearing,即使它在主线程上,也处于某些更改被覆盖/丢失的状态,因为 Xamarin 在 OnAppearing 之后运行的代码(为了实际显示页面)。
  • 一开始我没有提到所有这些,因为如果这不是问题,我不想让你走上死胡同。我很高兴我没有这样做,因为您编写的调度它的代码比我一直使用的要简单。我启动了一个一次性计时器,以获得延迟,并在计时器滴答上调用线程。很高兴看到 BeginInvoke + async/await 足以将它排队到一个好时机! (待定更复杂的情况是否仍需要强制延迟。)
  • 仅供参考,如果确实想要延迟,Device.StartTimer 是一个不错的选择。 In this answer我展示了我使用的模式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-11
  • 2016-12-02
  • 1970-01-01
  • 2016-09-22
  • 2018-08-10
相关资源
最近更新 更多