Recursos de temas XAML

Los recursos de tema en XAML son un conjunto de recursos que aplican diferentes valores dependiendo del tema del sistema que esté activo. Hay 3 temas que admite el marco XAML: "Claro", "Oscuro" y "Alto contraste".

Requisitos previos: en este tema se supone que ha leído referencias a recursos XAML y ResourceDictionary.

Recursos de tema v. recursos estáticos

Hay dos extensiones de marcado XAML que pueden hacer referencia a un recurso XAML desde un diccionario de recursos XAML existente: extensión de marcado {StaticResource} y extensión de marcado {ThemeResource}.

La evaluación de una extensión de marcado {ThemeResource} se produce cuando la aplicación se carga y, posteriormente, cada vez que el tema cambia en tiempo de ejecución. Este suele ser el resultado de que el usuario cambie la configuración del dispositivo o de un cambio mediante programación dentro de la aplicación que modifique su tema actual.

En contraste, una extensión de marcado {StaticResource} solo se evalúa cuando la aplicación ha cargado XAML por primera vez. No se actualiza. Es similar a buscar y reemplazar en el código XAML con el valor real en tiempo de ejecución en el inicio de la aplicación.

Recursos de temas en la estructura del diccionario de recursos

Cada recurso de tema es parte del archivo XAML themeresources.xaml. Con fines de diseño, themeresources.xaml está disponible en la carpeta \(Archivos de programa)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<versión del SDK>\Generic desde una instalación del Kit de desarrollo de software (SDK) de Windows. Los diccionarios de recursos de themeresources.xaml también se reproducen en generic.xaml en el mismo directorio.

Windows Runtime no usa estos archivos físicos para la búsqueda en tiempo de ejecución. Por este motivo, están específicamente en una carpeta DesignTime y no se copian a aplicaciones de manera predeterminada. En cambio, estos diccionarios de recursos existen en la memoria como parte del propio Windows Runtime y las referencias de los recursos XAML de la aplicación a recursos de tema (o recursos del sistema) se resuelven allí en tiempo de ejecución.

Directrices para recursos de temas personalizados

Sigue estas instrucciones al definir y consumir tus propios recursos de tema personalizados:

Precaución

Si no sigue estas directrices, es posible que vea un comportamiento inesperado relacionado con los temas en su aplicación. Para más información, consulte la sección Solución de problemas de recursos de tema.

La rampa de color XAML y los pinceles dependientes del tema

El conjunto combinado de colores para los temas "Claro", "Oscuro" y "Alto contraste" constituyen la Rampa de colores de Windows en XAML. Tanto si desea modificar los temas del sistema como aplicar un tema a sus propios elementos XAML, es importante comprender cómo están estructurados los recursos de color.

Para obtener más información acerca de cómo aplicar color en tu aplicación de Windows, consulta Color en aplicaciones de Windows.

Temas de colores Claro y Oscuro

El marco XAML proporciona un conjunto de recursos Color designados con valores diseñados exclusivamente para los temas "Light" y "Dark". Para WinUI 2, los recursos del tema se definen en el archivo Xaml de recursos de tema comunes. Los nombres de color son muy descriptivos de su uso previsto y hay un recurso SolidColorBrush correspondiente para cada recurso color.

Sugerencia

Para obtener información general visual sobre estos colores, consulte la aplicación Galería de WinUI 3: Colores.

La aplicación WinUI 3 Gallery incluye ejemplos interactivos de la mayoría de los controles, características y funcionalidades de WinUI 3. Obtenga la aplicación de Microsoft Store u obtenga el código fuente en GitHub.

Colores del tema de contraste del sistema Windows

Además del conjunto de recursos proporcionados por el marco XAML, hay un conjunto de valores de color derivado de la paleta del sistema de Windows. Estos colores no son específicos de las aplicaciones de Windows Runtime o de Windows. Sin embargo, muchos de los recursos XAML Brush consumen estos colores cuando el sistema está funcionando (y la aplicación está ejecutándose) con el tema "HighContrast". El marco XAML proporciona estos colores de todo el sistema como recursos con clave. Las claves siguen el formato de nombre:SystemColor[name]Color.

Para obtener más información acerca de cómo admitir temas de contraste, consulte Temas de contraste.

Color de énfasis del sistema

Además de los colores del tema de contraste del sistema, el color de énfasis del sistema se proporciona como un recurso de color especial mediante la clave SystemAccentColor. En tiempo de ejecución, este recurso toma el color que el usuario haya especificado como color de énfasis en la configuración de personalización de Windows.

Nota:

Aunque es posible anular los recursos de color del sistema, es una buena práctica respetar las opciones de color del usuario, especialmente para la configuración del tema de contraste.

Pinceles dependientes del tema

Los recursos de color que se muestran en las secciones anteriores se usan para establecer la propiedad Color de los recursos SolidColorBrush en los diccionarios de recursos de temas del sistema. Los recursos de pincel se usan para aplicar el color a los elementos XAML.

Veamos cómo se determina el valor del color de este pincel en tiempo de ejecución. En los diccionarios de recursos "Claro" y "Oscuro", este pincel se define así:

<SolidColorBrush x:Key="TextFillColorPrimaryBrush" Color="{StaticResource TextFillColorPrimary}"/>

En el diccionario de recursos " AltoContraste ", este pincel se define así:

<SolidColorBrush x:Key="TextFillColorPrimaryBrush" Color="{ThemeResource SystemColorWindowTextColor}"/>

Cuando este pincel se aplica a un elemento XAML, el tema actual determina su color en tiempo de ejecución, como se muestra en esta tabla.

Tema Recurso de color Valor en tiempo de ejecución
Claro TextFillColorPrimary #E4000000
Oscuro TextFillColorPrimary #FFFFFFFF
AltoContraste SystemColorWindowTextColor El color especificado en la configuración de Texto.

La rampa de tipos XAML

El archivo themeresources.xaml define varios recursos que definen un Style que puedes aplicar a los contenedores de texto en tu interfaz de usuario, específicamente para TextBlock o RichTextBlock. No son los estilos implícitos predeterminados. Se proporcionan para facilitar la creación de definiciones de interfaces de usuario de XAML que coincidan con la Rampa de tipos de Windows documentada en Directrices para fuentes.

Estos estilos son para atributos de texto que quieres aplicar a todo el contenedor de texto. Si quieres que los estilos se apliquen solo a secciones del texto, debes establecer los atributos en los elementos de texto dentro del contenedor, como en Run para TextBlock.Inlines o en Paragraph para RichTextBlock.Blocks.

Los estilos tienen este aspecto cuando se aplican a un elemento TextBlock:

estilos de bloque de texto

Estilo Peso Size
Caption La normal. 12
Body La normal. 14
Cuerpo fuerte Semibold 14
Cuerpo grande La normal. 18
Subtítulo Semibold 20
Título Semibold 28
Título grande Semibold 40
Mostrar Semibold 68
<TextBlock Text="Caption" Style="{StaticResource CaptionTextBlockStyle}"/>
<TextBlock Text="Body" Style="{StaticResource BodyTextBlockStyle}"/>
<TextBlock Text="Body Strong" Style="{StaticResource BodyStrongTextBlockStyle}"/>
<TextBlock Text="Body Large" Style="{StaticResource BodyLargeTextBlockStyle}"/>
<TextBlock Text="Subtitle" Style="{StaticResource SubtitleTextBlockStyle}"/>
<TextBlock Text="Title" Style="{StaticResource TitleTextBlockStyle}"/>
<TextBlock Text="Title Large" Style="{StaticResource TitleLargeTextBlockStyle}"/>
<TextBlock Text="Display" Style="{StaticResource DisplayTextBlockStyle}"/>

Para obtener instrucciones sobre cómo usar la rampa de tipos de Windows en la aplicación, consulta Tipografía en aplicaciones de Windows.

Para más detalles de los estilos XAML, ver WinUI en GitHub:

Sugerencia

Para obtener información general visual sobre estos colores, consulte la aplicación Galería de WinUI 3: Tipografía.

BaseRichTextBlockStyle

TargetType: RichTextBlock

Proporciona las propiedades comunes para todos los demás estilos contenedores de RichTextBlock.

<!-- Usage -->
<RichTextBlock Style="{StaticResource BaseRichTextBlockStyle}">
    <Paragraph>Rich text.</Paragraph>
</RichTextBlock>

<!-- Style definition -->
<Style x:Key="BaseRichTextBlockStyle" TargetType="RichTextBlock">
    <Setter Property="FontFamily" Value="Segoe UI"/>
    <Setter Property="FontWeight" Value="SemiBold"/>
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="TextTrimming" Value="None"/>
    <Setter Property="TextWrapping" Value="Wrap"/>
    <Setter Property="LineStackingStrategy" Value="MaxHeight"/>
    <Setter Property="TextLineBounds" Value="Full"/>
    <Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>

BodyRichTextBlockStyle

<!-- Usage -->
<RichTextBlock Style="{StaticResource BodyRichTextBlockStyle}">
    <Paragraph>Rich text.</Paragraph>
</RichTextBlock>

<!-- Style definition -->
<Style x:Key="BodyRichTextBlockStyle" TargetType="RichTextBlock" BasedOn="{StaticResource BaseRichTextBlockStyle}">
    <Setter Property="FontWeight" Value="Normal"/>
</Style>

Nota: Los estilos RichTextBlock no tienen todos los estilos de rampa de texto que tiene TextBlock, principalmente porque el modelo de objetos de documento basado en bloques de RichTextBlock permite establecer atributos con mayor facilidad en los elementos de texto individuales. Además, al establecer TextBlock.Text mediante la propiedad de contenido XAML se crea una situación en la que no hay ningún elemento de texto para el estilo y, por tanto, tendrías que aplicar estilo al contenedor. Esto no supone ningún problema para RichTextBlock, porque su contenido de texto siempre tiene que estar en elementos de texto específicos, como Paragraph, que es donde probablemente aplicarás los estilos XAML para el encabezado de página, el subtítulo de página y las definiciones de rampa de texto similares.

Varios estilos con nombre

Existe un conjunto adicional de definiciones Style con clave que se puede usar para aplicar estilo a un elemento Button de forma diferente a su estilo implícito predeterminado.

TargetType: Botón

Este elemento Style proporciona una plantilla completa para un elemento Button que puede ser el botón de retroceso en la navegación para una aplicación de navegación. Las dimensiones predeterminadas son 40 x 40 píxeles. Para personalizar el estilo puedes establecer explícitamente Height, Width, FontSize y otras propiedades en tu elemento Button o crear un estilo derivado mediante BasedOn.

Este es un elemento Button al que se ha aplicado el recurso NavigationBackButtonNormalStyle.

<Button Style="{StaticResource NavigationBackButtonNormalStyle}" />

Tiene este aspecto:

Un botón con estilo de botón atrás

TargetType: Botón

Este elemento Style proporciona una plantilla completa para un elemento Button que puede ser el botón de retroceso en la navegación para una aplicación de navegación. Es similar a NavigationBackButtonNormalStyle, pero con unas dimensiones de 30 x 30 píxeles.

Este es un elemento Button al que se ha aplicado el recurso NavigationBackButtonSmallStyle.

<Button Style="{StaticResource NavigationBackButtonSmallStyle}" />

Recursos temáticos para la solución de problemas

Si no sigue las directrices para usar recursos de temas, es posible que vea un comportamiento inesperado en relación con los temas de su aplicación.

Por ejemplo, cuando abre un control flotante con temas claros, partes de la aplicación con temas oscuros también cambian como si estuvieran en el tema claro. O si navega a una página con temas claros y, a continuación, retrocede en la navegación, la página original con temas oscuros (o partes de él) ahora tienen una apariencia como si estuvieran en un tema claro.

Normalmente, estos tipos de problemas se producen al proporcionar un tema "Predeterminado" y un tema "AltoContraste" para admitir escenarios de contraste alto y, a continuación, usar ambos temas, "Claro" y "Oscuro", en distintas partes de la aplicación.

Por ejemplo, tenga en cuenta esta definición del diccionario de temas:

<!-- DO NOT USE. THIS XAML DEMONSTRATES AN ERROR. -->
<ResourceDictionary>
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Default">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource SystemColorButtonFaceColor}"/>
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

Intuitivamente, esto parece correcto. Quiere cambiar el color apuntado pormyBrush cuando está en alto contraste, pero cuando no está en alto contraste, se basa en la extensión de marcado {ThemeResource}para asegurarse de que myBrush apunta al color correcto para su tema. Si la aplicación nunca tiene FrameworkElement.RequestedTheme establecido en elementos dentro de su árbol visual, esto normalmente funcionará según lo esperado. Sin embargo, se encontrará con problemas en su aplicación en cuanto empiece a cambiar el tema de diferentes partes de su árbol visual.

El problema se produce porque los pinceles son recursos compartidos, a diferencia de la mayoría del resto de tipos XAML. Si tiene 2 elementos en subárboles XAML con diferentes temas que hacen referencia al mismo recurso de pincel, entonces como el marco recorre cada subárbol para actualizar sus expresiones de extensión de marcado {ThemeResource}, los cambios que se producen en el recurso compartido de pincel se reflejan en el otro subárbol, que no es el resultado previsto.

Para corregir esto, reemplace el diccionario "Predeterminado" por diccionarios de temas independientes para los temas "Claro" y "Oscuro" además de "AltoContraste":

<!-- DO NOT USE. THIS XAML DEMONSTRATES AN ERROR. -->
<ResourceDictionary>
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Light">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource SystemColorButtonFaceColor}"/>
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

Sin embargo, seguirán produciéndose problemas si se hace referencia a cualquiera de estos recursos en propiedades heredadas como Foreground. La plantilla de control personalizado puede especificar el color de primer plano de un elemento usando la extensión de marcado {ThemeResource}, pero cuando el marco propaga el valor heredado a los elementos secundarios, proporciona una referencia directa al recurso que resuelve la expresión de la extensión de marcado {ThemeResource}. Esto provoca problemas cuando el marco procesa los cambios de tema a medida que recorre el árbol visual del control. Reevalúa la expresión de la extensión de marcado {ThemeResource} para obtener un nuevo recurso de pincel, pero aún no propaga esta referencia a los elementos secundarios del control; esto sucede más adelante, por ejemplo durante el pase de medición siguiente.

Como resultado, después de recorrer el árbol visual del control en respuesta a un cambio de tema, el marco recorre los elementos secundarios y actualiza cualquier expresión de laextensión de marcado {ThemeResource}que se establezca en estos o en los objetos establecidos en sus propiedades. En este punto se produce el problema: el marco recorre el recurso de pincel y como especifica su color usando una extensión de marcado {ThemeResource}, se vuelve a evaluar.

En este punto, el marco parece haber contaminado su diccionario de temas porque ahora tiene un recurso de un diccionario que tiene su color establecido de otro diccionario.

Para solucionar este problema, use la extensión de marcado {StaticResource} en lugar de la extensión de marcado {ThemeResource}. Una vez aplicadas las directrices, los diccionarios de temas tienen este aspecto:

<ResourceDictionary>
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Light">
      <SolidColorBrush x:Key="myBrush" Color="{StaticResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
      <SolidColorBrush x:Key="myBrush" Color="{StaticResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource SystemColorButtonFaceColor}"/>
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

Observe que la extensión de marcado {ThemeResource} se sigue utilizando en el diccionario "AltoContraste" en lugar de la extensión de marcado {StaticResource}. Esta situación está incluida en la excepción descrita anteriormente en las directrices. La mayoría de los valores que se usan para el tema "AltoContraste" emplean opciones de colores que están controladas por el sistema de manera global, pero que se exponen a XAML como un recurso con nombre especial (aquellos con el prefijo 'SystemColor' en el nombre). El sistema permite al usuario establecer los colores específicos que se deben usar para su configuración del tema de contraste a través del Centro de accesibilidad. Estas opciones de color se aplican a los recursos con nombre especial. El marco XAML también usa el mismo evento de tema cambiado para actualizar estos pinceles cuando detecta que han cambiado en el nivel del sistema. Este es el motivo por el que se usa aquí la extensión de marcado {ThemeResource}.