Windows 8 Metro Style Apps – Layout and Orientation Management with XAML

Posted by: | Technical |

This post builds on my previous post around implementing navigation with xaml, it’s recommended you have a quick look at that one before continuing.

Your application is likely to have different views, however you will want to have a consistent way of handling orientation and layout changes across your application. Within the current set of templates that come with the Windows 8 Developer Preview, the code to handle and respond to layout and orientation changes are duplicated within each page.

The idea here is to have a single set of code which can handle the orientation and layout change using the same visual state management approach but then trigger the visual state in the Page of the current Frame which is used to display the current page content.

As a recap from my last post, we have a Frame to host the content declared on MainPage.xaml, when we navigate using the ContentFrame below, this will set the content of the frame.

        <!-- content -->
        <Frame x:Name="ContentFrame" Source="XamlStart.Views.HomeView">
            <Frame.ContentTransitions>
                <TransitionCollection>
                    <EntranceThemeTransition FromHorizontalOffset="200" />
                </TransitionCollection>
            </Frame.ContentTransitions>
        </Frame>

Within MainPage.xaml.cs you can wire up handlers for the layout and orientation changes as follows using the Loaded event handler. Note also that we ensure that we wire up the Navigated event handler for the ContentFrame as we will want to use this handler to ensure the integrity of orientation/layout as the user navigates through the application.


 public MainPage()
        {
            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

  void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            DisplayProperties.OrientationChanged += Page_OrientationChanged;
            ApplicationLayout.GetForCurrentView().LayoutChanged += Page_LayoutChanged;
            ContentFrame.Navigated += new Windows.UI.Xaml.Navigation.NavigatedEventHandler(ContentFrame_Navigated);
        }

Then you can have a single set of code in MainPage.xaml.cs which will trigger visual state changes on content currently displayed within the ContentFrame. Again, it’s important that we also ensure we explicitly enforce the layout/orientation when a page is navigated to, as this will ensure it’s displayed correctly on load.


        private void Page_LayoutChanged(object sender, ApplicationLayoutChangedEventArgs e)
        {
            SetCurrentViewState(ContentFrame.Content as Control);
        }

        private void Page_OrientationChanged(object sender)
        {
            SetCurrentViewState(ContentFrame.Content as Control);
        }

        void ContentFrame_Navigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
        {
            SetCurrentViewState(e.Content as Control);
        }

        private void SetCurrentViewState(Control viewStateAwareControl)
        {
            VisualStateManager.GoToState(viewStateAwareControl, this.GetViewState(), false);
        }

        private String GetViewState()
        {
            var orientation = DisplayProperties.CurrentOrientation;
            if (orientation == DisplayOrientations.Portrait ||
                orientation == DisplayOrientations.PortraitFlipped) return "Portrait";
            var layout = ApplicationLayout.Value;
            if (layout == ApplicationLayoutState.Filled) return "Fill";
            if (layout == ApplicationLayoutState.Snapped) return "Snapped";
            return "Full";
        }

All done, we are now free to respond to the orientation and layout changes as necessary within each page of our application.

For example, here is an updated HomeView.xaml page which is loaded into the ContentFrame on our MainPage.xaml by default. Note how it declares a visual state manager with states corresponding to various core layout/orientation states.

In this instance it’s just adjusting margins for my testing so I can see it’s working but you would likely be doing something a little more complex, like layout out content differently or hiding/showing alternative controls within each page.

 <Grid x:Name="LayoutRoot">

        <!-- layout states-->
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="OrientationStates">
                <VisualState x:Name="Full"/>
                <VisualState x:Name="Fill">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="ContentRoot">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Thickness>40,40,40,40</Thickness>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Portrait">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="ContentRoot">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Thickness>20,20,20,20</Thickness>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Snapped">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="ContentRoot">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Thickness>20,20,20,20</Thickness>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Grid x:Name="ContentRoot">
            <StackPanel>
                <TextBlock Text="Home View" FontSize="50"/>
                <Button x:Name="AboutUs" Content="About Us"  Click="About_Click"  />
            </StackPanel>
        </Grid>

    </Grid>

If this approach is not used, then there is required duplication of much of the code above within each page of your application. This approach centralises the plumbing code in one place and leaves your pages free to respond to the visual state changes as required.