0

How to switch tabs programmatically, as if the user had clicked the tabbar's tab button instead of using GoToAsync()

I have a Maui app (but the same in Xamarin Forms) with an App Shell <TabBar with 4 pages Each TabPage works independently, but I push new pages onto (Tab 1's) stack via

Shell.Current.Navigation.PushAsync(new ChildPage() );

(Further use case: The stack is dynamic, in that "ChildPage1" is not always in 1 place, and may be used several times in the stack, therefore I cannot use an absolute route path)

If I then use the navigation tab buttons, I can switch to a new tab and back, and it retains each Tab's stack (e.g. switching from Tab 2 to Tab 1 below would show "Child Page 2" [Tab 1] NewPage 1 Child Page 1 Child Page 2

[Tab 2] NewPage 2

[Tab 3] etc..

I need to be able to do this programmatically instead of clicking the tab button The only Navigation I have seen so far is

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

which isn't what I want

Is there a way to do this? Is this even exposed, or do I have to manually track the Navigated() event for instance and GoToAsync the full remembered path?

sample app portions AppShell.xaml relevant portions

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="ShellNavTest.AppShell"
.. xmlns removed due to filters
    xmlns:local="clr-namespace:ShellNavTest"
    Shell.FlyoutBehavior="Disabled">
<!--
    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" /> -->
    <TabBar>
        <ShellContent Title="Page1" Route="NewPage1" ContentTemplate="{DataTemplate local:NewPage1}" />
        <ShellContent Title="Page2" Route="NewPage2" ContentTemplate="{DataTemplate local:NewPage2}" />
        <ShellContent Title="Page3" Route="NewPage3" ContentTemplate="{DataTemplate local:NewPage3}" />
        <ShellContent Title="Page4" Route="NewPage4" ContentTemplate="{DataTemplate local:NewPage4}" />
    </TabBar>
</Shell>

NewPage1.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
.. xmlns removed due to filters
             x:Class="ShellNavTest.NewPage1"
             Title="NewPage1"
             Shell.TabBarIsVisible="True" >
    <VerticalStackLayout>
        <Label 
            Text="Page 1"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
        <Button x:Name="btnCreateChild" Text="Create Child" Clicked="btnCreateChild_Clicked" Margin="20" />
    </VerticalStackLayout>
</ContentPage>

NewPage1.xaml.cs

namespace ShellNavTest;

public partial class NewPage1 : ContentPage
{
    public NewPage1()
    {
        InitializeComponent();
    }

    private void btnCreateChild_Clicked(object sender, EventArgs e)
    {
        Shell.Current.Navigation.PushAsync(new ChildPage() );
    }
}

ChildPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
.. xmlns removed due to filters
             x:Class="ShellNavTest.ChildPage"
             Title="ChildPage"
             Shell.TabBarIsVisible="True" >
    <VerticalStackLayout BackgroundColor="Cyan">
        <Label 
            Text="this is a child page on navigation stack but not in routes"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

NewPage2.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
.. xmlns removed due to filters
             x:Class="ShellNavTest.NewPage2"
             Title="NewPage2"
             Shell.TabBarIsVisible="True" >
    <VerticalStackLayout>
        <Label 
            Text="Page 2"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
        <Button x:Name="btnGotoPage1" Text="Goto Page 1" Clicked="btnGotoPage1_Clicked" Margin="20" />
    </VerticalStackLayout>
</ContentPage>

NewPage2.xaml.cs

namespace ShellNavTest;

public partial class NewPage2 : ContentPage
{
    public NewPage2()
    {
        InitializeComponent();
    }

    private async void btnGotoPage1_Clicked(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync("//NewPage1");
    }

}
DHNZ
  • 11
  • 4
  • Shell GoToAsync can only navigate between shell pages. I didn't carefully read what you wrote, but the general rule is GoToAsync to the correct TabbedPage, then have that TabbedPage do PushAsync to the desired (sub)page within itself. It wouldn't be meaningful to use Shell navigation to go directly to a TabbedPage's subpage; Shell doesn't access what's inside the TabbedPage. OR stop using TabbedPages, do it all via Shell: see [Bottom and Top tabs](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/tabs?view=net-maui-7.0#bottom-and-top-tabs). OR NavigationPage instead of AppShell. – ToolmakerSteve Feb 23 '23 at 23:35
  • When more than one ShellContent object is present in a Tab object, a top tab bar is added to the bottom tab, through which the ContentPage objects are navigable by using the `DataTemplate` markup extension to set the ContentTemplate property of each `ShellContent` object to a ContentPage object.For more details, please refer to:https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/tabs?view=net-maui-7.0#bottom-tabs – Alexandar May - MSFT Feb 24 '23 at 09:32
  • Yesterday, a guy posted a question about his Android exceptions and using Shell.Current.Navigation for navigation. Unfortunately he deleted his own question. If Shell navigation with URL falls into the category "isn't what I want", I strongly recommend against using Shell at all. Either use Shell or NavigationPage. – H.A.H. Feb 24 '23 at 11:03
  • I just want to mimic clicking the tab. I'm happy with the Shell navigation otherwise – DHNZ Feb 25 '23 at 23:24

1 Answers1

1

I solved this by x:name -ing the TabBar and encompassing the ShellContent in named tabs, then setting the tabbar's CurrentItem specifically

AppShell.xaml

    <TabBar x:Name="shelltabbar">
        <Tab x:Name="shelltab_0" Title="Page1">
        <ShellContent Title="Page1" Route="NewPage1" ContentTemplate="{DataTemplate local:NewPage1}" />
        </Tab>
        <Tab x:Name="shelltab_1" Title="Page2">
            <ShellContent Title="Page2" Route="NewPage2" ContentTemplate="{DataTemplate local:NewPage2}" />
        </Tab>
        <Tab x:Name="shelltab_2" Title="Page3">
            <ShellContent Title="Page3" Route="NewPage3" ContentTemplate="{DataTemplate local:NewPage3}" />
        </Tab>
        <Tab x:Name="shelltab_3" Title="Page4">
            <ShellContent Title="Page4" Route="NewPage4" ContentTemplate="{DataTemplate local:NewPage4}" />
        </Tab>
    </TabBar>

AppShell.xaml.cs

namespace ShellNavTest;

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
    }
    public void SwitchtoTab(int tabIndex)
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            switch (tabIndex)
            {
                case 0: shelltabbar.CurrentItem = shelltab_0; break;
                case 1: shelltabbar.CurrentItem = shelltab_1; break;
                case 2: shelltabbar.CurrentItem = shelltab_2; break;
                case 3: shelltabbar.CurrentItem = shelltab_3; break;
            };
        });
    }

}

ChildPage.xaml.cs

    private void btnSwitchToTab1_Clicked(object sender, EventArgs e)
    {
        ((AppShell)App.Current.MainPage).SwitchtoTab(0);
    }
DHNZ
  • 11
  • 4