Layout

En este tema se describe el sistema de diseño de Windows Presentation Foundation (WPF). Comprender cómo y cuándo se producen los cálculos de diseño es esencial para crear interfaces de usuario en WPF.

Este tema contiene las siguientes secciones:

Rectángulos de selección de elementos

Al pensar en diseño en WPF, es importante entender el rectángulo de selección que rodea todos los elementos. Cada elemento FrameworkElement que consume el sistema de diseño puede considerarse un rectángulo asignado en el diseño. La clase LayoutInformation devuelve los límites de asignación de diseño de un elemento o ranura. Para determinar el tamaño del rectángulo, se calcula el espacio disponible en pantalla, el tamaño de las restricciones, las propiedades específicas del diseño (como los márgenes y el espaciado interno) y el comportamiento individual del elemento primario Panel. Al procesar estos datos, el sistema de diseño puede calcular la posición de todos los elementos secundarios de un elemento Panel determinado. Es importante recordar que las características de ajuste de tamaño que se definen en el elemento primario, como Border, afectan a sus elementos secundarios.

En la siguiente ilustración se muestra un diseño sencillo.

Captura de pantalla que muestra una cuadrícula típica, sin rectángulo delimitador superpuesto.

Este diseño se puede lograr con el siguiente código 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>

Un único elemento TextBlock se hospeda dentro de Grid. Aunque el texto rellena solo la esquina superior izquierda de la primera columna, el espacio asignado para TextBlock es mucho mayor. El rectángulo delimitador de cualquier FrameworkElement se puede recuperar mediante el método GetLayoutSlot. En la ilustración siguiente se muestra el rectángulo delimitador del elemento TextBlock.

Captura de pantalla que muestra que el rectángulo delimitador TextBlock ahora está visible.

Como se muestra en el rectángulo amarillo, el espacio asignado para el elemento TextBlock es mucho mayor de lo que parece. A medida que se agregan elementos adicionales a Grid, esta asignación se puede reducir o expandir, en función del tipo y el tamaño de los elementos que se agregan.

El diseño de la ranura de TextBlock se convierte en Path mediante el método GetLayoutSlot. Esta técnica puede ser útil para mostrar el rectángulo de selección de un 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

El sistema de diseño

En su forma más simple, el diseño es un sistema recursivo que permite dibujar y situar un elemento, así como cambiar su tamaño. En concreto, el diseño describe el proceso de medir y organizar los miembros de una colección de Children de un elemento de Panel. El diseño es un proceso intensivo. Cuanto mayor sea la colección de Children, mayor será el número de cálculos que se deben realizar. También se puede introducir complejidad según el comportamiento de diseño definido por el elemento Panel al que pertenece la colección. Un Panel relativamente simple, como Canvas, puede tener un rendimiento significativamente mejor que un Panel más complejo, como Grid.

Cada vez que un elemento secundario UIElement cambia de posición, tiene potencial para desencadenar un nuevo paso por el sistema de diseño. Por lo tanto, es importante comprender los eventos que pueden invocar el sistema de diseño, ya que una invocación innecesaria puede provocar un bajo rendimiento de la aplicación. A continuación, se describe el proceso que se produce cuando se invoca el sistema de diseño.

  1. Para empezar el proceso de diseño, se miden las propiedades del núcleo de un elemento secundario UIElement.

  2. Las propiedades de ajuste de tamaño definidas en FrameworkElement se evalúan, como Width, Height y Margin.

  3. Se aplica una lógica específica Panel, como la dirección Dock o el apilamiento Orientation.

  4. El contenido se organiza después de medir todos los elementos secundarios.

  5. La colección de Children se dibuja en la pantalla.

  6. El proceso se vuelve a invocar si se agregan otros Children a la colección, se aplica un LayoutTransform o se llama al método UpdateLayout.

Este proceso y cómo se invoca se definen con más detalle en las secciones siguientes.

Medición y organización de elementos secundarios

El sistema de diseño completa dos pases por cada miembro de la colección de Children: un pase de medición y un pase de organización. Cada Panel secundario proporciona sus propios métodos MeasureOverride y ArrangeOverride para lograr su propio comportamiento de diseño específico.

Durante el paso de medición, cada miembro de la colección de Children se evalúa. El proceso comienza con una llamada al método Measure. Se llama a este método desde la implementación del elemento Panel primario y no es necesario llamarlo explícitamente para que se produzca el diseño.

Primero, las propiedades nativas de tamaño de UIElement se evalúan, como Clipy Visibility. Esto genera un valor denominado constraintSize que se pasa a MeasureCore.

En segundo lugar, se procesan las propiedades de marco de trabajo definidas en FrameworkElement, lo que afecta al valor de constraintSize. Estas propiedades suelen describir las características de ajuste de dimensionamiento del UIElement subyacente, como su Height, Width, Margin y Style. Cada una de estas propiedades puede cambiar el espacio necesario para mostrar el elemento. Después, se llama a MeasureOverride con constraintSize como parámetro.

Nota

Hay una diferencia entre las propiedades de Height y Width y ActualHeight y ActualWidth. Por ejemplo, la propiedad ActualHeight es un valor calculado que se basa en otras entradas de altura y el sistema de diseño. El propio sistema de diseño define el valor en función del paso de representación real y puede, por tanto, quedarse ligeramente por detrás del valor de conjunto de las propiedades, como Height, que son la base del cambio de entrada.

Dado que ActualHeight es un valor calculado, debe tener en cuenta que puede haber varios cambios o cambios incrementales como resultado de varias operaciones por parte del sistema de diseño. El sistema de diseño puede calcular el espacio de medida necesario para los elementos secundarios, las restricciones impuestas por el elemento primario y así sucesivamente.

El objetivo final del paso de medición es que el elemento secundario determine su DesiredSize, lo que tiene lugar durante la llamada a MeasureCore. Measure almacena el valor DesiredSize para usarlo durante el pase de organización de contenido.

El pase de organización comienza con una llamada al método Arrange. Durante el pase de organización, el elemento Panel primario genera un rectángulo que representa los límites del elemento secundario. Este valor se pasa al método ArrangeCore para procesarse.

El método ArrangeCore evalúa el DesiredSize del elemento secundario y cualquier margen adicional que pueda afectar al tamaño representado del elemento. ArrangeCore genera un arrangeSize, que se pasa al método ArrangeOverride de Panel como parámetro. ArrangeOverride genera el finalSize del elemento secundario. Por último, el método ArrangeCore realiza una evaluación final de las propiedades de desplazamiento, como los márgenes y la alineación, y coloca el elemento secundario dentro de su ranura de diseño. El elemento secundario no tiene que ocupar todo el espacio asignado y con frecuencia no lo hace. A continuación, se devuelve el control al elemento Panel primario y se completa el proceso de diseño.

Elementos de panel y comportamientos de diseño personalizados

WPF incluye un grupo de elementos derivados de Panel. Estos elementos Panel permiten numerosos diseños complejos. Por ejemplo, se pueden apilar elementos fácilmente mediante el elemento StackPanel, mientras que otros diseños de flujo más libres y complejos son posibles mediante el uso de Canvas.

En la siguiente tabla se resumen los elementos Panel de diseño disponibles.

Nombre del panel Descripción
Canvas Define un área en la cual puede colocar elementos secundarios explícitamente mediante coordenadas relativas al área de Canvas.
DockPanel Define un área en la que se pueden organizar elementos secundarios de forma horizontal o vertical, relacionados entre sí.
Grid Define un área de cuadrícula flexible que consta de columnas y filas.
StackPanel Organiza elementos secundarios en una sola línea que puede orientarse horizontal o verticalmente.
VirtualizingPanel Proporciona un marco para elementos Panel que virtualizan su recolección de datos secundarios. Esta es una clase abstracta.
WrapPanel Coloca los elementos secundarios en posición secuencial de izquierda a derecha y traslada el contenido a la línea siguiente en el borde del cuadro contenedor. La clasificación siguiente se realiza secuencialmente de arriba abajo o de izquierda a derecha, en función del valor de la propiedad Orientation.

Para aplicaciones que requieren un diseño que no es posible utilizando cualquiera de los elementos Panel predefinidos, se pueden conseguir comportamientos de diseño personalizados mediante la herencia de Panel y la invalidación de los métodos MeasureOverride y ArrangeOverride.

Consideraciones del rendimiento del diseño

El diseño es un proceso recursivo. Cada elemento secundario de una colección de Children se procesa durante cada invocación del sistema de diseño. Como resultado, se debería evitar desencadenar el sistema de diseño cuando no sea necesario. Las consideraciones siguientes pueden ayudarle a lograr un mejor rendimiento.

  • Tenga en cuenta qué cambios de valor de propiedad forzarán una actualización recursiva por parte del sistema de diseño.

    Las propiedades de dependencia cuyos valores pueden provocar la inicialización del sistema de diseño se marcan con marcas públicas. AffectsMeasure y AffectsArrange proporcionan pistas útiles sobre qué cambios en los valores de las propiedades forzarán una actualización recursiva por parte del sistema de diseño. En general, cualquier propiedad que pueda afectar al tamaño del rectángulo de selección de un elemento debe tener una marca AffectsMeasure establecida en true. Para obtener más información sobre las propiedades de dependencia, vea Información general sobre las propiedades de dependencia.

  • Cuando sea posible, utilice RenderTransform en lugar de LayoutTransform.

    Un LayoutTransform puede ser una forma muy útil de afectar al contenido de una interfaz de usuario (UI). Sin embargo, si el efecto de la transformación no tiene que impactar en la posición de otros elementos, es mejor utilizar RenderTransform en su lugar, porque RenderTransform no invoca el sistema de diseño. LayoutTransform aplica su transformación y fuerza una actualización recursiva del diseño para tener en cuenta la nueva posición del elemento afectado.

  • Evite las llamadas innecesarias a UpdateLayout.

    El método UpdateLayout obliga a una actualización recursiva del diseño, y a menudo no es necesario. A menos que esté seguro de que se requiere una actualización completa, confíe en el sistema de diseño para que llame a este método.

  • Cuando trabaje con una colección grande de Children, considere usar VirtualizingStackPanel en lugar de StackPanel normal.

    Al virtualizar la colección secundaria, VirtualizingStackPanel solo mantiene los objetos en memoria que están actualmente en la ventanilla del elemento primario. Como resultado, el rendimiento se mejora sustancialmente en la mayoría de escenarios.

Representación de subpíxeles y redondeo del diseño

El sistema de gráficos de WPF usa unidades independientes del dispositivo para habilitar la resolución y la independencia del dispositivo. Cada píxel independiente del dispositivo se escala automáticamente con la configuración de puntos por pulgada (ppp) del sistema. Esto proporciona a las aplicaciones WPF un escalado adecuado para diferentes configuraciones de ppp y hace que la aplicación detecte el valor de los ppp automáticamente.

Sin embargo, esta independencia de los ppp puede crear representaciones de bordes irregulares debido al suavizado de contorno. Estos artefactos, que suelen aparecer como bordes borrosos o semitransparentes, pueden mostrarse cuando un borde se encuentra en medio de un píxel de dispositivo, en lugar de entre píxeles de dispositivo. El sistema de diseño proporciona una manera de ajustarlo con el redondeo del diseño. El redondeo del diseño se produce cuando el sistema de diseño redondea los valores de píxel no enteros durante el cálculo de diseño.

De forma predeterminada, el redondeo de diseño está deshabilitado. Para habilitar el redondeo del diseño, establezca la propiedad UseLayoutRounding en true en cualquier FrameworkElement. Dado que es una propiedad de dependencia, el valor se propagará a todos los elementos secundarios del árbol visual. Para activar el redondeo del diseño para toda la interfaz de usuario, establezca UseLayoutRounding en true en el contenedor raíz. Para obtener un ejemplo, consulte UseLayoutRounding.

Pasos siguientes

Comprender cómo se miden y organizan los elementos es el primer paso para entender el diseño. Para obtener más información sobre los elementos Panel disponibles, vea Información general sobre los paneles. Para entender mejor las diversas propiedades de posición que pueden afectar al diseño, consulte Información general sobre alineación, márgenes y relleno. Cuando esté listo para reunirlo todo en una aplicación ligera, vea Tutorial: Introducción a WPF.

Vea también