Sunday, 9 July 2017

Navigation 04 Industrialization

Previously we explored navigation the use of the SystemNavigationManager and the back shell button, which was pretty cool, but doing that on every page seems ridiculous, because it is; in software development if you're ever copy and pasting there's probably a better way. Now it's easy to jump to conclusions like I first did and create a MyPage base class that inherits from Page and add all of your back logic there, but as an awesome MVA demonstrated there is no need for that, because we can get everything we need from our app.xaml.cs page.

first let's drop the back button from our second page and remove all the back navigation from it. 

<Page
    x:Class="pc.NavigateExample.SecondPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock>Second Page</TextBlock>
            <TextBox x:Name="output_TextBox" PlaceholderText="Parameter received" />
        </StackPanel>
    </Grid>
</Page>

above we have our SecondPage xaml without the back button we created. next we have our trimmed down codebehind for our second page

using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace pc.NavigateExample
{
    public sealed partial class SecondPage : Page
    {
        public SecondPage() { this.InitializeComponent(); }

        protected override void OnNavigatedTo(NavigationEventArgs e) {
            var parameter = e.Parameter as string;

            if (parameter != null)
                output_TextBox.Text = parameter;
        }
    }
}


notice how much simpler that looks.

Now  lets do the heavy lifting, open up the App.xaml.cs class and go to your OnLaunched(LaunchActivatedEventArgs e) method. In this method go to the very end it and create and subscribe to the "SystemNavigationManager.GetForCurrentView().BackRequested".

protected override void OnLaunched(LaunchActivatedEventArgs e) {
    // removed for brevity
    SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested;
}

public event EventHandler<BackRequestedEventArgs> OnBackRequested;

private void App_BackRequested(object sender, BackRequestedEventArgs e)
{
    //Allow objects to intercept back event
    OnBackRequested?.Invoke(this, e);

    //ensure event is not handled
    if (!e.Handled) {
        var frame = Window.Current.Content as Frame;

        if (frame.CanGoBack) {
            frame.GoBack();

            //ensure that user is not navigated to the previous application
            e.Handled = true;
        }
    }
}

above we subscribe to the BackRequested event, then in side the App_BackRequested handler we navigate our application to the previous page. However we created an OnBackRequested eventhandler that we can use to intercept the App_BackRequested event logic to implement our own and set the e.handled to true.

now that's great but we still don't see our Shell back button so again in our onLaunched method of the app.xaml.cs page let's add a small handler to deal with our shell back button

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    // removed for brevity
    // handle system wide back commands
    SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested;

    //hide or show shell back button if frame can go back
    rootFrame.Navigated += RootFrame_Navigated;

}

and the actual hide/show logic

private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
                ((Frame)sender).CanGoBack ?
                    AppViewBackButtonVisibility.Visible :
                    AppViewBackButtonVisibility.Collapsed;

}

is pretty simple, if you can go back, show the shell button  if you can't then hide the shell back button. this check fires every time the root frame navigates to a new page.