共用方式為


Quickstart: Designing apps for different window sizes

[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]

When you design an app to look good and function well at any window size, you must choose if you want to create additional layouts to complement your default full-screen horizontal layout. You can design your app to support a minimum width of 320 pixels instead of the default minimum of 500 pixels, and to switch to a vertical layout when the app is taller than it is wide. These are optional design choices.

See these features in action as part of our App features, start to finish series: Windows Store app UI, start to finish

Objective: After reading this article you will understand how to change the minimum width of an app from 500 pixels to 320 pixels and how to change your design so that the app looks good and functions well at narrow widths. You will also understand how to design a horizontally panning app to change to a vertical layout any time the app height is greater than the width.

Prerequisites

Create your first Windows Store app using C# or Visual Basic

Guidelines for window sizes and scaling to screens

Guidelines for resizing windows to tall and narrow layouts

The sample: Layout for windows that are taller than wide

This article demonstrates the additional design options for tall and narrow layouts by describing how they are implemented in the Layout for windows that are taller than wide sample. This sample is based on the Grid App template in Microsoft Visual Studio. For more information about project templates for Windows Store apps, see C#, VB, and C++ project templates for Windows Store apps.

The Grid App template has three pages:

  • GroupedItemsPage
  • GroupDetailPage
  • ItemDetailPage

You’ll make the same changes to the layout on each page. In this article, we discuss only the changes to the GroupedItemsPage.

Define the minimum width

By default, the minimum width of an app is 500 pixels. The height of the app always fills the screen. The minimum screen height for an app is 768 pixels.

If your app works well at narrow widths, or if multitasking is an important scenario for your app and you want users to keep it on the screen along with other apps, you can change the minimum width to 320 pixels instead of 500. That way, users can resize your app fluidly to any size, from full screen down to 320 pixels wide.

You change the minimum width of the app in the "package.appxmanifest" manifest file. To do this, in Visual Studio, complete the following steps:

  1. Open the "package.appxmanifest" manifest file. The manifest opens automatically in the Manifest Designer.

  2. Open the Application UI tab and find the Minimum width field.

  3. Use the drop-down list to change the minimum width to 320 px.

  4. If you open "package.appxmanifest" in a text editor, you should see the ApplicationView element as a child of the VisualElements element. For example, the new minimum width in the manifest file looks like this:

    <ApplicationView MinWidth="width320" /> 
    

Design the narrow width layout

Now that your app is capable of being resized down to 320 pixels, you need to modify your app so that it is usable and well-designed at narrow widths. The changes include:

  • Smaller font for the header
  • Smaller back button style
  • Narrower left margin
  • Vertical panning instead of horizontal
  1. Decide at what width you want to make these changes. In this sample, we chose 500 pixels as the point at which the app switches to the narrow layout. At any width below 500 pixels, the app uses the narrow layout.

  2. In the GroupedItemsPage.xaml file, find the section where the back button, page title, margins, and text block are laid out. Use this code from the template for the default view, 500 pixels and above. Then define an additional Grid with the smaller, narrow styles. The following XAML shows these two Grids.

    <!-- Back button and page title -->
    <Grid x:Name="defaultNavigation" Visibility="Visible">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <AppBarButton x:Name="backButton" Icon="Back" Height="95" Margin="10,46,10,0"
                      Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}" 
                      Visibility="{Binding IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource Mode=Self}}"
                      AutomationProperties.Name="Back"
                      AutomationProperties.AutomationId="BackButton"
                      AutomationProperties.ItemType="Navigation Button"/>
        <TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                   IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40"/>
        </Grid>
        <Grid x:Name="minimalNavigation" Visibility="Collapsed">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <AppBarButton x:Name="backButtonMinimal" Icon="Back" Height="70" Margin="10,56,10,0" IsCompact="True"
                          Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}" 
                          Visibility="{Binding IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource Mode=Self}}"
                          AutomationProperties.Name="Back"
                          AutomationProperties.AutomationId="BackButton"
                          AutomationProperties.ItemType="Navigation Button" RenderTransformOrigin="0.5,0.5">
                <AppBarButton.RenderTransform>
                    <CompositeTransform ScaleX="0.75" ScaleY="0.75"/>
                </AppBarButton.RenderTransform>
            </AppBarButton>
            <TextBlock x:Name="pageTitleMinimal" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" FontSize="27"
                       IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="20,0,30,40"/>
        </Grid>
    
  3. Change the app to pan vertically instead of horizontally when it is in the narrow layout. To do that, you create a ListView. Here is the XAML for the new narrow layout ListView in the GroupedItemsPage.xaml file.

    <!-- Vertical scrolling list used in small portrait view -->
    <ListView
        x:Name="itemListView"
        AutomationProperties.AutomationId="ItemListView"
        AutomationProperties.Name="Items In Group"
        TabIndex="1"
        Grid.Row="1"
        Visibility="Collapsed"
        Padding="10,0,0,60"
        ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid Margin="6">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">
                        <Image Source="{Binding ImagePath}" Stretch="UniformToFill"/>
                    </Border>
                    <StackPanel Grid.Column="1" Margin="10,0,0,0">
                        <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" MaxHeight="40"/>
                        <TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <Grid Margin="7,7,0,0">
                            <Button
                                AutomationProperties.Name="Group Title"
                                Click="Header_Click"
                                Style="{StaticResource TextBlockButtonStyle}">
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource SubheaderTextBlockStyle}" />
                                    <TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource SubheaderTextBlockStyle}"/>
                                </StackPanel>
                            </Button>
                        </Grid>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
    </ListView>
    

Design the tall layout

Next you need to define the layout for when the app window is taller than it is wide. In this case, you want to switch from a horizontally panning app to a vertically panning app. Everything else, including header size, margins, and back button style, stays the same. These other changes only occur when the user resizes the app to a narrow width, less than 500 pixels.

  • Change the app to pan vertically instead of horizontally when taller than it is wide. To do that, you could create another ListView in the GroupedItemsPage.xaml file, but it would be almost identical to the vertical ListView you created for the narrow layout. The only differences are the Padding values and the DataTemplate element. Instead of creating another ListView, you use the ListView you created previously, but move the DataTemplate details to the Page.Resources section of GroupedItemsPage.xaml. You'll use the DataTemplate later when you specify when to switch between layouts.

    Here is the Page.Resources section of code

    <Page.Resources>
            <common:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
            <x:String x:Key="ChevronGlyph">&#xE26B;</x:String>
            <!--
                Collection of grouped items displayed by this page, bound to a subset
                of the complete item list because items in groups cannot be virtualized
            -->
            <CollectionViewSource
                x:Name="groupedItemsViewSource"
                Source="{Binding Groups}"
                IsSourceGrouped="true"
                ItemsPath="Items"
                d:Source="{Binding Groups, Source={d:DesignData Source=/DataModel/SampleData.json, Type=data:SampleDataSource}}"/>
    
            <!-- DataTemplate to use in the portrait layout. -->
            <DataTemplate x:Key="portraitItemTemplate">
                <Grid HorizontalAlignment="Left" Width="250" Height="250">
                    <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                        <Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                    </Border>
                    <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                        <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                        <TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
    
            <!-- DataTemplate to use in the minimal layout. -->
            <DataTemplate x:Key="portraitSmallItemTemplate">
                <Grid Margin="6">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">
                        <Image Source="{Binding ImagePath}" Stretch="UniformToFill"/>
                    </Border>
                    <StackPanel Grid.Column="1" Margin="10,0,0,0">
                        <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" MaxHeight="40"/>
                        <TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </Page.Resources>
    

    And here is the itemListViewPortrait that you created previously, but now the DataTemplate has been removed and added to the Page.Resources instead.

    <!-- Vertical scrolling list used in portrait view -->
    <ListView
        x:Name="itemListViewPortrait"
        AutomationProperties.AutomationId="ItemListView"
        AutomationProperties.Name="Items In Group"
        TabIndex="1"
        Grid.Row="1"
        Visibility="Collapsed"
        Padding="60,0,0,10"
        ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <Grid Margin="7,7,0,0">
                            <Button
                                AutomationProperties.Name="Group Title"
                                Click="Header_Click"
                                Style="{StaticResource TextBlockButtonStyle}">
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource SubheaderTextBlockStyle}" />
                                    <TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource SubheaderTextBlockStyle}"/>
                                </StackPanel>
                            </Button>
                        </Grid>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
    </ListView>
    

Define three visual states

Define the visual states that the app switches between when a user resizes it. In the sample, these are called "DefaultLayout," "MinimalLayout," and "PortraitLayout." You do not have to define the properties for the default layout because these defaults are already defined elsewhere. For example, in the defaultNavigation Grid, the Visibility property is set to Visible.

Let's look more closely at one of these visual states. In the MinimalLayout state, you set the defaultNavigation Grid to "Collapsed" and the minimalNavigation Grid to "Visible" so that the Grid that is shown is the minimalNavigation Grid, which you defined to have the smaller back button, smaller header font, and narrower margin. You set the itemGridView, which displays items horizontally, to Collapsed. You set the itemListView, which display items in a vertical list, to Visible. You also set the ItemTemplate property to use the DataTemplate that you defined in the Page.Resources. Finally, you set the Padding property for the itemListView.

Here is the XAML that defines the three states.

  • Define the visual states.

    <VisualStateManager.VisualStateGroups>
                <VisualStateGroup>
                    <VisualState x:Name="DefaultLayout">
                        <!-- The default Visibility properties are set in the XAML that defines the objects. -->
                    </VisualState>
                    <VisualState x:Name="MinimalLayout">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="defaultNavigation" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="minimalNavigation" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="ItemTemplate">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource portraitSmallItemTemplate}"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Padding">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="10,0,0,60"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="PortraitLayout">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="defaultNavigation" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="minimalNavigation" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="ItemTemplate">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource portraitItemTemplate}"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Padding">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="60,0,0,10"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
    

Switch layouts

Now that you have defined the narrow layout and the tall layout, you must define the event that causes the app to switch to and from these layouts.

  • Add a SizeChanged event handler to the page. This event occurs whenever the height or width of your page changes size. Here is the code from the GroupedItemsPage.xaml.cs file that defines the SizeChanged event handler.

    public sealed partial class GroupedItemsPage : Page
        {
            /// ...
    
            public GroupedItemsPage()
            {
                this.InitializeComponent();
                this.navigationHelper = new NavigationHelper(this);
                this.navigationHelper.LoadState += navigationHelper_LoadState;
                this.SizeChanged += GroupedItemsPage_SizeChanged;
            }
    
            /// If the page is resized to less than 500 pixels, use the layout for narrow widths. 
            /// If the page is resized so that the width is less than the height, use the tall (portrait) layout.
            /// Otherwise, use the default layout.
            void GroupedItemsPage_SizeChanged(object sender, SizeChangedEventArgs e) 
            { 
                if (e.NewSize.Width < 500) 
                    { 
                        VisualStateManager.GoToState(this, "MinimalLayout", true); 
                    } 
                    else if (e.NewSize.Width < e.NewSize.Height) 
                    { 
                        VisualStateManager.GoToState(this, "PortraitLayout", true); 
                    } 
                    else 
                    { 
                        VisualStateManager.GoToState(this, "DefaultLayout", true); 
                    } 
                } 
           }
           /// ...
        }
    

Summary

You should now understand how you can design your app so that it looks good and functions well at narrow widths and in layouts that are taller than wide.

Quickstart: Defining layouts