Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows 10 UWP, NavigationView Update Selected MenuItem on BackNavigation

I am writing an windows 10 UWP app and want to use the NavigationView in combination with the BackRequested event handler to handle back navigation, however "GoBack" doesn't update the selected menu item, this means when I use the backbutton the selected menu item doesn't change. to fix this I have created an ugly foreach loop that selects the MenuItem on back navigation using a tag. This works but I was wondering if there is a more elegant way to do this, GoBack doesn't fire the ItemInvoked or SelectionChanged event so I can't seem to be able to use those.

MainPage.xaml

  <NavigationView x:Name="NavView"
                CompactModeThresholdWidth="1920" ExpandedModeThresholdWidth="1920"
                ItemInvoked="NavView_ItemInvoked"
                SelectionChanged="NavView_SelectionChanged"
                Loaded="NavView_Loaded"
                Canvas.ZIndex="0">

    <NavigationView.MenuItems>
        <NavigationViewItem x:Uid="HomeNavItem" Content="Home" Tag="home">
            <NavigationViewItem.Icon>
                <FontIcon Glyph="&#xE10F;"/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItemSeparator/>
    </NavigationView.MenuItems>

    <NavigationView.HeaderTemplate>
        <DataTemplate>
            <Grid Margin="24,10,0,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <TextBlock Style="{StaticResource TitleTextBlockStyle}"
                       FontSize="28"
                       VerticalAlignment="Center"
                       Text="Welcome"/>
                            </Grid>
        </DataTemplate>
    </NavigationView.HeaderTemplate>

    <Frame x:Name="ContentFrame" Margin="24">
        <Frame.ContentTransitions>
            <TransitionCollection>
                <NavigationThemeTransition/>
            </TransitionCollection>
        </Frame.ContentTransitions>
    </Frame>

</NavigationView>

MainPage.xaml.cs snippet:

        public MainPage()
    {
       this.InitializeComponent();
       // initial page for ContentFrame
       ContentFrame.Navigate(typeof(HomePage));
       ContentFrame.Navigated += MainFrame_Navigated;
       SystemNavigationManager.GetForCurrentView().BackRequested += MainPage_BackRequested;

    }

    private void MainPage_BackRequested(object sender, BackRequestedEventArgs e)
    {
        string tag = null;
        if (!ContentFrame.CanGoBack) return;
        e.Handled = true;
        ContentFrame.GoBack();
        if (ContentFrame.SourcePageType == typeof(HomePage))
        {
            tag = "home";
        }

        foreach (var navViewMenuItem in NavView.MenuItems)
        {
            if (navViewMenuItem is NavigationViewItem item)
            {
                if (item.Tag.Equals(tag)) item.IsSelected = true;
            }
        }               
    }

    private void MainFrame_Navigated(object sender, NavigationEventArgs e)
    {
        SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = ((Frame) sender).CanGoBack
            ? AppViewBackButtonVisibility.Visible
            : AppViewBackButtonVisibility.Collapsed;
    }
like image 534
houba016 Avatar asked Jan 20 '18 22:01

houba016


1 Answers

The NavigationView menu items themselves could do other action than just navigation, hence the control has "no reason" to track the back navigation and update accordingly. What you can do however to set the tag of your MenuItems and use it for both ItemInvoked navigation and for back navigation.

NavigationView menu items XAML would include a Tag that exactly matches the target page type name:

<NavigationView Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <NavigationView.MenuItems>
        <NavigationViewItem Content="First" Tag="FirstPage">
            <NavigationViewItem.Icon>
                <FontIcon Glyph="1" FontFamily="Segoe UI"/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItem Content="Second" Tag="SecondPage">
            <NavigationViewItem.Icon>
                <FontIcon Glyph="2" FontFamily="Segoe UI"/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        ...
    </NavigationView.MenuItems>
</NavigationView>

Now within the MainFrame_Navigated method we can do the following:

//get the Type of the currently displayed page
var pageName = AppFrame.Content.GetType().Name;
//find menu item that has the matching tag
var menuItem = AppNavigationView.MenuItems
                         .OfType<NavigationViewItem>()
                         .Where(item => item.Tag.ToString() == pageName)
                         .First();
//select
AppNavigationView.SelectedItem = menuItem;

You can also use similar approach for ItemInvoked handler:

var invokedMenuItem = sender.MenuItems
                            .OfType<NavigationViewItem>()
                            .Where(item => 
                                 item.Content.ToString() == 
                                 args.InvokedItem.ToString())
                            .First();
var pageTypeName = invokedMenuItem.Tag.ToString();
var pageType = Assembly.GetExecutingAssembly().GetType($"{PageNamespace}.{pageTypeName}");
AppFrame.Navigate(pageType);

Where PageNamespace is a string constant with the name of the namespace where your Pages are stored. You can use nameof to keep it up to date safely, for example:

private const string PageNamespace = nameof(MyApp.Pages);

I have prepared a sample project that demonstrates these suggestions, you can check it out on my GitHub.

like image 163
Martin Zikmund Avatar answered Sep 25 '22 20:09

Martin Zikmund