【问题标题】:Defining routes for and navigating to pages beyond those specified in AppShell.xaml为 AppShell.xaml 中指定的页面之外的页面定义路由并导航到这些页面
【发布时间】:2021-07-02 14:46:23
【问题描述】:

我有一个 Xamarin shell 应用程序,在 AppShell.xaml 中定义了以下内容:

   <TabBar Route="tabs" x:Name="tbTabs">
        <Tab Title="Home"
             Route="home"             
             Style="{StaticResource BaseStyle}"
             Icon="home.png" >
            <ShellContent>
                <views:LandingPage />
            </ShellContent>
        </Tab>
        <Tab Title="Services"
             Route="service"             
             Style="{StaticResource BaseStyle}"
             Icon="service.png">
            <ShellContent>
                <views:ServicesPage/>
            </ShellContent>
        </Tab>
   </TabBar>

我的目标是通过单击主页上的图标转到列出应用收到的通知历史记录的页面,从主页导航到在 shell 结构之外定义的页面。单击列表中的通知,将转到该通知的详细信息页面。

首页 通知历史 通知详情

在 AppShell.xaml 中未定义为 shell 一部分的页面的路由在 AppShell.xaml.cs 中定义为:

Routing.RegisterRoute("notificationhistorypage", typeof(NotificationHistoryPage));
Routing.RegisterRoute("notificationdetailspage", typeof(NotificationDetailsPage));

问题是,当我点击通知历史页面上的通知时,导航动画会在导航到详细信息页面之前显示导航回主页。当我从详细信息页面导航回来时,它不会返回通知列表,而是直接转到主页。

我已经尝试了很多方法来解决这个问题。所有似乎都表现出相同的行为,或导致异常。

从我在https://github.com/xamarin/Xamarin.Forms/issues/5790 读到的内容看来,应该将路由定义为更合乎逻辑:

Routing.RegisterRoute("//tabs/home/notificationhistorypage", typeof(NotificationHistoryPage));
Routing.RegisterRoute("//tabs/home/notificationhistorypage/notificationdetailspage", typeof(NotificationDetailsPage));

不幸的是,这会在运行时产生异常:

“无法找出路由:/notificationhistorypage\n参数名称:uri”

为了从主页导航到通知历史记录页面,我尝试了以下方法:

await Shell.Current.GoToAsync("/notificationhistorypage");

await Shell.Current.GoToAsync("//tabs/home/notificationhistorypage");

为了从通知历史页面导航到通知详细信息页面,我尝试了以下方法:

await Shell.Current.GoToAsync($"/notificationdetailspage?itemid={notification.ID}", true);

await Shell.Current.GoToAsync($"///tabs/home/notificationhistorypage/notificationdetailspage?itemid={notification.ID}", true);

await Shell.Current.Navigation.PushAsync(new NotificationDetailsPage(notification.ID.ToString()));

结果总是一样的。出于某种原因,导航历史页面总是在推送通知详细信息页面之前从导航堆栈中弹出。我正在尝试做的事情是否可能,或者我应该摆脱外壳,并使用旧导航来完成这一切?

【问题讨论】:

    标签: c# xaml xamarin.forms xamarin.forms.shell


    【解决方案1】:

    不幸的是,这个问题是由我在 AppShell.xaml.cs 的 OnNavigating() 中的一些代码引起的,当按下某些选项卡时,它会弹出导航堆栈的所有内容。当 args.Source == ShellNavigationSource.Push 时,我没有意识到我需要阻止该代码运行。我还应该提到,为了使我的代码修复工作,我必须从 4.8 更新到 Xamarin.Forms 5.0。

    【讨论】:

      【解决方案2】:

      问题是,当我单击通知历史记录页面上的通知时,导航动画会在导航到详细信息页面之前显示导航回主页。当我从详细信息页面导航回来时,它不会返回通知列表,而是直接转到主页。

      AppShell.xaml 中的弹出项:

      <FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
          <ShellContent
              Title="About"
              ContentTemplate="{DataTemplate local:AboutPage}"
              Icon="tab_about.png"
              Route="AboutPage" />
          <ShellContent
              Title="Browse"
              ContentTemplate="{DataTemplate local:ItemsPage}"
              Icon="tab_feed.png" />
          <ShellContent
              Title="Main"
              ContentTemplate="{DataTemplate local:MainPage}"
              Icon="tab_feed.png" />
      </FlyoutItem>
      

      Shell子类构造函数中的Routing.RegisterRoute方法:

       public AppShell()
          {
              InitializeComponent();
              
              Routing.RegisterRoute("NotificationHistory", typeof(NotificationHistory));
              Routing.RegisterRoute("NotificationDetails", typeof(NotificationDetails));
          }
      

      从 MainPage 导航到 NotificationHistory 页面。

      private async void btn1_Clicked(object sender, EventArgs e)
          {
              await Shell.Current.GoToAsync("NotificationHistory");
          }
      

      从 NotificationHistory 导航到 NotificationDetailspage。

       async void OnItemSelected(Item item)
          {
              if (item == null)
                  return;
      
              // This will push the ItemDetailPage onto the navigation stack
              await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={item.Id}");
          }
      

      通知历史页面:

        <StackLayout>
             
                  <CollectionView
                      x:Name="ItemsListView"
                      ItemsSource="{Binding Items}"
                      SelectionMode="None">
                      <CollectionView.ItemTemplate>
                          <DataTemplate>
                              <StackLayout Padding="10" x:DataType="model:Item">
                                  <Label
                                      FontSize="16"
                                      LineBreakMode="NoWrap"
                                      Text="{Binding Text}" />
                                  <Label
                                      FontSize="13"
                                      LineBreakMode="NoWrap"
                                      Text="{Binding Description}" />
                                  <StackLayout.GestureRecognizers>
                                      <TapGestureRecognizer
                                          Command="{Binding Source={RelativeSource AncestorType={x:Type local:HistoryViewModel}}, Path=ItemTapped}"
                                          CommandParameter="{Binding .}"
                                          NumberOfTapsRequired="1" />
                                  </StackLayout.GestureRecognizers>
                              </StackLayout>
                          </DataTemplate>
                      </CollectionView.ItemTemplate>
                  </CollectionView>
             
          </StackLayout>
      
       public partial class NotificationHistory : ContentPage
      {
          HistoryViewModel _viewModel;
          public NotificationHistory()
          {
              InitializeComponent();
              BindingContext = _viewModel = new HistoryViewModel();
          }
      
      }
      public class HistoryViewModel : BaseViewModel
      {  
          public   ObservableCollection<Item> Items { get; set; }    
          public Command<Item> ItemTapped { get; }
          public HistoryViewModel()
          {
              Title = "history";
              Items = new ObservableCollection<Item>()
              {
                  new Item { Id ="1" , Text = "First item", Description="This is an item description." },
                  new Item { Id = "2", Text = "Second item", Description="This is an item description." },
                  new Item { Id = "3", Text = "Third item", Description="This is an item description." },
                  new Item { Id = "4", Text = "Fourth item", Description="This is an item description." },
                  new Item { Id = "5", Text = "Fifth item", Description="This is an item description." },
                  new Item { Id = "6", Text = "Sixth item", Description="This is an item description." }
              };
      
              ItemTapped = new Command<Item>(OnItemSelected);
          }
          async void OnItemSelected(Item item)
          {
              if (item == null)
                  return;
      
              // This will push the ItemDetailPage onto the navigation stack
              await Shell.Current.GoToAsync($"{nameof(NotificationDetails)}?{nameof(DetailViewModel.ItemId)}={item.Id}");
          }
      }
      

      通知详情页面:

        <StackLayout Padding="15" Spacing="20">
                  <Label FontSize="Medium" Text="Text:" />
                  <Label FontSize="Small" Text="{Binding Text}" />
                  <Label FontSize="Medium" Text="Description:" />
                  <Label FontSize="Small" Text="{Binding Description}" />
              </StackLayout>
      
       public partial class NotificationDetails : ContentPage
      {
          public NotificationDetails()
          {
              InitializeComponent();
              this.BindingContext =new DetailViewModel();
          }
      }
      [QueryProperty(nameof(ItemId), nameof(ItemId))]
      public class DetailViewModel : BaseViewModel
      {
          private string itemId;
          private string text;
          private string description;
          public string Id { get; set; }
      
          public string Text
          {
              get => text;
              set => SetProperty(ref text, value);
          }
      
          public string Description
          {
              get => description;
              set => SetProperty(ref description, value);
          }
      
          public string ItemId
          {
              get
              {
                  return itemId;
              }
              set
              {
                  itemId = value;
                  LoadItemId(value);
              }
          }
      
          public async void LoadItemId(string itemId)
          {
              HistoryViewModel items = new HistoryViewModel();
              try
              {
                  var item = items.Items.Where(x => x.Id == itemId).FirstOrDefault();
                  if(item!=null)
                  {
                      Id = item.Id;
                      Text = item.Text;
                      Description = item.Description;
                  }
                
              }
              catch (Exception)
              {
                  Debug.WriteLine("Failed to Load Item");
              }
          }
      }
      

      BaseViewModel 是实现 INotifyPropertyChanged 的​​类

       public class BaseViewModel : INotifyPropertyChanged
      {
         
          string title = string.Empty;
          public string Title
          {
              get { return title; }
              set { SetProperty(ref title, value); }
          }
      
          protected bool SetProperty<T>(ref T backingStore, T value,
              [CallerMemberName] string propertyName = "",
              Action onChanged = null)
          {
              if (EqualityComparer<T>.Default.Equals(backingStore, value))
                  return false;
      
              backingStore = value;
              onChanged?.Invoke();
              OnPropertyChanged(propertyName);
              return true;
          }
      
          #region INotifyPropertyChanged
          public event PropertyChangedEventHandler PropertyChanged;
          protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
          {
              var changed = PropertyChanged;
              if (changed == null)
                  return;
      
              changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
          }
          #endregion
      }
      

      【讨论】:

      • 谢谢。这可以正常工作。我没有直接解决我的问题,因为这个问题是由与路由无关的东西引起的,但它让我深入挖掘以意识到它是什么。 (见我的回答)
      • @MSibalik 很高兴听到您解决了您的问题,如果我的回复对您有帮助,您可以标记它或标记自己回复。这对面临同样问题的其他人是有益的。
      猜你喜欢
      • 2023-03-24
      • 2021-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-05
      • 2018-10-18
      • 2019-06-05
      • 1970-01-01
      相关资源
      最近更新 更多