Diseños dinámicos con XAML

El sistema de diseño XAML permite usar el ajuste de tamaño automático de los elementos, paneles de diseño y estados visuales como ayuda para crear una interfaz de usuario adaptativa. Con un diseño adaptativo, puedes hacer que la aplicación tenga un gran aspecto en pantallas con diferentes tamaños de ventana de aplicación, resoluciones, densidades de píxeles y orientaciones. También puedes usar XAML para cambiar la posición, ajustar el tamaño, redistribuir, mostrar u ocultar, reemplazar y rediseñar la interfaz de usuario de tu aplicación, como se explica en Técnicas de diseño con capacidad de respuesta. Aquí describimos cómo implementar diseños adaptativos con XAML.

Diseños fluidos con propiedades y paneles

La base de un diseño adaptativo es hacer un uso adecuado de las propiedades y los paneles de diseño de XAML para cambiar la posición, ajustar el tamaño y redistribuir el contenido de forma fluida.

El sistema de diseño de XAML admite diseños tanto estáticos como fluidos. En un diseño estático, se definen en los controles tamaños de píxeles y posiciones explícitas. Cuando el usuario cambia la resolución u orientación de su dispositivo, la interfaz de usuario no cambia. Los diseños estáticos pueden recortarse en distintos factores de forma y tamaños de pantalla. Por otro lado, los diseños fluidos se reducen, crecen y vuelven a fluir para responder al espacio visual disponible en un dispositivo.

En la práctica, usa una combinación de elementos estáticos y fluidos para crear la interfaz de usuario. Seguirás usando elementos y valores estáticos en algunas partes, pero debes asegurarte de que el conjunto de la interfaz de usuario se adapte a las distintas resoluciones, tamaños de pantalla y vistas.

Aquí describimos cómo usar las propiedades de XAML y los paneles de diseño para crear un diseño fluido.

Propiedades de diseño

Las propiedades de diseño controlan el tamaño y la posición de un elemento. Para crear un diseño fluido, usa variaciones de tamaño automáticas o proporcionales para los elementos, y permite que los paneles de diseño establezcan la posición de sus elementos secundarios según sea necesario.

Aquí se muestran algunas propiedades de diseño habituales y cómo usarlas para crear diseños fluidos.

Alto y ancho

Las propiedades Height y Width especifican el tamaño de un elemento. Puedes usar valores fijos medidos en píxeles efectivos o puedes usar la variación de tamaño automática o proporcional.

La variación de tamaño automática cambia el tamaño de los elementos de la interfaz de usuario de modo que se ajusten a su contenido o contenedor primario. La variación de tamaño automática también se puede usar con las filas y columnas de una cuadrícula. Para usar la variación de tamaño automática, establece las propiedades Height o Width de los elementos de interfaz de usuario en Auto.

Nota:

El hecho de que el tamaño de un elemento cambie para ajustarse a su contenido o contenedor depende de cómo el contenedor primario controla la variación de tamaño de sus elementos secundarios. Para más información, consulta Paneles de diseños más adelante en este artículo.

La variación de tamaño proporcional distribuye el espacio disponible entre las filas y columnas de una cuadrícula en proporciones ponderadas. En XAML, los valores de star se expresan como * (o n* para el tamaño ponderado star). Por ejemplo, para especificar que una columna es 5 veces más ancha que la segunda columna en un diseño de 2 columnas, use "5*" y "*" para las propiedades Width de los elementos ColumnDefinition .

En este ejemplo, se combinan variaciones de tamaño fijas, automáticas y proporcionales en una clase Grid con 4 columnas.

Columna Ajuste de tamaño Description
Columna_1 Auto La columna se ajustará a su contenido.
Columna_2 * Después de que se calculen las columnas Auto, la columna recibe parte del ancho restante. Columna_2 será la mitad de ancha que Columna_4.
Columna_3 44 La columna tendrá un ancho de 44 píxeles.
Columna_4 2* Después de que se calculen las columnas Auto, la columna recibe parte del ancho restante. Columna_4 será el doble de ancha que Columna_2.

El ancho de columna predeterminado es "*",, de modo que no es necesario establecer explícitamente el valor de la segunda columna.

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

En el Diseñador XAML de Visual Studio, el resultado tiene este aspecto.

Una cuadrícula de 4 columnas en el diseñador de Visual Studio

Para obtener el tamaño de un elemento en tiempo de ejecución, usa las propiedades de solo lectura ActualHeight y ActualWidth en lugar de Height y Width.

Restricciones de tamaño

Cuando usas la variación de tamaño automática en la interfaz de usuario, es posible que aún tengas que establecer restricciones en el tamaño de un elemento. Puedes establecer las propiedades MinWidth/MaxWidth y MinHeight/MaxHeight para especificar valores que limitan el tamaño de un elemento al tiempo que permiten un cambio de tamaño fluido.

En una Grid, MinWidth/MaxWidth también se puede usar con las definiciones de columna y MinHeight/MaxHeight se puede usar con las definiciones de fila.

Alineación

Usa las propiedades HorizontalAlignment y VerticalAlignment para especificar cómo un elemento se debe colocar en su contenedor primario.

  • Los valores de HorizontalAlignment son Left, Center, Right y Stretch.
  • Los valores de VerticalAlignment son Top, Center, Bottom y Stretch.

Mediante la alineación Stretch, puedes conseguir que los elementos llenen todo el espacio que se les proporciona en el contenedor primario. Stretch es el valor predeterminado para ambas propiedades de alineación. Sin embargo, algunos controles, como Button, invalidan este valor en su estilo predeterminado. Cualquier elemento que pueda tener elementos secundarios puede tratar el valor Stretch de las propiedades HorizontalAlignment y VerticalAlignment de manera exclusiva. Por ejemplo, un elemento que usa el valor predeterminado Stretch colocado en una Grid se ajusta para rellenar la celda que lo contiene. El mismo elemento se coloca en tamaños de Canvas según su contenido. Para obtener más información sobre cómo cada panel controla el valor Stretch, consulta el artículo paneles de diseño.

Para más información, consulta el artículo Alineación, margen y espaciado y las páginas de referencia HorizontalAlignment y VerticalAlignment.

Visibilidad

Puedes mostrar u ocultar un elemento si estableces su propiedad Visibility en uno de los valores de enumeración Visibility: Visible o Collapsed. Cuando un elemento tiene el estado Collapsed, no ocupa espacio en el diseño de la interfaz de usuario.

Puedes cambiar la propiedad Visibility de un elemento en código o en un estado visual. Cuando el objeto Visibility de un elemento cambia, también cambian todos sus elementos secundarios. Puedes reemplazar secciones de la interfaz de usuario al mostrar un panel mientras contraes otro.

Sugerencia

Si tienes elementos en la interfaz de usuario con el estado Collapsed de manera predeterminada, los objetos se seguirán creando en el inicio, aunque no sean visibles. Puede aplazar la carga de estos elementos hasta que se muestren mediante el atributo x:Load para retrasar la creación de los objetos. Esto puede mejorar el rendimiento del inicio. Para obtener más información, consulte atributo x:Load.

Recursos de estilo

No hace falta establecer cada valor individualmente en un control. Suele ser más eficiente agrupar valores de propiedad en un recurso Style y aplicar el Style a un control. Esto es especialmente cierto cuando necesites aplicar los mismos valores de propiedad a muchos controles. Para obtener más información sobre los estilos, consulta Controles de estilo.

Paneles de diseño

Para colocar objetos visuales, debes ponerlos en un panel u otro objeto contenedor. El marco de XAML proporciona diversas clases Panel como Canvas, Grid, RelativePanel y StackPanel, que sirven como contenedores y permiten colocar y organizar los elementos de la interfaz de usuario dentro de ellas.

La principal consideración al elegir un panel de diseño es cuál será la posición del panel y el tamaño de sus elementos secundarios. Es posible que también debas considerar cómo se colocarán los elementos secundarios cuando se superpongan.

Esta es una comparación de las principales funciones de los controles de panel que se proporcionan en el marco XAML.

Control de panel Descripción
Canvas Canvas no admite la interfaz de usuario fluida; se controlan todos los aspectos de posicionamiento y ajuste de tamaño de los elementos secundarios. Normalmente se usa para casos especiales, como la creación de gráficos o la definición de áreas estáticas pequeñas de una interfaz de usuario adaptativa de mayor tamaño. Puedes usar código o estados visuales para cambiar la posición de los elementos en tiempo de ejecución.
  • Los elementos se colocan de forma absoluta mediante las propiedades adjuntas Canvas.Top y Canvas.Left.
  • La disposición se puede especificar explícitamente mediante la propiedad adjunta Canvas.ZIndex.
  • Los valores Stretch de HorizontalAlignment/VerticalAlignment se omiten. Si el tamaño de un elemento no se establece explícitamente, su tamaño se ajusta a su contenido.
  • El contenido secundario no se recorta visualmente si su tamaño es mayor que el panel.
  • El contenido secundario no se ve restringido por los límites del panel.
  • Grid Grid admite la variación de tamaño fluida de elementos secundarios. Puedes usar código o estados visuales para cambiar la posición y redistribuir elementos.
  • Los elementos se organizan en filas y columnas mediante las propiedades adjuntas Grid.Row y Grid.Column.
  • Los elementos pueden abarcar varias filas y columnas mediante las propiedades adjuntas Grid.RowSpan y Grid.ColumnSpan.
  • Los valores Stretch de HorizontalAlignment/VerticalAlignment se respetan. Si el tamaño de un elemento no se establece explícitamente, se amplía para rellenar el espacio disponible en la celda de la cuadrícula.
  • El contenido secundario se recorta visualmente si su tamaño es mayor que el panel.
  • El tamaño del contenido se ve restringido por los límites del panel, por lo que el contenido desplazable muestra barras de desplazamiento si es necesario.
  • RelativePanel
  • Los elementos se organizan en relación con el borde o el centro del panel y en relación entre ellos.
  • Los elementos se colocan mediante una variedad de propiedades adjuntas que controlan la alineación del panel, la alineación de elementos de mismo nivel y la posición de los elemento del mismo nivel.
  • Los valores Stretch de HorizontalAlignment/VerticalAlignment se omiten, a menos que las propiedades adjuntas RelativePanel de la alineación provoquen una ampliación (por ejemplo, un elemento se alinea a los bordes derecho e izquierdo del panel). Si el tamaño de un elemento no se establece explícitamente y no se amplía, su tamaño se ajusta a su contenido.
  • El contenido secundario se recorta visualmente si su tamaño es mayor que el panel.
  • El tamaño del contenido se ve restringido por los límites del panel, por lo que el contenido desplazable muestra barras de desplazamiento si es necesario.
  • StackPanel
  • Los elementos se apilan en una única línea vertical u horizontal.
  • Los valores Stretch de HorizontalAlignment/VerticalAlignment se respetan en la dirección opuesta a la propiedad Orientation. Si el tamaño de un elemento no se establece explícitamente, se amplía para rellenar el ancho disponible (o el alto si la Orientation es Horizontal). En la dirección especificada por la propiedad Orientation, el tamaño de un elemento se ajusta a su contenido.
  • El contenido secundario se recorta visualmente si su tamaño es mayor que el panel.
  • El tamaño del contenido no está restringido por los límites del panel en la dirección especificada por la propiedad Orientation, por lo que el contenido desplazable se amplía más allá de los límites del panel y no se muestran barras de desplazamiento. Tienes que restringir explícitamente el alto (o el ancho) del contenido secundario para mostrar sus barras de desplazamiento.
  • VariableSizedWrapGrid
  • Los elementos se organizan en filas o en columnas que se ajustan automáticamente a una nueva fila o columna cuando se alcanza el valor MaximumRowsOrColumns.
  • La organización de los elementos en filas o en columnas se especifica mediante la propiedad Orientation.
  • Los elementos pueden abarcar varias filas y columnas mediante las propiedades adjuntas VariableSizedWrapGrid.RowSpan y VariableSizedWrapGrid.ColumnSpan.
  • Se omiten los valores extendidos de HorizontalAlignment y VerticalAlignment. El tamaño de los elementos se ajusta según se especifica en las propiedades ItemHeight y ItemWidth. Si no se establecen estas propiedades, toman sus valores del tamaño de la primera celda.
  • El contenido secundario se recorta visualmente si su tamaño es mayor que el panel.
  • El tamaño del contenido se ve restringido por los límites del panel, por lo que el contenido desplazable muestra barras de desplazamiento si es necesario.
  • Para obtener información detallada y ejemplos de estos paneles, consulta Paneles de diseño.

    Los paneles de diseño te permitirán organizar la interfaz de usuario en grupos lógicos de controles. Cuando los usas con valores de propiedad adecuados, obtienes algo de compatibilidad con la variación de tamaño automática, el cambio de posición y la redistribución de elementos de interfaz de usuario. Sin embargo, la mayoría de los diseños de interfaz de usuario requiere más modificaciones cuando hay cambios significativos en el tamaño de ventana. Para ello, puedes usar estados visuales.

    Diseños adaptativos con estados visuales y desencadenadores de estado

    Usa estados visuales para hacer modificaciones importantes a tu interfaz de usuario en relación con el tamaño de la ventana u otros cambios.

    Cuando el tamaño de la ventana de tu aplicación aumenta o disminuye más allá de una cantidad determinada, es recomendable cambiar las propiedades de diseño para cambiar la posición, cambiar el tamaño, redistribuir, mostrar o reemplazar secciones de la interfaz de usuario. Puedes definir diferentes estados visuales para la interfaz de usuario y aplicarlos cuando el ancho o el alto de la ventana cruce un umbral especificado.

    Un Objeto VisualState define los valores de propiedad que se aplican a un elemento cuando se encuentra en un estado determinado. Los estados visuales se agrupan en un VisualStateManager que aplica el VisualState adecuado cuando se cumplen las condiciones especificadas. Un elemento AdaptiveTrigger proporciona una forma fácil de establecer el umbral (también llamado "punto de interrupción") en el que se aplica un estado a XAML. También puede llamar al método VisualStateManager.GoToState en el código para aplicar el estado visual. En las secciones siguientes se muestran ejemplos de ambas alternativas.

    Establecer estados visuales en el código

    Para aplicar un estado visual a partir de código, llama al método VisualStateManager.GoToState. Por ejemplo, para aplicar un estado cuando la ventana de la aplicación tiene un tamaño determinado, controla el evento SizeChanged y llama a GoToState para aplicar el estado pertinente.

    Aquí, una clase VisualStateGroup contiene dos definiciones VisualState. La primera, DefaultState, está vacía. Cuando se aplica, se aplican los valores definidos en la página XAML. La segunda, WideState, cambia la propiedad DisplayMode de la SplitView a Inline y abre el panel. Este estado se aplica en el controlador de eventos SizeChanged si la ventana tiene un ancho superior a 640 píxeles efectivos.

    Nota:

    Windows no proporciona un método para que la aplicación detecte el dispositivo específico en el que esta se ejecuta. Puede avisarle a la familia de dispositivos (escritorio, etc.) en la que se ejecuta la aplicación, la resolución efectiva y la cantidad de espacio de pantalla disponible para la aplicación (el tamaño de la ventana de la aplicación). Se recomienda definir estados visuales para tamaños de pantalla y puntos de interrupción.

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

    Establecer estados visuales en el marcado XAML

    Antes de Windows 10, las definiciones de VisualState necesitaban objetos Storyboard para los cambios de propiedad y hacía falta llamar a GoToState en el código para aplicar el estado. Esto se muestra en el ejemplo anterior. Verás muchos ejemplos que usan esta sintaxis, o bien quizás ya tengas código existente que la usa.

    A partir de Windows 10, puedes usar la sintaxis Setter simplificada que se muestra aquí y puedes usar los objetos StateTrigger en el marcado XAML para aplicar el estado. Los desencadenadores de estado se usan para crear reglas simples que desencadenan automáticamente cambios de estado visual en respuesta a un evento de la aplicación.

    En este ejemplo se hace lo mismo que en el ejemplo anterior, pero se usa la sintaxis Setter simplificada en lugar de un Storyboard para definir los cambios de propiedad. Además, en lugar de llamar a GoToState, se usa el desencadenador de estado AdaptiveTrigger para aplicar el estado. Cuando usas desencadenadores de estado, no es necesario definir un DefaultState vacío. La configuración predeterminada se vuelve a aplicar automáticamente cuando las condiciones del desencadenador de estado ya no se cumplen.

    <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

    En el ejemplo anterior, la propiedad adjunta VisualStateManager.VisualStateGroups está establecida en el elemento Grid. Cuando usas objetos StateTrigger, para que los desencadenadores se apliquen automáticamente, asegúrate siempre de que VisualStateGroups se adjunte al primer elemento secundario de la raíz. (Aquí, Grid es el primer elemento secundario del elemento Page raíz).

    Sintaxis de propiedad adjunta

    En un VisualState, normalmente se establece un valor para una propiedad de control, o bien para una de las propiedades adjuntas del panel que contiene el control. Al establecer una propiedad adjunta, usa paréntesis alrededor del nombre de la propiedad adjunta.

    En este ejemplo se muestra cómo establecer la propiedad adjunta RelativePanel.AlignHorizontalCenterWithPanel en un TextBox denominado myTextBox. El primer código XAML usa la sintaxis ObjectAnimationUsingKeyFrames y el segundo usa 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"/>
    

    Desencadenadores de estado personalizados

    Puedes ampliar la clase StateTrigger y crear desencadenadores personalizados para una amplia variedad de escenarios. Por ejemplo, puedes crear un StateTrigger para desencadenar estados diferentes según el tipo de entrada y luego aumentar los márgenes alrededor de un control cuando el tipo de entrada es la entrada táctil. O bien, puedes crear un objeto StateTrigger para aplicar distintos estados en función de la familia de dispositivos en la que se ejecuta la aplicación. Para obtener ejemplos de cómo crear desencadenadores personalizados y usarlos para crear experiencias de interfaz de usuario optimizadas desde dentro de una única vista XAML, consulta la Muestra de desencadenadores de estado.

    Estados visuales y estilos

    Puedes usar los recursos "Style" de los estados visuales para aplicar un conjunto de cambios de propiedades a varios controles. Para obtener más información sobre los estilos, consulta Controles de estilo.

    En este código XAML simplificado de la muestra de desencadenadores de estado, se aplica un recurso "Style" a un objeto "Button" para ajustar el tamaño y los márgenes de la entrada de mouse o entrada táctil. Para obtener el código completo y la definición del desencadenador de estado personalizado, consulta Muestra de desencadenadores 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>