Статьи

Prism и универсальные приложения для Windows: управление раскладкой

В предыдущих статьях мы видели, что одним из требований для настройки инфраструктуры Prism является изменение представлений в нашем приложении, чтобы вместо наследования от класса Page мы наследовали от класса VisualStateAwarePage . Одной из функций, предлагаемых этим классом, является поддержка управления макетом: с помощью визуальных состояний мы сможем легко управлять различными визуальными макетами, которыми может воспользоваться приложение.

К сожалению, на данный момент эта функция поддерживается только в приложениях Windows 8. В VisualStateAwarePage предложения, автоматически, три различные визуальные состояния:

  • DefaultLayout — это стандартный макет, который используется, когда приложение отображается в горизонтальном режиме (который используется по умолчанию на устройствах с Windows 8).
  • PortraitLayout — это макет, который используется, когда приложение отображается в портретном режиме.
  • MinimalLayout — это макет, который используется, когда размер приложения изменяется, а его ширина достигает значения, которого больше недостаточно для отображения стандартного макета, поэтому нам нужно его изменить.

Давайте посмотрим, как реализовать эти сценарии.

Управление макетом портрета

Благодаря подходу визуальных состояний мы сможем изменить макет, просто указав с помощью XAML различия по сравнению со стандартным макетом. Давайте посмотрим на следующий пример:

<storeApps:VisualStateAwarePage
    x:Class="Prism_LayoutManagement.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Prism_LayoutManagement.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps"
    xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm"
    mc:Ignorable="d"
     
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
 
 
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="DefaultLayout"/>
                <VisualState x:Name="PortraitLayout">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonsPanel"
                                                   Storyboard.TargetProperty="Orientation">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Vertical" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
 
        <StackPanel x:Name="ButtonsPanel" Orientation="Horizontal">
            <Button Content="Button 1" />
            <Button Content="Button 2" />
            <Button Content="Button 3" />
            <Button Content="Button 4" />
        </StackPanel>
 
    </Grid>
</storeApps:VisualStateAwarePage>

Страница просто содержит четыре кнопки управления, которые расположены внутри StackPanel которых ориентация свойства устанавливаются в горизонтальный . Это означает, что когда страница отображается с использованием макета по умолчанию (это альбомный режим), четыре кнопки отображаются одна рядом с другой. Наша цель — изменить этот макет так, чтобы при использовании приложения в портретном режиме кнопки отображались одна под другой.

Давайте представим VisualStateManager , который является классом, способным управлять различными визуальными состояниями элемента управления. Каждое визуальное состояние соответствует одному из возможных состояний элемента управления: например, элемент управления Button использует визуальное состояние, чтобы определить, как оно должно выглядеть при нажатии. Как мы уже упоминали, лучшим преимуществом этого подхода является то, что нам не нужно каждый раз определять всю компоновку элемента управления, но достаточно определить различия между одним состоянием и другим. Например, состояние « Нажатие» элемента управления Button просто указывает, что по сравнению со стандартным макетом фон кнопки должен быть белым, а не черным, а текст должен быть черным, а не белым.

Мы используем тот же подход для управления различными макетами страницы: внутри VisualStateManager, который мы добавили в основную сетку , мы добавляем список элементов VisualState , каждый из которых имеет определенное имя, соответствующее соглашениям Prism. Стандартный называется DefaultLayout: он пустой, поскольку является базовым определением страницы. Затем мы создаем новый для портретного режима, который называется PortraitLayout : внутри него, используя анимацию, мы указываем различия с макетом по умолчанию. В нашем примере мы определяем раскадровку с помощью одной анимации ObjectAnimationUsingKeyFrames , которая просто меняет значение ориентациисвойство StackPanel : от горизонтального до вертикального . Если бы нам нужно было изменить другие элементы управления, мы бы просто добавили другие анимации в раскадровку .

Если вы хотите проверить код, который мы написали, вы можете использовать симулятор Windows 8: в выпадающем меню « Отладка» просто выберите « Симулятор» вместо « Локальный компьютер» . Фактически, симулятор предлагает опцию имитации вращения устройства, что очень полезно, если вы разрабатываете и тестируете свое приложение на традиционном компьютере.

Layout1

Layout2

Управление изменением размера приложения

Если вы пользователь Windows 8, вы должны быть знакомы с концепцией «привязки»: у вас есть возможность держать несколько приложений открытыми одновременно и размещать их рядом. В Windows 8.1 эта функция была улучшена: если в Windows 8 привязанный вид имел фиксированный размер, в Windows 8.1 пользователь может изменить ширину приложения практически до любого размера, пока он не достигнет минимальной ширины ( по умолчанию это 500px, но его можно изменить на 320px).

Как разработчики, мы должны правильно управлять этим сценарием, чтобы обеспечить хороший пользовательский опыт независимо от размера приложения. Первым шагом к достижению этой цели является создание гибкого макета с использованием элементов управления (таких как Grid ), которые могут адаптировать свое содержимое к размеру окна. Таким образом, если пользователь изменяет размер приложения, макет автоматически адаптируется для отображения максимально возможного содержимого. Если вы знакомы с веб-разработкой, этот подход очень похож на адаптивную концепцию дизайна, которая применяется к веб-сайтам, так что они могут автоматически адаптироваться к размеру экрана (ПК, планшеты, смартфоны и т. Д.).

Однако этого шага недостаточно: если окно приложения становится слишком маленьким, отображение содержимого может быть затруднено с использованием того же макета и элемента управления, которые мы используем в стандартном макете. Самым простым способом управления этим сценарием является использование опять-таки визуальных состояний: когда размер приложения становится меньше минимальной ширины, которой, в соответствии с нашим дизайном, уже недостаточно для правильного отображения содержимого, мы переключаемся на другой макет.

Эта цель достигается тем же способом, который мы видели ранее для управления портретным режимом: путем определения нового визуального состояния, идентифицируемого ключом MinimalLayout , с разницей с макетом по умолчанию. Давайте посмотрим на следующий пример:

<storeApps:VisualStateAwarePage
    x:Class="Prism_LayoutManagement.Views.MinimumWidthPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Prism_LayoutManagement.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps"
    xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm"
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">
 
    <storeApps:VisualStateAwarePage.Resources>
        <CollectionViewSource Source="{Binding Path=Persons}" x:Name="PersonsCollection"/>
    </storeApps:VisualStateAwarePage.Resources>
 
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="DefaultLayout"/>
                <VisualState x:Name="MinimalLayout">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="GridPersons"
                                                   Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListPersons"
                                                   Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
 
        <GridView ItemsSource="{Binding Source={StaticResource PersonsCollection}}" SelectionMode="None" IsItemClickEnabled="True"
                  Margin="120, 0, 12, 0" x:Name="GridPersons" >
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid HorizontalAlignment="Left" Width="250" Height="250">
                        <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
                            <Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                        </Border>
                        <StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}" Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                            <TextBlock Text="{Binding Surname}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid GroupPadding="0,0,70,0"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
        </GridView>
 
        <ListView ItemsSource="{Binding Source={StaticResource PersonsCollection}}"
                  Visibility="Collapsed"
                  x:Name="ListPersons">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}" Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                        <TextBlock Text="{Binding Surname}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</storeApps:VisualStateAwarePage>

Страница содержит два элемента управления для отображения коллекций: GridView и ListView . Затем в ресурсах страницы я определил объект CollectionViewSource , который связан с коллекцией в ViewModel. Как видите, оба элемента управления подключены к одному и тому же CollectionViewSource : в результате оба элемента отображают одни и те же данные. Однако есть важное отличие: по умолчанию элемент управления GridView отображается, а элемент управления ListView скрыт (вы можете заметить, что для свойства Visibility установлено значение Collapsed ). Это означает, что когда приложение используется в полноэкранном режиме, мы отображаем данные, используяЭлемент управления GridView , который лучше всего подходит для этого сценария, поскольку он использует подход горизонтальной навигации.

Однако VisualStateManager на странице определяет визуальное состояние с именем MinimalLayout , которое выполняет две анимации, которые взаимодействуют со свойством Visibility элементов управления: первая скрывает GridView , а вторая отображает ListView . Таким образом, когда размер приложения изменяется до размера, который делает невозможным использование и чтение GridView , мы переключаемся на элемент управления ListView , который вместо этого проще использовать также с небольшим размером, поскольку он отображает данные с вертикальной компоновкой ,

По умолчанию минимальная ширина установлена ​​в 500px: это означает, что при изменении размера приложения до 500px визуальное состояние MinimalLayout сработает. И наоборот, когда размер приложения возвращается к 500px или больше, DefaultLayout восстанавливается. Если вы хотите изменить это значение, достаточно изменить свойство, предлагаемое классом VisualStateAwarePage, с именем MinimalLayoutWidth. В следующем примере показано, как установить в XAML минимальную ширину на 800 пикселей:

<storeApps:VisualStateAwarePage
    x:Class="Prism_LayoutManagement.Views.MinimumWidthPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Prism_LayoutManagement.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps"
    xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm"
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    MinimalLayoutWidth="800"
    mc:Ignorable="d">
 
    <!-- content of the page --> 
 
</storeApps:VisualStateAwarePage>

Если вы хотите поиграть с этой функцией, помните, что по умолчанию минимальная ширина приложения Магазина Windows составляет 500 пикселей: это означает, что по умолчанию вы не сможете запускать визуальное состояние, поскольку вы не можете изменить размер приложение менее 500px. Однако в файле манифеста в разделе « Приложение » вы найдете параметр « Минимальная ширина» , который позволит вам изменить его на 320 пикселей (это был размер старого привязанного представления в Windows 8).

layout3

layout4

Завершение

Как обычно, вы можете найти пример проекта, использованного для этого поста, на GitHub по адресу https://github.com/qmatteoq/Prism-UniversalSample . Только помните, что только эта демонстрация будет содержать только демонстрацию Windows 8.1, так как функция, описанная в этом посте, работает только в Windows 8.1.