Back Button and Page Title in Universal Apps

Tags: Apps WinRt Xaml

When developing my app World Places my target was to resue as much code as possible. To achive this goal the problems started at the very beginning when I started to develop the app. Unfortunately you need two different implementations/behaviours for the page title and back button. This is because the Windows Phone platform has a "hardware" back button. Becuause of this you need no back button in your xaml page and the title of the page has to slip to the left.

On an official Microsoft page I read the recommendation to make two different "master" pages for a universal app. One master for phone and one for tablet and desktop. Then you have to place all the content of the app into user controlls and add them to the master page. This is also how the universal hub app template works in visual stundio 2013. I agree this aproach will work but it also produces a lot of "plumbing" code which is required to wire up the app. Because I would like prevent this plumbing code I was looking for a different solution to this problem.

My Solution

In my solution I am using the same xaml fragment for windows and windows phone with two different styles with the same name. The style is called BackButtonStyle and is individual for the platforms and is therefore located in the "Windows" specific and the "Windows Phone" specific project. Doing it this way I can place my xaml pages into the “Shared” project. All I need in the platform specific projects is just the style. Therefore I can reuse my complete xaml page for both platforms.

In the grid containing the back button and page title it is important to set the width of the column containing the back button to auto. This causes the page title to slip to the left if the back button is not present. The second thing to do is to apply the custom “BackButtonStyle” style to the button. The following snippet shows the xaml code I am using in my universal app to show the back button and the page title on each page (The most importand parts in the code are in line 4 and 9):

<!-- Back button and page title -->
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Button x:Name="backButton"
        Style="{StaticResource BackButtonStyle}"
        Command="{Binding NavigationHelper.GoBackCommand, ElementName=homeRoot}"/>

    <TextBlock x:Name="pageTitle"
        x:Uid="Header" 
        Text="World Places"
        Style="{StaticResource HeaderTextBlockStyle}" 
        Grid.Column="1"
        VerticalAlignment="Top"
        IsHitTestVisible="false"
        TextWrapping="NoWrap" />
</Grid>

The “BackButtonStyle” style for the Windows Phone platform is very easy. All it does is to set the visibility of the button to collapsed. Therefore the back button will never show up. This is exactly what we want on the Windows Phone platform. It is shown in the next code snippet:

<Style x:Key="BackButtonStyle" TargetType="Button">
    <Setter Property="Visibility" Value="Collapsed" />
</Style>

The “BackButtonStyle” style for the Windows platform is a little bit more complicated. When the button is not required I wanted the page title to slip to the left. Doing this the content of the page is alwas justified either with the back button or the page title. But I needed a margin between the back button and the page title to get a nice layout when both is shown on the page. To get the margin of the button collapseable when the back button is not required (for example at the home page) I had to modify the template of the button. To do this I copied the template of the button from the generic.xaml file into my custom style and added the required margin to the button into the template.  The following snippet shows the “BackButtonStyle” for the Windows platform (the most important lines in this snippet are lines 2 and 7):

<Style x:Key="BackButtonStyle" TargetType="Button" 
        BasedOn="{StaticResource NavigationBackButtonNormalStyle}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Background="Transparent" x:Name="RootGrid" 
                      Margin="0,0,39,0">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames                  
                                        Storyboard.TargetProperty="Fill"                                                            
                                        Storyboard.TargetName="Ellipse">
                                        <DiscreteObjectKeyFrame KeyTime="0" 
                                            Value="{ThemeResource AppBarItemPointerOverBackgroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetProperty="Foreground"                                                                        
                                        Storyboard.TargetName="Content">
                                        <DiscreteObjectKeyFrame KeyTime="0" 
                                            Value="{ThemeResource AppBarItemPointerOverForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetProperty="Stroke"                                                                     
                                        Storyboard.TargetName="Ellipse">
                                        <DiscreteObjectKeyFrame KeyTime="0" 
                                            Value="{ThemeResource AppBarItemForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetProperty="Fill"                                                                     
                                        Storyboard.TargetName="Ellipse">
                                        <DiscreteObjectKeyFrame KeyTime="0" 
                                            Value="{ThemeResource AppBarItemForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetProperty="Foreground" 
                                        Storyboard.TargetName="Content">
                                        <DiscreteObjectKeyFrame KeyTime="0" 
                                            Value="{ThemeResource AppBarItemPressedForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="RootGrid"                          
                                        Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0" 
                                            Value="Collapsed"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="RootGrid"                                                               
                                        Storyboard.TargetProperty="Margin">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,0"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation Duration="0" To="1" 
                                        Storyboard.TargetProperty="Opacity"                                                       
                                        Storyboard.TargetName="FocusVisualWhite"/>
                                    <DoubleAnimation Duration="0" To="1" 
                                        Storyboard.TargetProperty="Opacity"                                                          
                                        Storyboard.TargetName="FocusVisualBlack"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused"/>
                            <VisualState x:Name="PointerFocused"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid 
                        Height="41" 
                        Width="41">
                        <Ellipse
                            x:Name="Ellipse"
                            Fill="{ThemeResource AppBarItemBackgroundThemeBrush}"
                            Stroke="{ThemeResource AppBarItemForegroundThemeBrush}"
                            StrokeThickness="2"
                            UseLayoutRounding="False" />
                        <ContentPresenter 
                            x:Name="Content" 
                            Foreground="{TemplateBinding Foreground}" 
                            HorizontalAlignment="Stretch" 
                            VerticalAlignment="Stretch">
                            <PathIcon Data="F1 M 17.4126,18L 24.0752,11L 17.6558,11L 8.77931,20.4678L 8.77931,
                                20.5322L 17.6558,30L 24.0752,30L 17.4126,23L 32,23L 32,18L 17.4126,18 Z " />
                        </ContentPresenter>
                    </Grid>
                    <Rectangle x:Name="FocusVisualWhite" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="1.5" 
                                StrokeEndLineCap="Square" Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}" 
                                StrokeDashArray="1,1"/>
                    <Rectangle x:Name="FocusVisualBlack" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="0.5" 
                                StrokeEndLineCap="Square" Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}" 
                                StrokeDashArray="1,1"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Hope this helps you for your universal apps!

No Comments

Add a Comment