Layout

Este tópico descreve o sistema de layout do Windows Presentation Foundation (WPF). 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 um consumido pelo sistema de layout pode ser pensado como um retângulo que é encaixado no layout. A LayoutInformation classe retorna os limites da alocação de layout de um elemento, ou slot. O tamanho do retângulo é determinado calculando o espaço de tela disponível, o tamanho de quaisquer restrições, as propriedades específicas do 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.

Screenshot that shows a typical grid, no bounding box superimposed.

Esse layout pode ser obtido usando o seguinte XAML.

<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 Gridarquivo . 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 GetLayoutSlot método. A ilustração a seguir mostra a caixa delimitadora do TextBlock elemento .

Screenshot that shows that the TextBlock bounding box is now visible.

Como mostrado pelo retângulo amarelo, o espaço alocado para o TextBlock elemento é realmente 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 é traduzido 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 leva um elemento a ser dimensionado, posicionado e desenhado. Mais especificamente, o layout descreve o processo de medir e organizar os Children membros da coleção de um Panel elemento. O layout é um processo intensivo. Quanto maior a Children coleta, 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 proprietário da coleção. Um , relativamente simplesPanel, como , pode ter um desempenho significativamente melhor do que um mais complexoPanel, como CanvasGrid.

Cada vez que uma criança UIElement muda de posição, ela tem o potencial de desencadear uma nova passagem pelo sistema de layout. Portanto, é importante compreender os eventos que podem invocar o sistema de layout, pois invocações desnecessárias podem levar a desempenho ruim do aplicativo. O exemplo a seguir descreve o processo que ocorre quando o sistema de layout é invocado.

  1. Uma criança UIElement começa o processo de layout primeiro tendo suas propriedades principais medidas.

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

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

  4. O conteúdo é organizado depois que todos os filhos são medidos.

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

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

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

Medindo e organizando filhos

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

Durante a passagem da medida, cada membro da Children coleção é 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 da estrutura 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 seu Height, , WidthMargine Style. Cada uma dessas propriedades pode alterar o espaço necessário para exibir o elemento. MeasureOverride é então chamado com constraintSize como um parâmetro.

Observação

Há uma diferença entre as propriedades de Height e e e ActualHeightWidth .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 passo de renderização real, e pode, portanto, ficar um pouco atrás do valor definido de 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 pelo sistema de layout. O sistema de layout pode estar calculando o espaço de medição necessário para elementos filhos, as restrições do elemento pai e assim por diante.

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

O passe de organização começa com uma chamada para o Arrange método. Durante a passagem de organização, o elemento pai Panel gera um retângulo que representa os limites da criança. 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. ArrangeCoregera um , que é passado para o ArrangeOverridePanel método do como um arrangeSizeparâmetro. ArrangeOverride gera o finalSize da criança. Finalmente, o método faz uma avaliação final das propriedades de deslocamento, como margem e alinhamento, e coloca o ArrangeCore filho dentro de seu slot de layout. O filho não precisa preencher o espaço inteiro alocado (e geralmente não faz isso). O controle é retornado ao pai Panel e o processo de layout é concluído.

Elementos de painel e comportamentos de layout personalizados

WPF inclui um grupo de elementos que derivam de Panel. Esses Panel elementos permitem muitos layouts complexos. Por exemplo, elementos de empilhamento podem ser facilmente obtidos usando o StackPanel elemento , enquanto layouts mais complexos e de fluxo livre são possíveis usando um Canvasarquivo .

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

Nome do painel Descrição
Canvas Define uma área dentro da Canvas qual você pode posicionar explicitamente elementos filho por coordenadas em relação à área.
DockPanel Define uma área na qual é possível organizar os elementos filho horizontal ou verticalmente em relação um ao outro.
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 os elementos Panel que virtualizam os respectivos dados filho. Esta é uma classe abstrata.
WrapPanel Coloca os elementos filho na posição sequencial da esquerda para a direita, quebrando o conteúdo para a próxima linha na borda da caixa delimitadora. 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 usando qualquer um dos elementos predefinidos Panel , os comportamentos de Panel layout personalizados podem ser obtidos herdando e substituindo os MeasureOverride métodos e ArrangeOverride .

Considerações sobre desempenho de layout

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

  • Esteja ciente de quais alterações de valor da propriedade forçarão uma atualização recursiva realizada 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 fornecer pistas úteis sobre quais alterações no valor da propriedade 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 AffectsMeasure sinalizador definido como true. Para obter mais informações, consulte Visão geral sobre propriedades de dependência.

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

    A 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 tiver que 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 recursiva para levar em conta a nova posição do elemento afetado.

  • Evite chamadas desnecessárias para UpdateLayouto .

    O UpdateLayout método força uma atualização de layout recursiva 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 para você.

  • Ao trabalhar com uma coleção grandeChildren, considere usar um em vez de um StackPanelVirtualizingStackPanel arquivo .

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

Renderização subpixel 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 sensível ao dpi.

No entanto, essa independência de dpi pode criar renderização de borda irregular devido à suavização de serrilhado. Esses artefatos, geralmente vistos como bordas desfocadas ou semitransparentes, podem ocorrer quando o local de uma borda está no meio de um pixel de dispositivo, em vez de entre os pixels de dispositivo. O sistema de layout fornece uma maneira de compensar isso com o arredondamento de layout. O arredondamento de layout ocorre quando o sistema de layout arredonda valores de pixel não integrais durante a passagem de layout.

O arredondamento de layout é desabilitado por padrão. Para habilitar o arredondamento de layout, defina a UseLayoutRounding propriedade como true em qualquer FrameworkElementarquivo . Como essa é 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 como 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 é o primeiro passo para entender o layout. Para obter mais informações sobre os elementos disponíveis Panel , consulte Visão geral dos painéis. Para compreender melhor as várias propriedades de posicionamento que podem afetar o layout, consulte Visão geral de alinhamento, margens e preenchimento. Quando você estiver pronto para colocar tudo junto em um aplicativo leve, consulte Passo a passo: Meu primeiro aplicativo de desktop WPF.

Confira também