Гибкие макеты на основе XAML

Система макетов XAML позволяет применять функцию автоматического выбора размера, панели макетов, визуальные состояния для создания адаптивного интерфейса. Благодаря гибкому макету ваше приложение будет прекрасно смотреться на экранах с различными размерами окна, разными разрешениями, плотностью пикселей и ориентацией. XAML также можно использовать для изменения положения, изменения размера, показа и скрытия или изменения архитектуры пользовательского интерфейса приложения, как описано в статье Методы создания адаптивного дизайна. В этой статье описано, как реализовывать гибкие макеты с помощью XAML.

Гибкие макеты со свойствами и панелями

Основа гибкого макета — это правильное применение свойств и панелей макета XAML для гибкого изменения положения, изменения размеров и адаптации содержимого.

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

На практике используется сочетание статических и гибких элементов для создания пользовательского интерфейса. Можно использовать статические элементы и значения в некоторых местах, но пользовательский интерфейс в целом должен адаптироваться к разным разрешениям, размерам экрана и представлениям.

В этой статье описано, как создать гибкий макет с помощью свойств и панелей макета XAML.

Свойства макета

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

Ниже приведены некоторые распространенные свойства макета и способы их использования для создания гибких макетов.

Height и Width

Свойства Height и Width позволяют определить размер элемента. Можно использовать статические значения, измеренные в эффективных пикселях, функцию автоматического выбора размера либо пропорциональный размер.

Функция автоматического выбора размера позволяет изменяет размер элементов пользовательского интерфейса в соответствии с их содержимым или размером родительского контейнера. Можно также использовать функцию автоматического выбора размера со строками и столбцами сетки. Для использования функции автоматического выбора размеров установите для параметров Height и/или Width элементов пользовательского интерфейса значение Auto.

Примечание

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

Пропорциональное изменение размеров, которое задается символом звездочки, распределяет доступное пространство среди строк и столбцов сетки с помощью взвешенных пропорций. В XAML star значения выражаются как * (или n* для взвешиваемого star размера). Например, чтобы указать, что один столбец в 5 раз шире, чем второй столбец в макете с 2 столбцами, используйте "5*" и "*" для свойств Width в элементах ColumnDefinition .

В этом примере сочетаются фиксированный, автоматический и пропорциональный размер в элементе Grid с четырьмя столбцами.

Столбец Определение размера Описание
Column_1 Автоматически Размер столбца автоматически подстраивается под содержимое.
Column_2 * После вычисления значений ширины для столбцов с автоматическим подбором размера этот столбец получает часть оставшейся ширины. Ширина столбца Column_2 будет занимать половину ширины столбца Column_4.
Column_3 44 Столбец будет иметь ширину 44 пикселя.
Column_4 2* После вычисления значений ширины для столбцов с автоматическим подбором размера этот столбец получает часть оставшейся ширины. Ширина столбца Column_4 будет в два раза больше ширины столбца Column_2.

Ширина столбцов по умолчанию составляет «*», поэтому не нужно явным образом задавать это значение для второго столбца.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition/>
        <ColumnDefinition Width="44"/>
        <ColumnDefinition Width="2*"/>
    </Grid.ColumnDefinitions>
    <TextBlock Text="Column 1 sizes to its content." FontSize="24"/>
</Grid>

В конструкторе XAML в Visual Studio результат выглядит следующим образом.

Сетка 4 столбцов в конструкторе Visual Studio

Чтобы получить размер элемента во время выполнения, используйте свойства ActualHeight и ActualWidth только для чтения вместо Height и Width.

Ограничения размера

При автоматическом выборе размера в вашем пользовательском интерфейсе все же может потребоваться установить ограничения для размера элемента. Вы можете задать свойства MinWidth/MaxWidth и MinHeight/MaxHeight, чтобы определить значения, которые ограничивают размер элемента, но в то же время обеспечивают его гибкое изменение.

В элементах Grid свойство MinWidth/MaxWidth можно также использовать с определениями столбца. Свойство MinHeight/MaxHeight можно использовать с определениями строки.

Выравнивание

Используйте свойства HorizontalAlignment и VerticalAlignment, чтобы указать, как элемент должен быть расположен в выделенном родительском контейнере.

  • Значения для HorizontalAlignment: Left, Center, Right и Stretch.
  • Значения для VerticalAlignment: Top, Center, Bottom и Stretch.

При выравнивании Stretch элементы занимают все выделенное им место в родительском контейнере. Stretch является значением по умолчанию для обоих свойств выравнивания. Однако некоторые элементы управления, например Button, перезаписывают это значение в своем стиле по умолчанию. Любой элемент, который может содержать дочерние элементы, может интерпретировать значение Stretch только для свойств HorizontalAlignment и VerticalAlignment. Например, элемент, использующий значения Stretch по умолчанию в Grid, растягивается, заполняя ячейку, которая его содержит. Тот же элемент, помещенный в Canvas, принимает размеры его содержимого. Подробнее о том, как каждая панель обрабатывает значение Stretch, см. в статье Панели макета.

Подробнее см. в статье Выравнивание, поле и заполнение и на справочных страницах HorizontalAlignment и VerticalAlignment.

Видимость

Вы можете показать или скрыть элемент, задав свойству Visibility одно из значений перечисления Visibility: Visible или Collapsed. Когда элемент свернут (Collapsed), он не занимает место в макете пользовательского интерфейса.

Можно изменить свойство Visibility элемента в коде или в визуальном состоянии. Когда свойство Visibility элемента изменено, все его дочерние элементы также изменяются. Вы можете заменить разделы своего пользовательского интерфейса путем отображения одной панели и сворачивания другой.

Совет

Если в пользовательском интерфейсе есть элементы, которые свернуты (Collapsed) по умолчанию, объекты продолжают создаваться при запуске, даже если они невидимы. Вы можете отложить загрузку этих элементов до их отображения с помощью атрибута x:Load , чтобы отложить создание объектов. Это может улучшить производительность при запуске. Дополнительные сведения см. в разделе Атрибут x:Load.

Ресурсы стиля

Вам не придется задавать значения свойства по отдельности для элемента управления. Обычно такой способ более эффективен для группировки значений свойств в ресурс Style и применения Style к элементу управления. Это в первую очередь касается случаев, когда необходимо применить одинаковые значения свойства к многочисленным элементам управления. Подробнее об использовании стилей см. в разделе Настройка стиля элементов управления.

Панели макета

Чтобы разместить видимые объекты, необходимо поместить их в элемент «Панель» или в другой объект-контейнер. Платформа XAML предоставляет различные классы panel, такие как Canvas, Grid, RelativePanel и StackPanel, которые служат контейнерами для размещения элементов пользовательского интерфейса.

Выбирая панель, главное — определить, как панель размещает свои дочерние элементы и задает их размеры. Также может потребоваться учесть характер перекрытия дочерних элементов, наложенных друг на друга.

Ниже показано сравнение основных функций элементов управления панели, предоставленных в платформе XAML.

Элемент управления панели Описание
Canvas Canvas не поддерживает гибкий пользовательский интерфейс; вы управляете всеми аспектами размещения и изменения размера дочерних элементов. Он обычно применяется в особых случаях, например, при создании графики или определении небольших статических областей большего адаптивного пользовательского интерфейса. Вы можете использовать код или визуальные состояния для перемещения элементов во время выполнения.
  • Абсолютное положение элементов задается присоединенными свойствами Canvas.Top и Canvas.Left.
  • Многоуровневое расположение можно явным образом указать с помощью присоединенного свойства Canvas.ZIndex.
  • Значения Stretch для параметра HorizontalAlignment/VerticalAlignment игнорируются. Если размер элемента явным образом не задан, его размер подбирается по содержимому.
  • Содержимое дочерних элементов визуально не сокращается, если его размер больше панели.
  • Содержимое дочерних элементов может выходить за границы панели.
  • Grid Grid поддерживает гибкое изменение размера дочерних элементов. Вы можете использовать код или визуальные состояния для перемещения элементов и адаптации.
  • Элементы упорядочиваются в строки и столбцы с помощью прикрепленных свойств Grid.Row и Grid.Column.
  • Присоединенные свойства Grid.RowSpan и Grid.ColumnSpan позволяют распространить элементы на несколько строк или столбцов.
  • Значения Stretch для параметра HorizontalAlignment/VerticalAlignment учитываются. Если размер элемента явным образом не задан, он растягивается на все доступное пространство ячейки сетки.
  • Содержимое дочерних элементов визуально обрезается, если его размер больше панели.
  • Размер содержимого ограничен границами панели, поэтому в прокручиваемом содержимом отображаются полосы прокрутки, если необходимо.
  • RelativePanel
  • Элементы упорядочены относительно края или центра панели и относительно друг друга.
  • Элементы размещаются с помощью различных присоединенных свойств, которые управляют выравниванием панели, выравниванием элементов одного уровня и расположением элементов одного уровня.
  • Значения Stretch для параметров HorizontalAlignment/VerticalAlignment игнорируются, если присоединенные свойства RelativePanel для выравнивания не будут вызывать растягивание (например, элемент выравнивается по правому и левому краям панели). Если размер элемента не задан явно и он не растянут, то размер соответствует его содержимому.
  • Содержимое дочерних элементов визуально обрезается, если его размер больше панели.
  • Размер содержимого ограничен границами панели, поэтому в прокручиваемом содержимом отображаются полосы прокрутки, если необходимо.
  • StackPanel
  • Элементы размещаются стопкой на одной линии по вертикали или горизонтали.
  • Значения Stretch для параметров HorizontalAlignment/VerticalAlignment соблюдаются в направлении, противоположном свойству Orientation. Если размер элемента явным образом не задан, он растягивается на всю доступную ширину (или высоту, если Orientation имеет значение Horizontal). В направлении, определенном свойством Orientation, размер элемента соответствует его содержимому.
  • Содержимое дочерних элементов визуально обрезается, если его размер больше панели.
  • Размер содержимого может выходить за границы панели в направлении, определенном свойством Orientation. Поэтому содержимое с возможностью прокрутки растягивается за границы панели, и в нем не отображаются полосы прокрутки. Необходимо явным образом ограничить высоту (или ширину) содержимого дочерних элементов, чтобы сделать полосы прокрутки видимыми.
  • VariableSizedWrapGrid
  • Элементы упорядочены по строкам и столбцам с автоматическим переносом на новую строку или столбец при достижении значения MaximumRowsOrColumns.
  • Упорядочиваются ли элементы по строкам и столбцам, определяется свойством Orientation.
  • Присоединенные свойства VariableSizedWrapGrid.RowSpan и VariableSizedWrapGrid.ColumnSpan позволяют распространить элементы на несколько строк или столбцов.
  • Значения Stretch для HorizontalAlignment и VerticalAlignment игнорируются. Размеры элементов задаются свойствами ItemHeight и ItemWidth. Если эти свойства не заданы, они получают значения на основе размера первой ячейки.
  • Содержимое дочерних элементов визуально обрезается, если его размер больше панели.
  • Размер содержимого ограничен границами панели, поэтому в прокручиваемом содержимом отображаются полосы прокрутки, если необходимо.
  • Подробнее об этих панелях и примеры см. в разделе Панели макета.

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

    Адаптивные макеты с визуальными состояниями и триггерами состояния

    Используйте визуальные состояния, чтобы существенно изменить пользовательский интерфейс на основе размера окна или внести другие правки.

    Когда окно вашего приложения увеличится или уменьшится до размеров, выходящих за указанный диапазон, возможно, вы захотите изменить свойства макета, чтобы перемещать окна и изменять их размеры, адаптировать, отображать или заменять разделы вашего пользовательского интерфейса. Вы можете определять различные визуальные состояния пользовательского интерфейса и применять их, когда ширина или высота окна превысит заданное пороговое значение.

    VisualState определяет значения свойств, которые применяются к элементу, когда он находится в определенном состоянии. Сгруппируйте визуальные состояния в VisualStateManager, который применяет соответствующее VisualState при определенных условиях. AdaptiveTrigger предоставляет простой способ установки порогового значения (или "точки останова"), при котором применяется состояние в XAML. Или вызовите метод VisualStateManager.GoToState в коде, чтобы применить визуальное состояние. Примеры обоих способов показаны в следующих разделах.

    Задание визуальных состояний в коде

    Для применения визуального состояния из кода вызовите метод VisualStateManager.GoToState. Например, чтобы применить состояние, когда окно приложения имеет определенный размер, обработайте событие SizeChanged и вызовите GoToState для применения соответствующего состояния.

    Здесь VisualStateGroup содержит два определения VisualState. Первое, DefaultState, пустое. Если оно применяется, применяются значения, определенные на странице XAML. Второе, WideState, изменяет свойство DisplayMode параметра SplitView на Inline и открывает панель. Это состояние применяется в обработчике событий SizeChanged, если ширина окна превышает 640 эффективных пикселей.

    Примечание

    Windows не предоставляет средств, благодаря которым приложение могло бы определить устройство, на котором оно запущено. Он может сообщить о семействе устройств (настольном компьютере и т. д.), на котором выполняется приложение, эффективном разрешении и объеме экранного пространства, доступного для приложения (размер окна приложения). Мы рекомендуем задавать визуальные состояния для размеров экрана и точек прерывания.

    <Page ...
        SizeChanged="CurrentWindow_SizeChanged">
        <Grid>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup>
                    <VisualState x:Name="DefaultState">
                            <Storyboard>
                            </Storyboard>
                        </VisualState>
    
                    <VisualState x:Name="WideState">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames
                                Storyboard.TargetProperty="SplitView.DisplayMode"
                                Storyboard.TargetName="mySplitView">
                                <DiscreteObjectKeyFrame KeyTime="0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <SplitViewDisplayMode>Inline</SplitViewDisplayMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames
                                Storyboard.TargetProperty="SplitView.IsPaneOpen"
                                Storyboard.TargetName="mySplitView">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
    
            <SplitView x:Name="mySplitView" DisplayMode="CompactInline"
                       IsPaneOpen="False" CompactPaneLength="20">
                <!-- SplitView content -->
    
                <SplitView.Pane>
                    <!-- Pane content -->
                </SplitView.Pane>
            </SplitView>
        </Grid>
    </Page>
    
    private void CurrentWindow_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
        if (e.Size.Width > 640)
            VisualStateManager.GoToState(this, "WideState", false);
        else
            VisualStateManager.GoToState(this, "DefaultState", false);
    }
    
    // YourPage.h
    void CurrentWindow_SizeChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs const& e);
    
    // YourPage.cpp
    void YourPage::CurrentWindow_SizeChanged(IInspectable const& sender, SizeChangedEventArgs const& e)
    {
        if (e.NewSize.Width > 640)
            VisualStateManager::GoToState(*this, "WideState", false);
        else
            VisualStateManager::GoToState(*this, "DefaultState", false);
    }
    
    

    Задание визуальных состояний в разметке XAML

    До Windows 10 определения VisualState требовали объектов Storyboard для изменения свойств, и необходимо было вызвать GoToState в коде для применения состояния. Это демонстрируется в предыдущем примере. Вы увидите много примеров, которые также используют этот синтаксис. Возможно, у вас даже есть код, в котором он используется.

    Начиная c Windows 10, можно использовать упрощенный синтаксис Setter, показанный здесь, и использовать объекты StateTrigger в разметке XAML для применения состояния. Триггеры состояния используется для создания своих простых правил, которые автоматически вызывают изменения визуального состояния в ответ на события приложения.

    В этом примере выполняются те же действия, что и в предыдущем, но используется упрощенный синтаксис Setter вместо Storyboard для определения изменений свойств. И вместо вызова GoToState в нем используется встроенный триггер состояния AdaptiveTrigger для применения состояния. При использовании триггеров состояния не нужно указывать пустое состояние DefaultState. Параметры по умолчанию повторно автоматически применяются при игнорировании условий триггера состояния.

    <Page ...>
        <Grid>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup>
                    <VisualState>
                        <VisualState.StateTriggers>
                            <!-- VisualState to be triggered when the
                                 window width is >=640 effective pixels. -->
                            <AdaptiveTrigger MinWindowWidth="640" />
                        </VisualState.StateTriggers>
    
                        <VisualState.Setters>
                            <Setter Target="mySplitView.DisplayMode" Value="Inline"/>
                            <Setter Target="mySplitView.IsPaneOpen" Value="True"/>
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
    
            <SplitView x:Name="mySplitView" DisplayMode="CompactInline"
                       IsPaneOpen="False" CompactPaneLength="20">
                <!-- SplitView content -->
    
                <SplitView.Pane>
                    <!-- Pane content -->
                </SplitView.Pane>
            </SplitView>
        </Grid>
    </Page>
    

    Важно!

    В предыдущем примере для элемента Grid настраивается прикрепленное свойство VisualStateManager.VisualStateGroups. При использовании StateTriggers всегда следует убедиться, что свойство VisualStateGroups является присоединенным к первому дочернему элементу корня, чтобы триггеры запускались автоматически. (Здесь Grid — это первый дочерний элемент корня Page).

    Синтаксис присоединенного свойства

    В VisualState обычно задается значение для свойства элемента управления или для одного из присоединенных свойств панели, содержащей элемент управления. Когда вы задаете присоединенное свойство, используйте скобки вокруг имени присоединенного свойства.

    В этом примере показано, как задать присоединенное свойство RelativePanel.AlignHorizontalCenterWithPanel в TextBox с именем myTextBox. Первый XAML использует синтаксис ObjectAnimationUsingKeyFrames, а второй — синтаксис Setter.

    <!-- Set an attached property using ObjectAnimationUsingKeyFrames. -->
    <ObjectAnimationUsingKeyFrames
        Storyboard.TargetProperty="(RelativePanel.AlignHorizontalCenterWithPanel)"
        Storyboard.TargetName="myTextBox">
        <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
    </ObjectAnimationUsingKeyFrames>
    
    <!-- Set an attached property using Setter. -->
    <Setter Target="myTextBox.(RelativePanel.AlignHorizontalCenterWithPanel)" Value="True"/>
    

    Пользовательские триггеры состояния

    Вы можете расширить класс StateTrigger, чтобы создавать пользовательские триггеры для широкого диапазона сценариев. Например, вы можете создать StateTrigger, чтобы активировать различные состояния на основании типа ввода, а затем расширять поля вокруг элемента управления, когда тип ввода является сенсорным. Также StateTrigger позволяет применять различные состояния, основанные на семействах устройств, на которых запущено приложение. Примеры создания пользовательских триггеров и способы их использования для оптимизации пользовательского интерфейса из единого представления XAML см. в разделе Пример триггеров состояния.

    Визуальные состояния и стили

    Ресурсы Style можно использовать в визуальных состояниях для применения набора изменений свойств к нескольким элементам управления. Подробнее об использовании стилей см. в разделе Настройка стиля элементов управления.

    В этом упрощенном коде XAML из раздела "Пример триггеров состояния" ресурс Style применяется к Button для настройки размера и полей для ввода мышью или сенсорного ввода. Полный код и определение пользовательского триггера состояния см. в разделе Пример триггеров состояния.

    <Page ... >
        <Page.Resources>
            <!-- Styles to be used for mouse vs. touch/pen hit targets -->
            <Style x:Key="MouseStyle" TargetType="Rectangle">
                <Setter Property="Margin" Value="5" />
                <Setter Property="Height" Value="20" />
                <Setter Property="Width" Value="20" />
            </Style>
            <Style x:Key="TouchPenStyle" TargetType="Rectangle">
                <Setter Property="Margin" Value="15" />
                <Setter Property="Height" Value="40" />
                <Setter Property="Width" Value="40" />
            </Style>
        </Page.Resources>
    
        <RelativePanel>
            <!-- ... -->
            <Button Content="Color Palette Button" x:Name="MenuButton">
                <Button.Flyout>
                    <Flyout Placement="Bottom">
                        <RelativePanel>
                            <Rectangle Name="BlueRect" Fill="Blue"/>
                            <Rectangle Name="GreenRect" Fill="Green" RelativePanel.RightOf="BlueRect" />
                            <!-- ... -->
                        </RelativePanel>
                    </Flyout>
                </Button.Flyout>
            </Button>
            <!-- ... -->
        </RelativePanel>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="InputTypeStates">
                <!-- Second set of VisualStates for building responsive UI optimized for input type.
                     Take a look at InputTypeTrigger.cs class in CustomTriggers folder to see how this is implemented. -->
                <VisualState>
                    <VisualState.StateTriggers>
                        <!-- This trigger indicates that this VisualState is to be applied when MenuButton is invoked using a mouse. -->
                        <triggers:InputTypeTrigger TargetElement="{x:Bind MenuButton}" PointerType="Mouse" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="BlueRect.Style" Value="{StaticResource MouseStyle}" />
                        <Setter Target="GreenRect.Style" Value="{StaticResource MouseStyle}" />
                        <!-- ... -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState>
                    <VisualState.StateTriggers>
                        <!-- Multiple trigger statements can be declared in the following way to imply OR usage.
                             For example, the following statements indicate that this VisualState is to be applied when MenuButton is invoked using Touch OR Pen.-->
                        <triggers:InputTypeTrigger TargetElement="{x:Bind MenuButton}" PointerType="Touch" />
                        <triggers:InputTypeTrigger TargetElement="{x:Bind MenuButton}" PointerType="Pen" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="BlueRect.Style" Value="{StaticResource TouchPenStyle}" />
                        <Setter Target="GreenRect.Style" Value="{StaticResource TouchPenStyle}" />
                        <!-- ... -->
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </Page>