Diccionarios de recursos de Xamarin.Forms

Un elemento ResourceDictionary es un repositorio para los recursos que usa una aplicación de Xamarin.Forms. Los recursos típicos que se almacenan en un elemento ResourceDictionary incluyen estilos, plantillas de control, plantillas de datos, colores y convertidores.

En XAML, se puede hacer referencia a los recursos almacenados en un elemento ResourceDictionary y aplicarlos a los elementos mediante la extensión de marcado StaticResource o DynamicResource. En C#, los recursos también se pueden definir en ResourceDictionary y, a continuación, hacer referencia y aplicar a los elementos mediante un indexador basado en cadenas. Sin embargo, hay pocas ventajas por usar un elemento ResourceDictionary en C#, ya que los objetos compartidos se pueden almacenar como campos o propiedades, y se accede directamente sin tener que recuperarlos primero de un diccionario.

Creación de recursos en XAML

Cada objeto derivado VisualElement tiene una propiedad Resources, que es ResourceDictionary que puede contener recursos. Del mismo modo, un objeto derivado Application tiene una propiedad Resources, que es ResourceDictionary que puede contener recursos.

Una aplicación de Xamarin.Forms solo contiene una clase que deriva de Application, pero a menudo usa muchas clases que derivan de VisualElement, incluidas páginas, diseños y controles. Cualquiera de estos objetos puede tener su propiedad Resources establecida en un recurso contenedor ResourceDictionary. Elegir dónde colocar un ResourceDictionary determinado afecta dónde se pueden usar los recursos:

  • Los recursos de un elemento ResourceDictionary que se adjuntan a una vista como Button o Label solo se pueden aplicar a ese objeto determinado.
  • Los recursos de un elemento ResourceDictionary adjuntos a un diseño como StackLayout o Grid se pueden aplicar al diseño y a todos los elementos secundarios de ese diseño.
  • Los recursos de ResourceDictionary definidos en el nivel de página se pueden aplicar a la página y a sus elementos secundarios.
  • Los recursos de un elemento ResourceDictionary definidos en el nivel de aplicación se pueden aplicar en toda la aplicación.

A excepción de los estilos implícitos, cada recurso del diccionario de recursos debe tener una clave de cadena única definida con el atributo x:Key.

En el código XAML siguiente se muestran los recursos definidos a nivel ResourceDictionary de aplicación en el archivo App.xaml:

<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ResourceDictionaryDemo.App">
    <Application.Resources>

        <Thickness x:Key="PageMargin">20</Thickness>

        <!-- Colors -->
        <Color x:Key="AppBackgroundColor">AliceBlue</Color>
        <Color x:Key="NavigationBarColor">#1976D2</Color>
        <Color x:Key="NavigationBarTextColor">White</Color>
        <Color x:Key="NormalTextColor">Black</Color>

        <!-- Implicit styles -->
        <Style TargetType="{x:Type NavigationPage}">
            <Setter Property="BarBackgroundColor"
                    Value="{StaticResource NavigationBarColor}" />
            <Setter Property="BarTextColor"
                    Value="{StaticResource NavigationBarTextColor}" />
        </Style>

        <Style TargetType="{x:Type ContentPage}"
               ApplyToDerivedTypes="True">
            <Setter Property="BackgroundColor"
                    Value="{StaticResource AppBackgroundColor}" />
        </Style>

    </Application.Resources>
</Application>

En este ejemplo, el diccionario de recursos define un recurso Thickness, varios recursos Color y dos recursos Style implícitos. Para obtener más información sobre la clase App, consulte Clase App de Xamarin.Forms.

Nota:

También es válido colocar todos los recursos entre etiquetas ResourceDictionary explícitas. Sin embargo, desde Xamarin.Forms 3.0 las etiquetas ResourceDictionary no son obligatorias. En su lugar, el objeto ResourceDictionary se crea automáticamente y puede insertar los recursos directamente entre las etiquetas del elemento de propiedad Resources.

Consumo de recursos en XAML

Cada recurso tiene una clave que se especifica mediante el atributo x:Key, que se convierte en su clave de diccionario en ResourceDictionary. La clave se usa para hacer referencia a un recurso del elemento ResourceDictionary con la extensión de marcado StaticResource o DynamicResource.

La extensión de marcado StaticResource es similar a la extensión de marcado DynamicResource en que ambas usan una clave de diccionario para hacer referencia a un valor de un diccionario de recursos. Sin embargo, mientras que la extensión de marcado StaticResource realiza una búsqueda de diccionario única, la extensión de marcado DynamicResource mantiene un vínculo a la clave de 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 en los recursos en tiempo de ejecución en una aplicación. Para obtener más información sobre las extensiones de marcado, consulte Extensiones de marcado de XAML.

En el ejemplo de XAML siguiente, se muestra cómo consumir recursos y también se definen recursos adicionales en un elemento StackLayout:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ResourceDictionaryDemo.HomePage"
             Title="Home Page">
    <StackLayout Margin="{StaticResource PageMargin}">
        <StackLayout.Resources>
            <!-- Implicit style -->
            <Style TargetType="Button">
                <Setter Property="FontSize" Value="Medium" />
                <Setter Property="BackgroundColor" Value="#1976D2" />
                <Setter Property="TextColor" Value="White" />
                <Setter Property="CornerRadius" Value="5" />
            </Style>
        </StackLayout.Resources>

        <Label Text="This app demonstrates consuming resources that have been defined in resource dictionaries." />
        <Button Text="Navigate"
                Clicked="OnNavigateButtonClicked" />
    </StackLayout>
</ContentPage>

En este ejemplo, el objeto ContentPage consume el estilo implícito definido en el diccionario de recursos a nivel de aplicación. El objeto StackLayout consume el recurso PageMargin definido en el diccionario de recursos a nivel de aplicación, mientras que el objeto Button consume el estilo implícito definido en el diccionario de recursos StackLayout. El resultado es el aspecto que se muestra en las capturas de pantalla siguientes:

Consumo de recursos ResourceDictionary

Importante

Los recursos específicos de una sola página no se deben incluir en un diccionario de recursos de nivel de aplicación, ya que dichos recursos se analizarán en el inicio de la aplicación en lugar de cuando lo requiera una página. Para obtener más información, consulte Reducción del tamaño del diccionario de recursos de la aplicación.

Comportamiento de búsqueda de recursos

El siguiente proceso de búsqueda se produce cuando se hace referencia a un recurso con la extensión de marcado StaticResource o DynamicResource:

  • Se comprueba la clave solicitada en el diccionario de recursos, si existe, para el elemento que establece la propiedad. Si se encuentra la clave solicitada, se devuelve su valor y finaliza el proceso de búsqueda.
  • Si no se encuentra una coincidencia, el proceso de búsqueda busca en el árbol visual hacia arriba y comprueba el diccionario de recursos de cada elemento primario. Si se encuentra la clave solicitada, se devuelve su valor y finaliza el proceso de búsqueda. De lo contrario, este proceso continúa hasta que se alcanza el elemento raíz.
  • Si no se encuentra una coincidencia en el elemento raíz, se examina el diccionario de recursos a nivel de aplicación.
  • Si todavía no se encuentra una coincidencia, se produce XamlParseException.

Por lo tanto, cuando el analizador XAML encuentra una extensión de marcado StaticResource o DynamicResource, busca una clave coincidente al desplazarse por el árbol visual, usando la primera coincidencia que encuentra. Si esta búsqueda finaliza en la página y la clave todavía no se encuentra, el analizador XAML busca ResourceDictionary adjunto al objeto App. Si todavía no se encuentra la clave, se produce una excepción.

Invalidar recursos

Cuando los recursos comparten claves, los recursos definidos en la parte inferior del árbol visual tendrán prioridad sobre los definidos en la parte superior. Por ejemplo,si se establece un recurso AppBackgroundColor en AliceBlue en el nivel de aplicación se invalidará mediante un recurso AppBackgroundColor de nivel de página establecido en Teal. Del mismo modo, un recurso AppBackgroundColor de nivel de página se invalidará mediante un recurso AppBackgroundColor de nivel de control.

diccionarios de recursos independientes

Una clase derivada de ResourceDictionary también puede estar en un archivo XAML independiente. De este modo, el archivo XAML se puede compartir entre aplicaciones.

Para crear este tipo de archivo, agregue un nuevo elemento de vista de contenido o página de contenido al proyecto (pero no una vista de contenido o una página de contenido con solo un archivo de C#). Elimine el archivo de código subyacente y, en el archivo XAML, cambie el nombre de la clase base de ContentView o ContentPage a ResourceDictionary. Además, quite el atributo x:Class de la etiqueta raíz del archivo.

En el ejemplo de XAML siguiente, se muestra un elemento ResourceDictionary llamado MyResourceDictionary.xaml:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <DataTemplate x:Key="PersonDataTemplate">
        <ViewCell>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.5*" />
                    <ColumnDefinition Width="0.2*" />
                    <ColumnDefinition Width="0.3*" />
                </Grid.ColumnDefinitions>
                <Label Text="{Binding Name}"
                       TextColor="{StaticResource NormalTextColor}"
                       FontAttributes="Bold" />
                <Label Grid.Column="1"
                       Text="{Binding Age}"
                       TextColor="{StaticResource NormalTextColor}" />
                <Label Grid.Column="2"
                       Text="{Binding Location}"
                       TextColor="{StaticResource NormalTextColor}"
                       HorizontalTextAlignment="End" />
            </Grid>
        </ViewCell>
    </DataTemplate>
</ResourceDictionary>

En este ejemplo, ResourceDictionary contiene un único recurso, que es un objeto de tipo DataTemplate. MyResourceDictionary.xaml se puede consumir combinándolo en otro diccionario de recursos.

De manera predeterminada, el enlazador quitará los archivos XAML independientes de las compilaciones de versión cuando el comportamiento del enlazador esté establecido para vincular todos los ensamblados. Para asegurarse de que los archivos XAML independientes permanecen en una compilación de versión:

  1. Agregue un atributo personalizado Preserve al ensamblado que contiene los archivos XAML independientes. Para obtener más información, consulte Conservación del código.

  2. Establezca el atributo Preserve en el nivel de ensamblado:

    [assembly:Preserve(AllMembers = true)]
    

Para obtener más información sobre la vinculación, consulte Vinculación de aplicaciones de Xamarin.iOS y Vinculación en Android.

Diccionarios de recursos combinados

Los diccionarios de recursos combinados combinan uno o varios objetos ResourceDictionary en otro objeto ResourceDictionary.

Combinar diccionarios de recursos locales

Un archivo ResourceDictionary local se puede combinar en otro ResourceDictionary creando un objeto ResourceDictionary cuya propiedad Source se establece en el nombre de archivo XAML con los recursos:

<ContentPage ...>
    <ContentPage.Resources>
        <!-- Add more resources here -->
        <ResourceDictionary Source="MyResourceDictionary.xaml" />
        <!-- Add more resources here -->
    </ContentPage.Resources>
    ...
</ContentPage>

Esta sintaxis no crea una instancia de la clase MyResourceDictionary. En su lugar, hace referencia al archivo XAML. Por este motivo, al establecer la propiedad Source, no se requiere un archivo de código subyacente y el atributo x:Class se puede quitar de la etiqueta raíz del archivo MyResourceDictionary.xaml.

Importante

La propiedad Source solo se puede establecer desde XAML.

Combinar diccionarios de recursos de otros ensamblados

ResourceDictionary también se puede combinar en otro ResourceDictionary agregándolo a la propiedad MergedDictionaries de ResourceDictionary. Esta técnica permite combinar diccionarios de recursos, independientemente del ensamblado en el que residen. La combinación de diccionarios de recursos de ensamblados externos requiere que el elemento ResourceDictionary tenga una acción de compilación establecida en EmbeddedResource, para tener un archivo de código subyacente y para definir el atributo x:Class en la etiqueta raíz del archivo.

Advertencia

La clase ResourceDictionary también define una propiedad MergedWith. Pero esta propiedad ha quedado en desuso y ya no debe usarse.

En el ejemplo de código siguiente se muestran dos diccionarios de recursos que se agregan a la colección MergedDictionaries de un nivel de página ResourceDictionary:

<ContentPage ...
             xmlns:local="clr-namespace:ResourceDictionaryDemo"
             xmlns:theme="clr-namespace:MyThemes;assembly=MyThemes">
    <ContentPage.Resources>
        <ResourceDictionary>
            <!-- Add more resources here -->
            <ResourceDictionary.MergedDictionaries>
                <!-- Add more resource dictionaries here -->
                <local:MyResourceDictionary />
                <theme:LightTheme />
                <!-- Add more resource dictionaries here -->
            </ResourceDictionary.MergedDictionaries>
            <!-- Add more resources here -->
        </ResourceDictionary>
    </ContentPage.Resources>
    ...
</ContentPage>

En este ejemplo, un diccionario de recursos del mismo ensamblado y un diccionario de recursos de un ensamblado externo se combinan en el diccionario de recursos de nivel de página. Además, también puedes agregar otros objetos ResourceDictionary dentro de las etiquetas de elemento de propiedad MergedDictionaries y otros recursos fuera de esas etiquetas.

Importante

Solo puede haber una etiqueta de elemento de propiedad MergedDictionaries en un ResourceDictionary, pero puedes colocar tantos objetos ResourceDictionary allí como sea necesario.

Cuando los recursos ResourceDictionary combinados comparten valores de atributo x:Key idénticos, Xamarin.Forms usa la siguiente precedencia de recursos:

  1. Los recursos locales para el diccionario de recursos.
  2. Los recursos contenidos en los diccionarios de recursos que se combinaron a través de la colección MergedDictionaries, en el orden inverso en el que aparecen en la propiedad MergedDictionaries.

Nota:

La búsqueda en diccionarios de recursos puede ser una tarea de uso intensivo de cálculo si una aplicación contiene varios diccionarios de recursos grandes. Por lo tanto, para evitar búsquedas innecesarias, asegúrate de que cada página de una aplicación solo use diccionarios de recursos adecuados para la página.

Encuentre más vídeos de Xamarin en Channel 9 y YouTube.