Compartir a través de


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 XAML admite diseños estáticos y fluidos. En un diseño estático, se proporcionan controles explícitos tamaños y posiciones de píxeles. Cuando el usuario cambia la resolución o la orientación de su dispositivo, la interfaz de usuario no cambia. Los diseños estáticos se pueden recortar en diferentes factores de forma y tamaños de presentación. 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, se 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. Puede usar valores fijos medidos en píxeles efectivos o puede usar el ajuste de tamaño automático 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. También puede usar el ajuste de tamaño automático con las filas y columnas de una cuadrícula. Para usar el ajuste de tamaño automático, establezca alto o ancho de los elementos de la interfaz de usuario en Automático.

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 estrella se expresan como * (o n* para el ajuste de tamaño de estrella ponderado). Por ejemplo, para especificar que una columna es 5 veces más amplia 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 el ajuste de tamaño fijo, automático y proporcional en una cuadrícula con 4 columnas.

Columna Dimensionamiento Descripción
Column_1 Automático La columna se ajustará a su contenido.
Column_2 * Una vez calculadas las columnas Automáticas, la columna obtiene parte del ancho restante. Column_2 será tan ancho como Column_4.
Column_3 44 La columna tendrá un ancho de 44 píxeles.
Column_4 2* Una vez calculadas las columnas Automáticas, la columna obtiene parte del ancho restante. Column_4 será el doble de ancho que Column_2.

El ancho de columna predeterminado es "*", por lo que no es necesario establecer explícitamente este valor para 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.

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

Al usar el ajuste de tamaño automático en la interfaz de usuario, es posible que tenga que colocar restricciones en el tamaño de un elemento. Puede establecer las propiedades MinWidth MaxWidth/ y MinHeight/MaxHeight para especificar valores que restrinjan el tamaño de un elemento al tiempo que permiten el cambio de tamaño fluido.

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

Alineación

Use las propiedades HorizontalAlignment y VerticalAlignment para especificar cómo se debe colocar un elemento dentro de su contenedor primario.

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

Con la alineación Stretch , los elementos rellenan todo el espacio que se proporcionan 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 forma única. Por ejemplo, un elemento que usa los valores de Stretch predeterminados colocados en una cuadrícula se extiende para rellenar la celda que la contiene. El mismo elemento colocado en un tamaño canvas en su contenido. Para obtener más información sobre cómo cada panel controla el valor stretch, consulte el artículo Paneles de diseño.

Para obtener más información, consulte el artículo Alineación, margen y relleno, así como las páginas de referencia HorizontalAlignment y VerticalAlignment.

Visibilidad

Puede mostrar u ocultar un elemento estableciendo su propiedad Visibility en uno de los valores de enumeración Visibility: Visible o Collapsed. Cuando un elemento está contraído, no ocupa ningún espacio en el diseño de la interfaz de usuario.

Puede cambiar la propiedad Visibility de un elemento en el código o en un estado visual. Cuando se cambia la visibilidad de un elemento, también se cambian todos sus elementos secundarios. Puede reemplazar las secciones de la interfaz de usuario revelando un panel mientras se contrae 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 de inicio. Para obtener más información, consulte el atributo x:Load.

Recursos de estilo

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

Paneles de diseño

Para colocar objetos visuales, debe colocarlos en un panel u otro objeto contenedor. El marco XAML proporciona varias clases de panel, como Canvas, Grid, RelativePanel y StackPanel, que sirven como contenedores y te permiten colocar y organizar los elementos de la interfaz de usuario dentro de ellos.

Lo principal que hay que tener en cuenta al elegir un panel de diseño es cómo el panel coloca y ajusta el tamaño de sus elementos secundarios. También es posible que tenga que tener en cuenta cómo se superponen los elementos secundarios superpuestos entre sí.

Esta es una comparación de las características principales de los controles de panel proporcionados en el marco XAML.

Control de panel Descripción
Lienzo 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 para definir áreas estáticas pequeñas de una interfaz de usuario adaptable más grande. Puede usar el código o los estados visuales para cambiar la posición de los elementos en tiempo de ejecución.
  • Los elementos se colocan absolutamente mediante propiedades adjuntas Canvas.Top y Canvas.Left.
  • La capa se puede especificar explícitamente mediante la propiedad adjunta Canvas.ZIndex.
  • Se omiten los valores extendidos de HorizontalAlignment/VerticalAlignment. Si el tamaño de un elemento no se establece explícitamente, cambia el tamaño a su contenido.
  • El contenido secundario no se recorta visualmente si es mayor que el panel.
  • El contenido secundario no está restringido por los límites del panel.
  • Grid Grid admite el cambio de tamaño fluido de los elementos secundarios. Puede usar estados visuales o de código para cambiar la posición y los elementos de flujo.
  • 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.
  • Se respetan los valores extendidos de HorizontalAlignment/VerticalAlignment. Si el tamaño de un elemento no se establece explícitamente, se extiende para rellenar el espacio disponible en la celda de cuadrícula.
  • El contenido secundario se recorta visualmente si es mayor que el panel.
  • El tamaño del contenido está restringido por los límites del panel, por lo que el contenido desplazable muestra las 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 sí.
  • Los elementos se colocan mediante una variedad de propiedades adjuntas que alinean el panel de control, alineación del mismo nivel y posición del mismo nivel.
  • Los valores extendidos de HorizontalAlignment/VerticalAlignment se omiten a menos que las propiedades adjuntas RelativePanel para la alineación provoquen el ajuste (por ejemplo, un elemento se alinea con los bordes derecho e izquierdo del panel). Si el tamaño de un elemento no se establece explícitamente y no se ajusta, se ajusta a su contenido.
  • El contenido secundario se recorta visualmente si es mayor que el panel.
  • El tamaño del contenido está restringido por los límites del panel, por lo que el contenido desplazable muestra las barras de desplazamiento si es necesario.
  • StackPanel
  • Los elementos se apilan en una sola línea vertical o horizontalmente.
  • Los valores stretch para 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 extiende para rellenar el ancho disponible (o alto si la orientación es Horizontal). En la dirección especificada por la propiedad Orientation, un elemento se dimensiona en su contenido.
  • El contenido secundario se recorta visualmente si 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 extiende más allá de los límites del panel y no muestra barras de desplazamiento. Debe restringir explícitamente el alto (o ancho) del contenido secundario para que se muestren sus barras de desplazamiento.
  • VariableSizedWrapGrid
  • Los elementos se organizan en filas o columnas que se ajustan automáticamente a una nueva fila o columna cuando se alcanza el valor MaximumRowsOrColumns.
  • Si los elementos se organizan en filas o columnas se especifican 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 stretch de HorizontalAlignment y VerticalAlignment. Los elementos tienen el tamaño especificado por 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 es mayor que el panel.
  • El tamaño del contenido está restringido por los límites del panel, por lo que el contenido desplazable muestra las barras de desplazamiento si es necesario.
  • Para obtener información detallada y ejemplos de estos paneles, consulte Paneles de diseño.

    Los paneles de diseño permiten organizar la interfaz de usuario en grupos lógicos de controles. Al usarlos con la configuración de propiedades adecuada, obtendrá cierta compatibilidad con el cambio de tamaño automático, el cambio de posición y el flujo de elementos de la interfaz de usuario. Sin embargo, la mayoría de los diseños de interfaz de usuario necesitan modificaciones adicionales cuando hay cambios significativos en el tamaño de la ventana. Para ello, puede usar estados visuales.

    Diseños adaptativos con estados visuales y desencadenadores de estado

    Use estados visuales para realizar modificaciones significativas en la interfaz de usuario en función del tamaño de la ventana u otros cambios.

    Cuando la ventana de la aplicación crece o se reduce más allá de una cantidad determinada, es posible que quiera modificar las propiedades de diseño para cambiar la posición, cambiar el tamaño, el flujo, mostrar o reemplazar secciones de la interfaz de usuario. Puede definir diferentes estados visuales para la interfaz de usuario y aplicarlos cuando el ancho de la ventana o el alto de la ventana cruza 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 objeto 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.

    Establecimiento de estados visuales en el código

    Para aplicar un estado visual desde el código, llame al método VisualStateManager.GoToState. Por ejemplo, para aplicar un estado cuando la ventana de la aplicación es un tamaño determinado, controle el evento SizeChanged y llame a GoToState para aplicar el estado adecuado.

    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 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, etcetera) 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 marcado XAML

    Antes de Windows 10, las definiciones de VisualState requerían objetos Storyboard para los cambios de propiedad y tenías que llamar a GoToState en el código para aplicar el estado. Esto se muestra en el ejemplo anterior. Todavía verá muchos ejemplos que usan esta sintaxis, o puede que tenga código existente que lo use.

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

    Este ejemplo hace lo mismo que el ejemplo anterior, pero usa la sintaxis setter simplificada en lugar de un Guión gráfico para definir los cambios de propiedad. Y en lugar de llamar a GoToState, usa el desencadenador de estado AdaptiveTrigger integrado para aplicar el estado. Cuando se usan desencadenadores de estado, no es necesario definir un vacío DefaultState. La configuración predeterminada se vuelve a aplicar automáticamente cuando ya no se cumplen las condiciones del desencadenador de estado.

    <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. Al usar StateTriggers, asegúrese siempre de que VisualStateGroups esté asociado al primer elemento secundario de la raíz para que los desencadenadores surtan efecto automáticamente. (Aquí, Grid es el primer elemento secundario del elemento Page raíz).

    Sintaxis de propiedad adjunta

    En visualState, normalmente se establece un valor para una propiedad de control o para una de las propiedades adjuntas del panel que contiene el control. Al establecer una propiedad adjunta, use 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 XAML usa la sintaxis ObjectAnimationUsingKeyFrames y la segunda usa la sintaxis 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 personalizado

    Puede ampliar la clase StateTrigger para crear desencadenadores personalizados para una amplia gama de escenarios. Por ejemplo, puede crear un StateTrigger para desencadenar diferentes estados en función del tipo de entrada y, a continuación, aumentar los márgenes alrededor de un control cuando el tipo de entrada sea táctil. O bien, cree un StateTrigger para aplicar diferentes 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 una sola vista XAML, consulta el ejemplo de desencadenadores de estado.

    Estados y estilos visuales

    Puede usar recursos de estilo en estados visuales para aplicar un conjunto de cambios de propiedad a varios controles. Para obtener más información sobre el uso de estilos, consulta Controles de estilo.

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