Layouts dinâmicos com XAML

O sistema de layout XAML fornece dimensionamento automático de elementos, painéis de layout e estados visuais para ajudar você a criar uma interface do usuário dinâmica. Com um layout dinâmico, é possível aprimorar a aparência do aplicativo em telas com diferentes tamanhos, resoluções, densidades de pixel e orientações de janela. Também é possível usar o XAML para reposicionar, redimensionar, refluir, mostrar/ocultar, substituir e reformular a interface do usuário do aplicativo, conforme discutido em Técnicas de design dinâmicas. Discutiremos aqui como implementar layouts dinâmicos com o XAML.

Layouts fluidos com propriedades e painéis

A base de um layout dinâmico é o uso apropriado de propriedades e painéis de layout XAML para reposicionar, redimensionar e refluir o conteúdo de maneira fluida.

O sistema de layout XAML dá suporte a layouts estáticos e fluidos. Em um layout estático, você pode dar posições e tamanhos de pixel explícitos aos controles. Quando o usuário altera a resolução ou a orientação de seu dispositivo, a interface do usuário não é alterada. Layouts estáticos podem ser recortados em diferentes fatores forma e tamanhos de tela. Por outro lado, os layouts fluidos reduzem, crescem e refluem para responderem ao espaço visual disponível em um dispositivo.

Na prática, você usa uma combinação de elementos estáticos e fluidos para criar sua interface do usuário. Você ainda usa elementos e valores estáticos em alguns lugares, mas verifique se a interface do usuário geral responde a diferentes resoluções, tamanhos de tela e modos de exibição.

Discutiremos aqui como usar painéis de layout e propriedades XAML para criar um layout fluido.

Propriedades do layout

As propriedade de layout controlam o tamanho e a posição de um elemento. Para criar um layout fluido, use o dimensionamento automático ou proporcional de elementos e permita que os painéis de layout posicionem seus filhos conforme necessário.

Aqui estão algumas propriedades de layout comuns e como usá-las para criar layouts fluidos.

Height e Width

As propriedades Height e Width especificam o tamanho de um elemento. Você pode usar valores fixos medidos em pixels efetivos, ou usar o dimensionamento automático ou proporcional.

O dimensionamento automático redimensiona os elementos de interface do usuário para caber no contêiner de conteúdo ou pai. Você também pode usar o dimensionamento automático com as linhas e as colunas de uma grade. Para usar o dimensionamento automático, defina Height e/ou Width dos elementos de interface do usuário como Auto.

Observação

O redimensionamento de um elemento para seu conteúdo ou seu contêiner depende de como o contêiner pai manipula o dimensionamento dos filhos. Para obter mais informações, confira Painéis de layout posteriormente neste artigo.

O dimensionamento proporcional, também chamado de dimensionamento em estrela, distribui o espaço disponível entre as linhas e as colunas de uma grade segundo proporções ponderadas. Em XAML, star valores são expressos como * (ou n* para dimensionamento de star ponderado). Por exemplo, para especificar que uma coluna é 5 vezes maior que a segunda coluna em um layout de duas colunas, use "5*" e "*" para as propriedades Width nos elementos ColumnDefinition .

Esse exemplo integra dimensionamentos fixo, automático e proporcional em um Grid com quatro colunas.

Coluna Dimensionamento Descrição
Coluna_1 Automático A coluna será dimensionada para ajustar o conteúdo.
Coluna_2 * Depois que as colunas Auto forem calculadas, a coluna obterá parte da largura remanescente. Column_2 terá metade da largura de Column_4.
Coluna_3 44 A coluna terá 44 pixels de largura.
Coluna_4 2* Depois que as colunas Auto forem calculadas, a coluna obterá parte da largura remanescente. Column_4 terá o dobro da largura de Column_2.

Como a largura da coluna padrão é "*", você não precisa definir explicitamente esse valor para a segunda coluna.

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

No designer XAML do Visual Studio, o resultado fica assim.

Uma grade de quatro colunas no designer do Visual Studio

Para obter o tamanho de um elemento em runtime, use as propriedades somente leitura ActualHeight e ActualWidth, em vez de Height e Width.

Restrições de tamanho

Ao usar o dimensionamento automático na interface do usuário, você talvez ainda precise fazer restrições ao tamanho de um elemento. Você pode definir as propriedades MinWidth/MaxWidth e MinHeight/MaxHeight para especificar valores que restrinjam o tamanho de um elemento ao mesmo tempo em que permitam o redimensionamento fluido.

Em um Grid, MinWidth/MaxWidth também pode ser usado com definições de coluna, e MinHeight/MaxHeight pode ser usado com definições de linha.

Alinhamento

Use as propriedades HorizontalAlignment e VerticalAlignment para especificar como um elemento deve ser posicionado no contêiner pai.

  • Os valores para HorizontalAlignment são Left, Center, Right e Stretch.
  • Os valores para VerticalAlignment são Top, Center, Bottom e Stretch.

Com o alinhamento Stretch, os elementos preenchem todos o espaço oferecido no contêiner pai. Stretch é o valor padrão para ambas as propriedades de alinhamento. No entanto, alguns controles, como Button, substituem esse valor no seu estilo padrão. Qualquer elemento que possa ter elementos filho pode tratar o valor Stretch das propriedades HorizontalAlignment e VerticalAlignment de maneira exclusiva. Por exemplo, um elemento que use os valores Stretch padrão colocados em um Grid se amplia para preencher a célula que o contém. O mesmo elemento colocado em um Canvas é dimensionado segundo seu conteúdo. Para obter mais informações sobre como cada painel manipula o valor Stretch, consulte o artigo Painéis de layout.

Para obter mais informações, consulte o artigo Alinhamento, margem e preenchimento e as páginas de referência HorizontalAlignment e VerticalAlignment.

Visibilidade

Você pode revelar ou ocultar um elemento definindo sua propriedade Visibility como um dos valores de enumeração Visibility: Visible ou Collapsed. Quando um elemento é Collapsed, ele não ocupa nenhum espaço no layout da interface do usuário.

Você pode alterar a propriedade Visibility de um elemento no código ou em um estado visual. Quando o Visibility de um elemento é alterado, todos os seus elementos filho também são alterados. Você pode substituir seções de sua interface do usuário, revelando um painel e recolhendo outro.

Dica

Quando você tiver elementos em sua interface do usuário Collapsed por padrão, ainda assim os objetos serão criados na inicialização, mesmo quando não estiverem visíveis. Você pode adiar o carregamento desses elementos até que eles sejam mostrados usando o atributo x:Load para atrasar a criação dos objetos. Isso pode melhorar o desempenho da inicialização. Para obter mais informações, consulte atributo x:Load.

Recursos de estilo

Você não precisa definir cada valor de propriedade individualmente em um controle. Costuma ser mais eficiente agrupar valores de propriedade em um recurso Style e aplicar o Style a um controle. Isso é especialmente verdadeiro quando você precisa aplicar os mesmos valores de propriedade a muitos controles. Para obter mais informações sobre como usar estilos, consulte Definindo o estilo de controles.

Painéis de layout

Para posicionar objetos visuais, você deve colocá-los em um painel ou em outro objeto de contêiner. A estrutura XAML fornece diversas classes de painel, como Canvas, Grid, RelativePanel e StackPanel, que servem como contêineres e que permitem posicionar e organizar os elementos da interface do usuário neles.

A principal coisa a ser considerada quando se escolhe um painel de layout é como o painel é posicionado e dimensiona seus elementos filho. Você também pode precisar considerar como os elementos filho sobrepostos são colocados uns sobre os outros.

Eis uma comparação dos principais recursos dos controles de painel fornecidos na estrutura XAML.

Controle do painel Descrição
Tela O Canvas não dá suporte à interface do usuário fluida; você controla todos os aspectos do posicionamento e dimensionamento de elementos filho. Você normalmente o usa em casos especiais, como a criação de elementos gráficos, ou para definir pequenas áreas estáticas de uma interface do usuário adaptável maior. Você pode usar o código ou os estados visuais para reposicionar elementos em runtime.
  • Os elementos são posicionados de maneira absoluta usando-se as propriedades anexadas Canvas.Top e Canvas.Left.
  • A disposição em camadas pode ser especificada explicitamente usando-se a propriedade anexada Canvas.ZIndex.
  • Os valores Stretch de HorizontalAlignment/VerticalAlignment são ignorados. Caso o tamanho do elemento não seja definido explicitamente, ele é dimensionado segundo seu conteúdo.
  • O conteúdo filho não será recortado visualmente se for maior do que o painel.
  • O conteúdo filho não é restringido pelos limites do painel.
  • Grid Grid dá suporte ao redimensionamento fluido de elementos filho. Você pode usar o código ou os estados visuais para reposicionar e refluir elementos.
  • Os elementos são organizados em linhas e colunas usando-se as propriedades anexadas Grid.Row e Grid.Column.
  • Os elementos podem abranger várias linhas e colunas usando-se as propriedades anexadas Grid.RowSpan e Grid.ColumnSpan.
  • Os valores Stretch de HorizontalAlignment/VerticalAlignment são respeitados. Se o tamanho de um elemento não for definido explicitamente, ele se ampliará para preencher o espaço disponível na célula da grade.
  • O conteúdo filho será recortado visualmente se for maior do que o painel.
  • Como o tamanho do conteúdo é restringido pelos limites do painel, o conteúdo rolável mostra barras de rolagem, caso necessário.
  • RelativePanel
  • Os elementos são organizados em relação à borda ou ao centro do painel e em relação uns aos outros.
  • Os elementos são posicionados usando-se uma variedade de propriedades anexadas que controlam o alinhamento do painel, o alinhamento do irmão e a posição do irmão.
  • Os valores Stretch de HorizontalAlignment/VerticalAlignment são ignorados, a menos que as propriedades anexadas RelativePanel do alinhamento causem o alongamento (por exemplo, um elemento está alinhado com as bordas direita e esquerda do painel). Caso o tamanho de um elemento não seja definido explicitamente e não seja ampliado, ele é dimensionado segundo seu conteúdo.
  • O conteúdo filho será recortado visualmente se for maior do que o painel.
  • Como o tamanho do conteúdo é restringido pelos limites do painel, o conteúdo rolável mostra barras de rolagem, caso necessário.
  • StackPanel
  • Os elementos são empilhados em uma única linha vertical ou horizontalmente.
  • Os valores Stretch de HorizontalAlignment/VerticalAlignment são respeitados na direção oposta à da propriedade Orientation. Caso o tamanho de um elemento não seja definido explicitamente, ele se amplia para preencher a largura disponível (ou a altura caso o Orientation seja Horizontal). Na direção especificada pela propriedade Orientation, um elemento é dimensionado segundo seu conteúdo.
  • O conteúdo filho será recortado visualmente se for maior do que o painel.
  • Como o tamanho do conteúdo não é restringido pelos limites do painel na direção especificada pela propriedade Orientation, o conteúdo rolável se amplia além dos limites do painel e não mostra barras de rolagem. Você deve restringir explicitamente a altura (ou a largura) do conteúdo filho para fazer suas barras de rolagem serem mostradas.
  • VariableSizedWrapGrid
  • Os elementos são organizados em linhas ou colunas que se ajustam automaticamente em uma nova linha ou coluna quando o valor MaximumRowsOrColumns é atingido.
  • A propriedade Orientation especifica se os elementos devem ser organizados em linhas ou colunas.
  • Os elementos podem abranger várias linhas e colunas usando-se as propriedades anexadas VariableSizedWrapGrid.RowSpan e VariableSizedWrapGrid.ColumnSpan.
  • Os valores stretch para HorizontalAlignment e VerticalAlignment são ignorados. Os elementos são dimensionados conforme especificados pelas propriedades ItemHeight e ItemWidth. Se essas propriedades não forem definidas, elas usarão os valores do tamanho da primeira célula.
  • O conteúdo filho será recortado visualmente se for maior do que o painel.
  • Como o tamanho do conteúdo é restringido pelos limites do painel, o conteúdo rolável mostra barras de rolagem, caso necessário.
  • Para obter informações detalhadas e exemplos desses painéis, consulte Painéis de layout.

    Os painéis de layout permitem organizar sua interface do usuário em grupos lógicos de controles. Ao usá-los com as configurações de propriedade apropriadas, você tem certo suporte para redimensionamento automático, reposicionamento e refluxo de elementos da interface do usuário. No entanto, a maioria dos layouts de interface do usuário ainda precisa de mais modificações quando ocorrem alterações significativas no tamanho da janela. Para isso, você pode usar estados visuais.

    Layouts adaptáveis com estados visuais e gatilhos de estado

    Use estados visuais para fazer alterações significativas na interface do usuário com base no tamanho da janela ou em outras alterações.

    Quando a janela do aplicativo é ampliada ou reduzida além de um determinado valor, convém alterar as propriedades do layout para reposicionar, redimensionar, refluir, revelar ou substituir seções de sua interface do usuário. Você pode definir estados visuais diferentes para sua interface do usuário e aplicá-los quando a largura ou altura da janela ultrapassar um limite especificado.

    Um VisualState define valores de propriedade que são aplicados a um elemento quando ele está em um estado específico. Você agrupa estados visuais em um VisualStateManager que aplica o VisualState apropriado quando as condições especificadas são atendidas. Um AdaptiveTrigger oferece uma forma fácil de definir o limite (também chamado de 'ponto de interrupção'), em que um estado é aplicado em XAML. Ou, então, você pode chamar o método VisualStateManager.GoToState no código para aplicar o estado visual. São mostrados exemplos das duas maneiras nas próximas seções.

    Definir estados visuais no código

    Para aplicar um estado visual no código, você chama o método VisualStateManager.GoToState. Por exemplo, para aplicar um estado quando a janela do aplicativo tem um determinado tamanho, manipule o evento SizeChanged e chame GoToState para aplicar o estado apropriado.

    Aqui, um VisualStateGroup contém duas definições VisualState. A primeira, DefaultState, está vazia. Quando ela é aplicada, os valores definidos na página XAML são aplicados. A segunda, WideState, altera a propriedade DisplayMode do SplitView para Inline e abre o painel. Esse estado é aplicado no manipulador de eventos SizeChanged caso a largura da janela seja superior a 640 pixels efetivos.

    Observação

    O Windows não permite que o aplicativo detecte o dispositivo em que o aplicativo está sendo executado. Ele pode informar a família de dispositivos (área de trabalho, etc. ) em que o aplicativo está em execução, a resolução efetiva e a quantidade de espaço de tela disponível para o aplicativo (o tamanho da janela do aplicativo). É recomendável definir estados visuais para tamanhos de tela e pontos de interrupção.

    <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);
    }
    
    

    Definir estados visuais na marcação XAML

    Antes do Windows 10, as definições VisualState exigiam objetos Storyboard para alterações de propriedade, e você precisava chamar GoToState no código para aplicar o estado. Isso é mostrado no exemplo anterior. Você ainda verá muitos exemplos que usam essa sintaxe, ou pode ter um código existente que o use.

    A partir do Windows 10, você pode usar a sintaxe Setter simplificada mostrada aqui e usar um StateTrigger na marcação XAML para aplicar o estado. Você usa gatilhos de estado para criar regras simples que disparam alterações de estado visuais automaticamente em resposta a um evento do aplicativo.

    Este exemplo faz a mesma coisa que o exemplo anterior, mas usa a sintaxe simplificada Setter, em vez de um Storyboard para definir as alterações de propriedade. E, em vez de chamar GoToState, ele usa o gatilho de estado AdaptiveTrigger interno para aplicar o estado. Ao usar gatilhos de estado, você não precisa definir um DefaultState vazio. As configurações padrão são reaplicadas automaticamente quando as condições do gatilho de estado não são mais atendidas.

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

    Importante

    No exemplo anterior, a propriedade anexada VisualStateManager.VisualStateGroups está definida no elemento Grid. Ao usar StateTriggers, certifique-se sempre de que VisualStateGroups esteja anexado ao primeiro filho da raiz para que os gatilhos entrem em vigor automaticamente. (Aqui, Grid é o primeiro filho do elemento Page raiz.)

    Sintaxe da propriedade anexada

    Em um VisualState, você normalmente define um valor para uma propriedade de controle ou para uma das propriedades anexadas do painel que contém o controle. Ao definir uma propriedade anexada, use parênteses no nome da propriedade anexada.

    Este exemplo mostra como definir a propriedade anexada RelativePanel.AlignHorizontalCenterWithPanel em um TextBox chamado myTextBox. O primeiro XAML usa uma sintaxe ObjectAnimationUsingKeyFrames e o segundo usa uma sintaxe 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"/>
    

    Gatilhos de estado personalizados

    Você pode estender a classe StateTrigger para criar gatilhos personalizados para uma ampla variedade de cenários. Por exemplo, você pode criar um StateTrigger para disparar estados diferentes com base no tipo de entrada e, em seguida, aumentar as margens em torno de um controle quando o tipo de entrada é toque. Ou crie um StateTrigger para aplicar estados diferentes com base na família de dispositivos em que o aplicativo está em execução. Para obter exemplos de como compilar gatilhos personalizados e usá-los para criar experiências de interface do usuário otimizadas dentro de um único modo de exibição XAML, consulte a Amostra de gatilhos de estado.

    Estilos e estados visuais

    Você pode usar recursos Style em estados visuais para aplicar um conjunto de alterações de propriedade a vários controles. Para obter mais informações sobre como usar estilos, consulte Definindo o estilo de controles.

    Neste XAML simplificado da Amostra de gatilhos de estado, um recurso Style é aplicado a um Button para ajustar o tamanho e as margens da entrada por mouse ou toque. Para obter o código completo e a definição do gatilho de estado personalizado, consulte Amostra de gatilhos de estado.

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