Поделиться через


Designing your app with XAML: Orientation

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Design your C# and XAML to respond appropriately when the orientation changes.

With more and more apps being run on tablet devices, and some smaller Windows 8 tablets using portrait mode as their default orientation, it's vitally important that your app looks good and functions perfectly in all orientations. If you're writing your app in XAML with either C#, Visual Basic, or C++, the good news is that XAML can help. If you're using JavaScript, you'll find that CSS is a great tool. Let's look at some XAML-based strategies for marking your app look and work great, regardless of the display size.

The basic question comes down to this: what will your app do when the user rotates their device? There are two options.

  • Everything magically looks good in portrait and landscape mode. If you've carefully (or luckily) set up your app's controls to be contained within a XAML grid, you might find your work is done. If you're using a GridView or Hub control to manage your app, this is often the case. Congratulations!

  • Sometimes an app with a less standard layout might not look so great when the orientation changes. If your app really can't work in multiple orientations, you might want to lock it to a specific format by setting the preferred orientations in the Package.appxmanifest file. Clearly this is not ideal, but if the design is fixed, it might be your only choice.

Supporting both portrait and landscape orientations in your app

Let's assume, though, that you do want to support both orientations. Where do you start?

First, let's trigger a response in our app when the user rotates their device. We can do this by adding a handler that fires when the screen size changes, and a method, like this:

// Add this to page initializer
Window.Current.SizeChanged += Current_SizeChanged;

void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            // Get the new view state
            // Add: using Windows.UI.ViewManagement;
            string CurrentViewState = ApplicationView.GetForCurrentView().Orientation.ToString();
        }

This is analogous to the iOS message didRotateFromInterfaceOrientation. If you want, you can programmatically shuffle around various controls to suit the screen orientation at this point, but as we've seen in Building your app with XAML: the Grid, it's usually better to let XAML reposition the controls. So what do we do instead?

If you have different controls present in both landscape and portrait modes, you can simply hide and reveal them as appropriate, using their Visibility property, like this:

        void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            // Get the new view state
            string CurrentViewState = ApplicationView.GetForCurrentView().Orientation.ToString();

            if (CurrentViewState == "Portrait")
            {
               
                landscapeControl.Visibility = Visibility.Collapsed;
                portraitControl.Visibility = Visibility.Visible;
            }

            if (CurrentViewState == "Landscape")
            {
                portraitControl.Visibility = Visibility.Collapsed;
                landscapeControl.Visibility = Visibility.Visible;
            }
            
        }

Unfortunately this approach doesn't work if you want to use the same control in the two orientations, but with some properties altered. You cannot have two buttons with the same name, and then hide one and reveal the other, because you cannot define two objects with the same name. You can however use one control and change its properties (say the color, or the height and width) programmatically, in the Current_SizeChanged method mentioned previously.

Using XAML Visual State Manager to manage orientation states

You can also achieve this without writing code using the XAML Visual State Manager. The Visual State Manager can change properties (say, color) depending on a specific "state name" (which you provide). In our example, the state name is literally "Landscape" or "Portrait". The downside to using the Visual State Manager is that your XAML quickly gets very large, with duplicated declarations, and so it's a good idea to have everything working in one orientation before trying to get it working in both!

Here's an example that shows how to use the Visual State Manager. First, the before case: a simple piece of XAML that draws three columns across the screen, in blue, red and green.

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <Rectangle Grid.Column="0" Fill="Blue"></Rectangle>
        <Rectangle Grid.Column="1" Fill="Red"></Rectangle>
        <Rectangle Grid.Column="2" Fill="Green"></Rectangle>
    </Grid>

When this XAML runs on a device in landscape mode, which is then rotated, you see something like this:

Now, let's say we'd rather have different colors when the view was rotated. That's were the Visual State Manager comes in. First, we add a line of code to the method to trigger the Visual State Manager.

        void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            // Get the new view state
            string CurrentViewState = ApplicationView.GetForCurrentView().Orientation.ToString();

            // Trigger the Visual State Manager
           VisualStateManager.GoToState(this, CurrentViewState, true);
      
        }
    }

This code sends "Portrait" or "Landscape" to the Visual State Manager. Now we update the XAML in our page. The same three columns are still there, but we also define two Visual States. The first, for landscape mode, is empty because this is the default. The second, for portrait mode, is where the changes happen and here we use Storyboard animations to change the color properties.

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ApplicationViewStates">

                <VisualState x:Name="Landscape"/>

                <VisualState x:Name="Portrait">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_1"
          Storyboard.TargetProperty="Fill">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Brown"/>
                        </ObjectAnimationUsingKeyFrames>
                        
                        <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_2"
          Storyboard.TargetProperty="Fill">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Purple"/>
                        </ObjectAnimationUsingKeyFrames>

                        <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_3"
          Storyboard.TargetProperty="Fill">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Orange"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Rectangle x:Name="rect_1" Grid.Column="0" Fill="Blue"></Rectangle>
        <Rectangle x:Name="rect_2" Grid.Column="1" Fill="Red"></Rectangle>
        <Rectangle x:Name="rect_3" Grid.Column="2" Fill="Green"></Rectangle>
    </Grid>

There's a lot more XAML in here, but look for the ObjectAnimationUsingKeyFrames tag as this is where the properties are actually changed (in this case, the fill color). Now when the app is rotated, we see something like this:

Changing other properties

You can use the Visual State Manager to change the Visibility property, if you wanted to avoid hiding controls in code, like this:

                        <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_1"
          Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                        </ObjectAnimationUsingKeyFrames>
                        
                        <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_2"
          Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                        </ObjectAnimationUsingKeyFrames>

You can also easily move elements around by re-defining their position within your grid. When specifying the new cell in the ObjectAnimationUsingKeyFrames tag, use brackets around the property name, like this:

                    <VisualState x:Name="Portrait">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_1"
          Storyboard.TargetProperty="(Grid.Column)">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                            </ObjectAnimationUsingKeyFrames>

                        <ObjectAnimationUsingKeyFrames
          Storyboard.TargetName="rect_1"
          Storyboard.TargetProperty="(Grid.Row)">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                    </VisualState>

Changing properties at launch

You should remember that your app can be launched in portrait mode, rather than be rotated into portrait mode. So, complete the same checks at launch as you do with Current_SizeChanged. Here's an example:

        public MyPage()
        {
            this.InitializeComponent();
            RefreshView(Window.Current.CoreWindow.Bounds.Width); // Use current window size
            Window.Current.SizeChanged += Current_SizeChanged;
        }

        void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            double width = e.Size.Width;
            RefreshView(width);
        }

        private void RefreshView(double width)
        {
            // Trigger the correct view state
            string CurrentViewState = ApplicationView.GetForCurrentView().Orientation.ToString();
            VisualStateManager.GoToState(this, CurrentViewState, false);        
        }

Wrapping up

With these techniques, your app should be able to work in any orientation. In Designing your app with XAML: Resizing, we'll consider what happens when the user changes the size of your display to fit more than one app on the screen at a time

Designing your app with XAML: the Grid

Designing your app with XAML: Resizing

Responsive design 101 for UWP apps

Quickstart: Designing apps for different window sizes

Guidelines for resizing windows to tall and narrow layouts

Quickstart: Defining app layouts (Windows Store apps using JavaScript and HTML)