Aplicación de estilo a aplicaciones con XAML

Las aplicaciones .NET Multi-Platform App UI (.NET MAUI) suelen contener varios controles que tienen un aspecto idéntico. Por ejemplo, una aplicación puede tener varias instancias de Label que tienen las mismas opciones de fuente y opciones de diseño:

<Label Text="These labels"
       HorizontalOptions="Center"
       VerticalOptions="Center"
       FontSize="18" />
<Label Text="are not"
       HorizontalOptions="Center"
       VerticalOptions="Center"
       FontSize="18" />
<Label Text="using styles"
       HorizontalOptions="Center"
       VerticalOptions="Center"
       FontSize="18" />

En este ejemplo, cada objeto Label tiene valores de propiedad idénticos para controlar la apariencia del texto mostrado por Label. Aunque, establecer la apariencia de cada control individual puede ser una tarea repetitiva y propensa a errores. En su lugar, se pueden crear estilos que definan la apariencia y, después, aplicarlos a los controles necesarios.

Introducción a los estilos

Se puede aplicar el estilo a una aplicación con la clase Style para agrupar una colección de valores de propiedad en un objeto que después se puede aplicar a varias instancias de elementos visuales. Esto permite reducir el uso de marcado repetitivo y conseguir que el aspecto de las aplicaciones pueda cambiarse con mayor facilidad.

Aunque los estilos están diseñados principalmente para aplicaciones basadas en XAML, también se pueden crear en C#:

  • Los objetos Style creados en XAML suelen estar definidos en un objeto ResourceDictionary que se asigna a la colección Resources de un control, una página, o bien a la colección Resources de la aplicación.
  • Los objetos Style creados en C# se suelen definir en la clase de la página, o bien en una clase a la que se puede acceder de forma global.

La elección de dónde se puede definir un elemento Style afecta a dónde se puede usar:

  • Las instancias de Style definidas en el nivel de vista se pueden aplicar a la vista y a sus elementos secundarios.
  • Las instancias de Style definidas en el nivel de página se pueden aplicar a la página y a sus elementos secundarios.
  • Las instancias de Style definidas en el nivel de aplicación se pueden aplicar en toda la aplicación.

Cada objeto Style contiene una colección de uno o varios objetos Setter, donde cada objeto Setter tiene un elemento Property y un elemento Value. Property es el nombre de la propiedad enlazable del elemento al que se aplica el estilo y Value es el valor que se aplica a la propiedad.

Cada objeto Style puede ser explícito o implícito:

  • Un objeto Styleexplícito se define especificando TargetType y un valor x:Key, y estableciendo la propiedad Style del elemento de destino en la referencia x:Key. Para más información, consulta Estilos explícitos.
  • Un objeto Styleimplícito se define especificando solamente un objeto TargetType. Después, el objeto Style se aplicará automáticamente a todos los elementos de ese tipo. Pero a las subclases de TargetType no se les aplica automáticamente el Style. Para más información, consulta Estilos implícitos.

Al crear un elemento Style, siempre se requiere la propiedad TargetType. En el ejemplo siguiente se muestra un estilo explícito:

<Style x:Key="labelStyle" TargetType="Label">
    <Setter Property="HorizontalOptions" Value="Center" />
    <Setter Property="VerticalOptions" Value="Center" />
    <Setter Property="FontSize" Value="18" />
</Style>

Para aplicar Style, el objeto de destino debe ser un elemento VisualElement que coincida con el valor de propiedad TargetType de Style:

<Label Text="Demonstrating an explicit style" Style="{StaticResource labelStyle}" />

Los estilos situados más abajo en la jerarquía de la vista tienen prioridad sobre los definidos más arriba. Por ejemplo, establecer un objeto Style que establece Label.TextColor en Red en el nivel de aplicación quedará anulado por un estilo de nivel de página que establece Label.TextColor en Green. Asimismo, un estilo de nivel de página quedará anulado por un estilo de nivel de control. Además, si Label.TextColor se establece directamente en una propiedad de control, tendrá prioridad sobre los estilos.

Los estilos no responden a los cambios de propiedad y permanecen sin cambios mientras dura una aplicación. Aunque las aplicaciones pueden responder a cambios de estilo dinámicamente en tiempo de ejecución con recursos dinámicos. Para más información, consulta Estilos dinámicos.

Estilos explícitos

Para crear un objeto Style en el nivel de página, debe agregarse ResourceDictionary a la página y, luego, se pueden incluir una o varias declaraciones Style en ResourceDictionary. Un objeto Style se convierte en explícito al asignar a su declaración un atributo x:Key, que le da una clave descriptiva en ResourceDictionary. Después, deberán aplicarse los estilos explícitos a elementos visuales específicos estableciendo sus propiedades Style.

En el ejemplo siguiente se muestran estilos explícitos en el elemento ResourceDictionary de una página, y se aplican a los objetos Label de la página:

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="labelRedStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="TextColor" Value="Red" />
        </Style>
        <Style x:Key="labelGreenStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="TextColor" Value="Green" />
        </Style>
        <Style x:Key="labelBlueStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="TextColor" Value="Blue" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <Label Text="These labels"
               Style="{StaticResource labelRedStyle}" />
        <Label Text="are demonstrating"
               Style="{StaticResource labelGreenStyle}" />
        <Label Text="explicit styles,"
               Style="{StaticResource labelBlueStyle}" />
        <Label Text="and an explicit style override"
               Style="{StaticResource labelBlueStyle}"
               TextColor="Teal" />
    </StackLayout>
</ContentPage>

En este ejemplo, ResourceDictionary define tres estilos que se establecen explícitamente en los objetos Label de la página. Cada Style se usa para mostrar texto en un color diferente, a la vez que también se establece el tamaño de fuente, y las opciones de diseño horizontales y verticales. Cada Style se aplica a un objeto Label diferente estableciendo sus propiedades Style con la extensión de marcado StaticResource. Además, mientras que el objeto Label final tiene Style establecido en él, también invalida la propiedad TextColor en un valor Color diferente.

Estilos implícitos

Para crear un objeto Style en el nivel de página, debe agregarse ResourceDictionary a la página y, luego, se pueden incluir una o varias declaraciones Style en ResourceDictionary. Style se convierte en implícito si no se especifica un atributo x:Key. Después, el estilo se aplicará a en los elementos visuales en el ámbito que coincidan con TargetType exactamente, pero no a los elementos derivados del valor TargetType.

En el ejemplo de código siguiente se muestra un estilo implícito en ResourceDictionary de una página y se aplica a los objetos Entry de la página:

<ContentPage ...>
    <ContentPage.Resources>
        <Style TargetType="Entry">
            <Setter Property="HorizontalOptions" Value="Fill" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="BackgroundColor" Value="Yellow" />
            <Setter Property="FontAttributes" Value="Italic" />
            <Setter Property="TextColor" Value="Blue" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <Entry Text="These entries" />
        <Entry Text="are demonstrating" />
        <Entry Text="implicit styles," />
        <Entry Text="and an implicit style override"
               BackgroundColor="Lime"
               TextColor="Red" />
        <local:CustomEntry Text="Subclassed Entry is not receiving the style" />
    </StackLayout>
</ContentPage>

En este ejemplo, ResourceDictionary define un único estilo implícito que se establece implícitamente en los objetos Entry de la página. Style se usa para mostrar texto azul en un fondo amarillo, al tiempo que establece otras opciones de apariencia. Style se agrega a la página ResourceDictionary sin especificar un atributo x:Key. Por lo tanto, Style se aplica a todos los objetos Entry implícitamente a medida que coinciden con la propiedad TargetType de Style exactamente. Pero, Style no se aplica al objeto CustomEntry, que es una subclase Entry. Además, la cuarta Entry invalida las propiedades BackgroundColor y TextColor del estilo a valores Color diferentes.

Aplicación de un estilo a tipos derivados

La propiedad Style.ApplyToDerivedTypes permite aplicar un estilo a los controles derivados del tipo base a los que hace referencia la propiedad TargetType. Por lo tanto, establecer esta propiedad en true permite que un único estilo tenga como destino varios tipos, siempre que los tipos deriven del tipo base especificado en la propiedad TargetType.

En el ejemplo siguiente se muestra un estilo implícito que establece el color de fondo de instancias Button en rojo:

<Style TargetType="Button"
       ApplyToDerivedTypes="True">
    <Setter Property="BackgroundColor"
            Value="Red" />
</Style>

Colocar este estilo en un nivel de página ResourceDictionary dará lugar a que se aplique a todos los objetos Button de la página y también a los controles que deriven de Button. Pero si la propiedad ApplyToDerivedTypes permanece sin establecer, el estilo solo se aplicaría a objetos Button.

Estilos globales

Los estilos se pueden definir globalmente agregándolos al diccionario de recursos de la aplicación. Estos estilos se pueden consumir a lo largo de una aplicación y ayudar a evitar la duplicación de estilos entre páginas y controles.

En el ejemplo siguiente se muestra un Style definido en el nivel de aplicación:


<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Styles"
             x:Class="Styles.App">
    <Application.Resources>        
        <Style x:Key="buttonStyle" TargetType="Button">
            <Setter Property="HorizontalOptions"
                        Value="Center" />
            <Setter Property="VerticalOptions"
                        Value="CenterAndExpand" />
            <Setter Property="BorderColor"
                        Value="Lime" />
            <Setter Property="CornerRadius"
                        Value="5" />
            <Setter Property="BorderWidth"
                        Value="5" />
            <Setter Property="WidthRequest"
                        Value="200" />
            <Setter Property="TextColor"
                        Value="Teal" />
        </Style>
    </Application.Resources>
</Application>

En este ejemplo, ResourceDictionary define un único estilo explícito, buttonStyle que se usará para establecer la apariencia de los objetos Button.

Nota:

Los estilos globales pueden ser explícitos o implícitos.

En el ejemplo siguiente se muestra una página que consume buttonStyle en los objetos Button de la página:

<ContentPage ...>
    <StackLayout>
        <Button Text="These buttons"
                Style="{StaticResource buttonStyle}" />
        <Button Text="are demonstrating"
                Style="{StaticResource buttonStyle}" />
        <Button Text="application styles"
                Style="{StaticResource buttonStyle}" />
    </StackLayout>
</ContentPage>

Herencia de estilo

Los estilos pueden heredar de otros estilos para reducir la duplicación y habilitar la reutilización. Esto se logra estableciendo la propiedad Style.BasedOn en un Style existente. En XAML, esto se puede lograr estableciendo la propiedad BasedOn en una extensión de marcado StaticResource que hace referencia a un objeto Style creado anteriormente.

Los estilos que heredan de un estilo base pueden incluir instancias Setter de nuevas propiedades o usarlas para invalidar establecedores del estilo base. Además, los estilos que heredan de un estilo base deben tener como destino el mismo tipo o un tipo que derive del tipo destinado al estilo base. Por ejemplo, si un estilo base tiene como objetivo objetos View, los estilos basados en el estilo base pueden dirigirse a objetos o tipos View que derivan de la clase View, como objetos Label y Button.

Un estilo solo puede heredar de estilos en el mismo nivel, o superior, en la jerarquía de la vista. Esto significa que:

  • Un estilo de nivel de aplicación solo puede heredar de otros estilos de nivel de aplicación.
  • Un estilo de nivel de página puede heredar de estilos de nivel de aplicación y de otros estilos de nivel de página.
  • Un estilo de nivel de control puede heredar de estilos de nivel de aplicación, estilos de nivel de página y otros estilos de nivel de control.

En el ejemplo siguiente se muestra la herencia de estilo explícita:

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="baseStyle"
               TargetType="View">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <StackLayout.Resources>
            <Style x:Key="labelStyle"
                   TargetType="Label"
                   BasedOn="{StaticResource baseStyle}">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="FontAttributes" Value="Italic" />
                <Setter Property="TextColor" Value="Teal" />
            </Style>
            <Style x:Key="buttonStyle"
                   TargetType="Button"
                   BasedOn="{StaticResource baseStyle}">
                <Setter Property="BorderColor" Value="Lime" />
                <Setter Property="CornerRadius" Value="5" />
                <Setter Property="BorderWidth" Value="5" />
                <Setter Property="WidthRequest" Value="200" />
                <Setter Property="TextColor" Value="Teal" />
            </Style>
        </StackLayout.Resources>
        <Label Text="This label uses style inheritance"
               Style="{StaticResource labelStyle}" />
        <Button Text="This button uses style inheritance"
                Style="{StaticResource buttonStyle}" />
    </StackLayout>
</ContentPage>

En este ejemplo, baseStyle tiene como destino objetivos View y establece las propiedades HorizontalOptions y VerticalOptions. baseStyle no está establecido directamente en ningún control. En su lugar, labelStyle y buttonStyle heredan de él, estableciendo valores de propiedad enlazables adicionales. Luego los objetos labelStyle y buttonStyle se establecen en Label y Button.

Importante

Un estilo implícito se puede derivar de un estilo explícito, pero un estilo explícito no se puede derivar de un estilo implícito.

Estilos dinámicos

Los estilos no responden a los cambios de propiedad y permanecen sin cambios mientras dura una aplicación. Por ejemplo, después de asignar un objeto Style a un elemento visual, si uno de los objetos Setter se modifica, quita o se agrega un nuevo Setter, los cambios no se aplicarán al elemento visual. Aunque las aplicaciones pueden responder a cambios de estilo dinámicamente en tiempo de ejecución con recursos dinámicos.

La extensión de marcado DynamicResource es similar a la extensión de marcado StaticResource en que ambos usan una clave de diccionario para capturar un valor de ResourceDictionary. Pero mientras StaticResource realiza una búsqueda de diccionario única, DynamicResource mantiene un vínculo a la clave del diccionario. Por lo tanto, si se reemplaza la entrada del diccionario asociada a la clave, el cambio se aplica al elemento visual. Esto permite realizar cambios de estilo en tiempo de ejecución en una aplicación.

En el ejemplo siguiente se muestran estilos dinámicos:

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="baseStyle"
               TargetType="View">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
        <Style x:Key="blueSearchBarStyle"
               TargetType="SearchBar"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontAttributes" Value="Italic" />
            <Setter Property="PlaceholderColor" Value="Blue" />
        </Style>
        <Style x:Key="greenSearchBarStyle"
               TargetType="SearchBar">
            <Setter Property="FontAttributes" Value="None" />
            <Setter Property="PlaceholderColor" Value="Green" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <SearchBar Placeholder="SearchBar demonstrating dynamic styles"
                   Style="{DynamicResource blueSearchBarStyle}" />
    </StackLayout>
</ContentPage>

En este ejemplo, el objeto SearchBar usa la extensión de marcado DynamicResource para establecer un Style denominado blueSearchBarStyle. Después, SearchBar puede tener su definición de Style actualizada en el código:

Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];

En este ejemplo, la definición blueSearchBarStyle se actualiza para usar los valores de la definición greenSearchBarStyle. Cuando se ejecuta este código, SearchBar se actualizará para usar los objetos Setter definidos en greenSearchBarStyle.

Herencia de estilo dinámico

La derivación de estilo de un estilo dinámico no se puede lograr mediante la propiedad Style.BasedOn. En su lugar, la clase Style incluye la propiedad BaseResourceKey, que se puede establecer en una clave de diccionario cuyo valor podría cambiar de manera dinámica.

En el ejemplo siguiente se muestra la herencia de estilo dinámico:

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="baseStyle"
               TargetType="View">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
        <Style x:Key="blueSearchBarStyle"
               TargetType="SearchBar"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontAttributes" Value="Italic" />
            <Setter Property="TextColor" Value="Blue" />
        </Style>
        <Style x:Key="greenSearchBarStyle"
               TargetType="SearchBar">
            <Setter Property="FontAttributes" Value="None" />
            <Setter Property="TextColor" Value="Green" />
        </Style>
        <Style x:Key="tealSearchBarStyle"
               TargetType="SearchBar"
               BaseResourceKey="blueSearchBarStyle">
            <Setter Property="BackgroundColor" Value="Teal" />
            <Setter Property="CancelButtonColor" Value="White" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <SearchBar Text="SearchBar demonstrating dynamic style inheritance"
                   Style="{StaticResource tealSearchBarStyle}" />
    </StackLayout>
</ContentPage>

En este ejemplo, el objeto SearchBar usa la extensión de marcado StaticResource para hacer referencia a un objeto Style denominado tealSearchBarStyle. Style establece algunas propiedades adicionales y usa la propiedad BaseResourceKey para hacer referencia a blueSearchBarStyle. La extensión de marcado DynamicResource no es necesaria porque tealSearchBarStyle no cambiará, a excepción de Style, del que deriva. Por lo tanto, tealSearchBarStyle mantiene un vínculo a blueSearchBarStyle y se actualiza cuando cambia el estilo base.

La definición blueSearchBarStyle se puede actualizar en el código:

Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];

En este ejemplo, la definición blueSearchBarStyle se actualiza para usar los valores de la definición greenSearchBarStyle. Cuando se ejecuta este código, SearchBar se actualizará para usar los objetos Setter definidos en greenSearchBarStyle.

Clases de estilo

Las clases de estilo permiten aplicar varios estilos a un control, sin recurrir a la herencia de estilos.

Se puede crear una clase de estilo estableciendo la propiedad Class en un objeto Stylestring que representa el nombre de clase. La ventaja que ofrece, sobre la definición de un estilo explícito mediante el atributo x:Key, es que se pueden aplicar varias clases de estilo a VisualElement.

Importante

Varios estilos pueden compartir el mismo nombre de clase, siempre que tengan como destino tipos diferentes. Esto permite que varias clases de estilo, que tienen un nombre idéntico, tienen como destino tipos diferentes.

En el ejemplo siguiente se muestran tres clases de estilo BoxView y una clase de estilo VisualElement:

<ContentPage ...>
    <ContentPage.Resources>
        <Style TargetType="BoxView"
               Class="Separator">
            <Setter Property="BackgroundColor"
                    Value="#CCCCCC" />
            <Setter Property="HeightRequest"
                    Value="1" />
        </Style>

        <Style TargetType="BoxView"
               Class="Rounded">
            <Setter Property="BackgroundColor"
                    Value="#1FAECE" />
            <Setter Property="HorizontalOptions"
                    Value="Start" />
            <Setter Property="CornerRadius"
                    Value="10" />
        </Style>    

        <Style TargetType="BoxView"
               Class="Circle">
            <Setter Property="BackgroundColor"
                    Value="#1FAECE" />
            <Setter Property="WidthRequest"
                    Value="100" />
            <Setter Property="HeightRequest"
                    Value="100" />
            <Setter Property="HorizontalOptions"
                    Value="Start" />
            <Setter Property="CornerRadius"
                    Value="50" />
        </Style>

        <Style TargetType="VisualElement"
               Class="Rotated"
               ApplyToDerivedTypes="true">
            <Setter Property="Rotation"
                    Value="45" />
        </Style>        
    </ContentPage.Resources>
</ContentPage>

En este ejemplo, las clases de estilo Separator, Rounded y Circle establecen propiedades BoxView en valores específicos. La clase de estilo Rotated tiene un elemento TargetType de VisualElement, lo que significa que solo se puede aplicar a instancias de VisualElement. Sin embargo, su propiedad ApplyToDerivedTypes se establece en true, lo que garantiza que se puede aplicar a cualquier control que derive de VisualElement, como BoxView. Para más información sobre cómo aplicar un estilo a un tipo derivado, consulta Aplicación de un estilo a tipos derivados.

Las clases de estilo se pueden consumir estableciendo la propiedad StyleClass del control, que es de tipo IList<string>, en una lista de nombres de clase de estilo. Se aplicarán las clases de estilo, siempre que el tipo del control coincida con el TargetType de las clases de estilo.

En el ejemplo siguiente se muestran tres instancias de BoxView, cada una establecida en clases de estilo diferentes:

<ContentPage ...>
    <ContentPage.Resources>
        ...
    </ContentPage.Resources>
    <StackLayout>
        <BoxView StyleClass="Separator" />       
        <BoxView WidthRequest="100"
                 HeightRequest="100"
                 HorizontalOptions="Center"
                 StyleClass="Rounded, Rotated" />
        <BoxView HorizontalOptions="Center"
                 StyleClass="Circle" />
    </StackLayout>
</ContentPage>    

En este ejemplo, el primer elemento BoxView tiene el estilo de separador de línea, mientras que el tercer elemento BoxView es circular. El segundo elemento BoxView tiene dos clases de estilo aplicadas, que le dan esquinas redondeadas y lo giran 45 grados:

Screenshot of BoxViews styled with style classes.

Importante

Se pueden aplicar varias clases de estilo a un control porque la propiedad StyleClass es de tipo IList<string>. Cuando esto ocurre, las clases de estilo se aplican en orden de lista ascendente. Por lo tanto, cuando varias clases de estilo establecen propiedades idénticas, la propiedad de la clase de estilo que se encuentra en la posición de lista más alta tendrá prioridad.