Compartilhar via


Esquema

Este tópico descreve o sistema de layout do WPF (Windows Presentation Foundation). Entender como e quando os cálculos de layout ocorrem é essencial para criar interfaces de usuário no WPF.

Este tópico contém as seguintes seções:

Caixas delimitadoras de elementos

Ao pensar em layout no WPF, é importante entender a caixa delimitadora que envolve todos os elementos. Cada FrameworkElement consumido pelo sistema de layout pode ser considerado como um retângulo que é encaixado no layout. A classe LayoutInformation retorna os limites da alocação do layout de um elemento ou seu espaço. O tamanho do retângulo é determinado calculando o espaço de tela disponível, o tamanho de quaisquer restrições, propriedades específicas de layout (como margem e preenchimento) e o comportamento individual do elemento pai Panel . Processando esses dados, o sistema de layout é capaz de calcular a posição de todos os filhos de um determinado Panel. É importante lembrar que as características de dimensionamento definidas no elemento pai, como um Border, afetam seus filhos.

A ilustração a seguir mostra um layout simples.

Captura de tela que mostra uma grade típica, sem nenhuma caixa delimitadora sobreposta.

Esse layout pode ser obtido usando o XAML a seguir.

<Grid Name="myGrid" Background="LightSteelBlue" Height="150">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="250"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>
  <TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!</TextBlock>
  <Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding Box</Button>
  <TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/>
</Grid>

Um único TextBlock elemento é hospedado em um Grid. Embora o texto preencha apenas o canto superior esquerdo da primeira coluna, o espaço alocado para o TextBlock é realmente muito maior. A caixa delimitadora de qualquer FrameworkElement pode ser recuperada usando o método GetLayoutSlot. A ilustração a seguir mostra a caixa delimitadora do elemento TextBlock.

Captura de tela que mostra que a caixa delimitadora do TextBlock agora está visível.

Conforme mostrado pelo retângulo amarelo, o espaço alocado para o TextBlock elemento é, na verdade, muito maior do que parece. À medida que elementos adicionais são adicionados ao Grid, essa alocação pode diminuir ou expandir, dependendo do tipo e do tamanho dos elementos adicionados.

O slot de layout do TextBlock é convertido em um Path usando o GetLayoutSlot método. Essa técnica pode ser útil para exibir a caixa delimitadora de um elemento.

private void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e)
{
    RectangleGeometry myRectangleGeometry = new RectangleGeometry();
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);
    Path myPath = new Path();
    myPath.Data = myRectangleGeometry;
    myPath.Stroke = Brushes.LightGoldenrodYellow;
    myPath.StrokeThickness = 5;
    Grid.SetColumn(myPath, 0);
    Grid.SetRow(myPath, 0);
    myGrid.Children.Add(myPath);
    txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString();
}
Private Sub getLayoutSlot1(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim myRectangleGeometry As New RectangleGeometry
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1)
    Dim myPath As New Path
    myPath.Data = myRectangleGeometry
    myPath.Stroke = Brushes.LightGoldenrodYellow
    myPath.StrokeThickness = 5
    Grid.SetColumn(myPath, 0)
    Grid.SetRow(myPath, 0)
    myGrid.Children.Add(myPath)
    txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString()
End Sub

O sistema de layout

Em sua forma mais simples, o layout é um sistema recursivo que faz com que um elemento seja dimensionado, posicionado e desenhado. Mais especificamente, o layout descreve o processo de medir e organizar os membros de um elemento Panel da coleção Children. O layout é um processo exigente. Quanto maior a Children coleção, maior o número de cálculos que devem ser feitos. A complexidade também pode ser introduzida com base no comportamento de layout definido pelo Panel elemento que possui a coleção. Uma estrutura relativamente simples Panel, como Canvas, pode ter um desempenho significativamente melhor do que uma estrutura mais complexa Panel, como Grid.

Cada vez que um elemento UIElement altera sua posição, ele tem o potencial de disparar uma nova passagem pelo sistema de layout. Portanto, é importante entender os eventos que podem invocar o sistema de layout, pois a invocação desnecessária pode levar a um desempenho ruim do aplicativo. A seguir, descreve o processo que ocorre quando o sistema de layout é invocado.

  1. O processo de layout de um filho UIElement começa ao medir primeiro suas propriedades fundamentais.

  2. As propriedades de dimensionamento definidas em FrameworkElement são avaliadas, como Width, Heighte Margin.

  3. PanelA lógica específica é aplicada, como Dock direção ou empilhamento Orientation.

  4. O conteúdo é organizado após todos os filhos terem sido medidos.

  5. A coleção Children é exibida na tela.

  6. O processo será invocado novamente se adicionais Children forem adicionados à coleção, um LayoutTransform for aplicado ou o UpdateLayout método for chamado.

Esse processo e como ele é invocado são definidos com mais detalhes nas seções a seguir.

Medindo e organizando crianças

O sistema de layout conclui duas passagens para cada membro da Children coleção, uma passagem de medida e uma passagem de organização. Cada filho Panel fornece seus próprios métodos MeasureOverride e ArrangeOverride para alcançar seu comportamento específico de layout.

Durante a passagem da medida, cada membro do Children conjunto é avaliado. O processo começa com uma chamada para o Measure método. Esse método é chamado dentro da implementação do elemento pai Panel e não precisa ser chamado explicitamente para que o layout ocorra.

Primeiro, as propriedades de tamanho nativo do UIElement são avaliadas, como Clip e Visibility. Isso gera um valor chamado constraintSize que é passado para MeasureCore.

Em segundo lugar, as propriedades do framework definidas em FrameworkElement são processadas, o que afeta o valor de constraintSize. Essas propriedades geralmente descrevem as características de dimensionamento do subjacente UIElement, como suas Height, Width e MarginStyle. Cada uma dessas propriedades pode alterar o espaço necessário para exibir o elemento. Em seguida, MeasureOverride é chamado com constraintSize como um parâmetro.

Observação

Há uma diferença entre as propriedades de Height e Width eActualHeight.ActualWidth Por exemplo, a ActualHeight propriedade é um valor calculado com base em outras entradas de altura e no sistema de layout. O valor é definido pelo próprio sistema de layout, com base em um passe de renderização real e, portanto, pode ficar um pouco atrás do valor definido das propriedades, como Height, que são a base da alteração de entrada.

Como ActualHeight é um valor calculado, você deve estar ciente de que pode haver várias ou incrementais alterações relatadas nele como resultado de várias operações do sistema de layout. O sistema de layout pode estar calculando o espaço necessário de medida para os elementos filho, as restrições impostas pelo elemento pai e assim por diante.

O objetivo final da aprovação da medida é que a criança determine sua DesiredSize, o que ocorre durante a MeasureCore chamada. O valor DesiredSize é armazenado por Measure para uso durante a passagem de organização de conteúdo.

O processo de organização começa com uma chamada para o método Arrange. Durante a etapa de arranjo, o elemento pai Panel gera um retângulo que representa os limites do elemento filho. Esse valor é passado para o ArrangeCore método de processamento.

O ArrangeCore método avalia o DesiredSize filho e avalia quaisquer margens adicionais que possam afetar o tamanho renderizado do elemento. ArrangeCore gera um arrangeSize, que é passado como parâmetro para o método ArrangeOverride de Panel. ArrangeOverride gera o finalSize do filho. Por fim, o método ArrangeCore realiza uma avaliação final das propriedades de deslocamento, como margem e alinhamento, e posiciona o elemento dentro de seu espaço de layout. O filho não precisa (e frequentemente não) preencher todo o espaço alocado. Em seguida, o controle é retornado para o pai Panel e o processo de layout é concluído.

Elementos do painel e comportamentos de layout personalizados

O WPF inclui um grupo de elementos que derivam de Panel. Esses Panel elementos habilitam muitos layouts complexos. Por exemplo, o empilhamento de elementos pode ser facilmente obtido usando o elemento StackPanel, enquanto layouts mais complexos e fluidos são possíveis usando um Canvas.

A tabela a seguir resume os elementos de layout Panel disponíveis.

Nome do painel Descrição
Canvas Defina uma área dentro da qual você pode posicionar explicitamente elementos filhos por coordenadas relativas à área Canvas.
DockPanel Define uma área na qual você pode organizar elementos filho horizontal ou verticalmente, em relação uns aos outros.
Grid Define uma área de grade flexível que consiste em colunas e linhas.
StackPanel Organiza elementos filho em uma única linha que pode ser orientada horizontal ou verticalmente.
VirtualizingPanel Fornece uma estrutura para Panel elementos que virtualizam a coleta de dados filho. Esta é uma classe abstrata.
WrapPanel Posiciona elementos filho em posição sequencial da esquerda para a direita, quebrando o conteúdo para a próxima linha na borda da caixa de contenção. A ordenação subsequente ocorre sequencialmente de cima para baixo ou da direita para a esquerda, dependendo do valor da Orientation propriedade.

Para aplicativos que exigem um layout que não é possível usar com qualquer um dos elementos predefinidos Panel, comportamentos de layout personalizado podem ser obtidos herdando de Panel e substituindo os métodos MeasureOverride e ArrangeOverride.

Considerações sobre o desempenho do layout

Layout é um processo recursivo. Cada elemento filho em uma Children coleção é processado durante cada invocação do sistema de layout. Como resultado, o acionamento do sistema de layout deve ser evitado quando não for necessário. As considerações a seguir podem ajudá-lo a obter um melhor desempenho.

  • Lembre-se de quais alterações de valor de propriedade forçarão uma atualização recursiva pelo sistema de layout.

    As propriedades de dependência cujos valores podem fazer com que o sistema de layout seja inicializado são marcadas com sinalizadores públicos. AffectsMeasure e AffectsArrange fornecem pistas úteis sobre quais alterações nos valores de propriedades forçarão uma atualização recursiva pelo sistema de layout. Em geral, qualquer propriedade que possa afetar o tamanho da caixa delimitadora de um elemento deve ter um sinalizador AffectsMeasure definido como verdadeiro. Para obter mais informações, consulte Visão geral das propriedades de dependência.

  • Quando possível, use um RenderTransform em vez de um LayoutTransform.

    Uma LayoutTransform pode ser uma maneira muito útil de afetar o conteúdo de uma interface do usuário (UI). No entanto, se o efeito da transformação não precisar afetar a posição de outros elementos, é melhor usar um RenderTransform em vez disso, porque RenderTransform não invoca o sistema de layout. LayoutTransform aplica sua transformação e força uma atualização de layout recursivo a considerar a nova posição do elemento afetado.

  • Evite chamadas desnecessárias para UpdateLayout.

    O UpdateLayout método força uma atualização de layout recursivo e frequentemente não é necessário. A menos que você tenha certeza de que uma atualização completa é necessária, confie no sistema de layout para chamar esse método automaticamente.

  • Ao trabalhar com uma coleção grande Children, considere usar um VirtualizingStackPanel em vez de um regular StackPanel.

    Ao virtualizar a coleção de filhos, o VirtualizingStackPanel mantém na memória apenas os objetos que estão atualmente dentro do ViewPort do pai. Como resultado, o desempenho é substancialmente melhorado na maioria dos cenários.

Renderização de sub-pixel e arredondamento de layout

O sistema gráfico WPF usa unidades independentes de dispositivo para habilitar a resolução e a independência do dispositivo. Cada pixel independente do dispositivo é dimensionado automaticamente com a configuração de pontos por polegada (dpi) do sistema. Isso fornece dimensionamento adequado de aplicativos WPF para diferentes configurações de dpi e torna o aplicativo automaticamente com reconhecimento de dpi.

No entanto, essa independência de dpi pode criar irregularidade na renderização das bordas devido ao anti-aliasing. Esses artefatos, normalmente vistos como bordas desfocadas ou semi-transparentes, podem ocorrer quando a localização de uma borda cai no meio de um pixel de dispositivo em vez de entre pixels de dispositivo. O sistema de layout oferece uma maneira de ajustar isso através do arredondamento de layout. O arredondamento de layout ocorre quando o sistema de layout arredonda todos os valores de pixel não inteiros durante o processo de layout.

O arredondamento de layout é desabilitado por padrão. Para habilitar o arredondamento de layout, defina a UseLayoutRounding propriedade como true em qualquer FrameworkElement. Como é uma propriedade de dependência, o valor será propagado para todos os filhos na árvore visual. Para habilitar o arredondamento de layout para toda a interface do usuário, defina UseLayoutRounding para true no contêiner raiz. Para obter um exemplo, consulte UseLayoutRounding.

O que vem a seguir

Entender como os elementos são medidos e organizados é a primeira etapa na compreensão do layout. Para obter mais informações sobre os elementos disponíveis Panel , consulte Visão geral dos painéis. Para entender melhor as várias propriedades de posicionamento que podem afetar o layout, consulte Alinhamento, Margens e Visão Geral de Preenchimento. Quando você estiver pronto para montar tudo em um aplicativo leve, confira Passo a passo: meu primeiro aplicativo da área de trabalho do WPF.

Consulte também