ResourceDictionary – это хранилище ресурсов, которые используются в приложении Xamarin.Forms. Обычно в ResourceDictionary хранятся такие ресурсы, как стили, шаблоны элементов управления, шаблоны данных, цвета и конвертеры.
В XAML на ресурсы, хранящиеся в ResourceDictionary, можно ссылаться и применять их к элементам с помощью расширения разметки StaticResource или DynamicResource. В C# ресурсы также можно определять в ResourceDictionary, а затем ссылаться на них и применять к элементам с помощью строкового индексатора. Однако использование ResourceDictionary в C# не имеет особых преимуществ, поскольку общие объекты можно хранить в виде полей или свойств и обращаться к ним напрямую, без предварительного извлечения из словаря.
Создание ресурсов в XAML
Каждый производный объект VisualElement имеет свойство Resources, которое представляет собой ResourceDictionary, содержащий ресурсы. Аналогично, производный объект Application имеет свойство Resources, которое представляет собой ResourceDictionary, содержащий ресурсы.
Приложение Xamarin.Forms содержит только класс, производный от Application, но часто использует множество классов, производных от VisualElement, включая страницы, макеты и элементы управления. У любого из этих объектов свойство Resources может быть установлено на ResourceDictionary, содержащий ресурсы. Выбор места размещения ResourceDictionary влияет на то, где эти ресурсы могут быть использованы:
- Ресурсы в ResourceDictionary, прикрепленном к представлению, например, Button или Label, могут быть применены только к этому объекту.
- Ресурсы в ResourceDictionary, прикрепленные к StackLayout или Grid, могут применяться к макету и всем дочерним объектам этого макета.
- Ресурсы в словаре, определенном на уровне страницы, могут быть применены к странице и всем ее дочерним объектам.
- Ресурсы из ResourceDictionary, определенные на уровне приложения, могут быть применены ко всему приложению.
За исключением неявных стилей, каждый ресурс в словаре ресурсов должен иметь уникальный строковый ключ, задаваемый атрибутом x:Key.
В следующем XAML показаны ресурсы, определенные в ResourceDictionary на уровне приложения в файле 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>
В данном примере словарь ресурсов определяет следующие ресурсы: Thickness, несколько Color и два неявных Style.
Примечание. Также допустимо поместить все ресурсы между явными тегами ResourceDictionary. Однако, начиная с версии Xamarin.Forms 3.0, теги ResourceDictionary не требуются. Вместо этого объект ResourceDictionary создается автоматически, и ресурсы можно вставлять непосредственно между тегами элемента свойства Resources.
Потребление ресурсов в XAML
Каждый ресурс имеет ключ, задаваемый с помощью атрибута x:Key, который становится ключом его словаря в ResourceDictionary. Ключ используется для ссылки на ресурс из ResourceDictionary с помощью расширения разметки StaticResource или DynamicResource.
Расширение разметки StaticResource похоже на расширение разметки DynamicResource тем, что в обоих случаях для ссылки на значение из словаря ресурсов используется ключ словаря. Однако если расширение разметки StaticResource выполняет однократный поиск по словарю, то расширение разметки DynamicResource сохраняет ссылку на ключ словаря. Поэтому при замене словарной статьи, связанной с ключом, изменение применяется к визуальному элементу. Это позволяет вносить изменения в ресурсы во время выполнения приложения.
Следующий пример XAML показывает, как потреблять ресурсы, а также определяет дополнительные ресурсы в 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>
В данном примере объект ContentPage потребляет неявный стиль, определенный в словаре ресурсов уровня приложения. Объект StackLayout потребляет ресурс PageMargin, определенный в словаре ресурсов уровня приложения, а объект Button потребляет неявный стиль, определенный в словаре ресурсов StackLayout. Это приводит к виду, показанному на следующих скриншотах:
Важно. Ресурсы, специфичные для отдельной страницы, не следует включать в словарь ресурсов на уровне приложения, поскольку в этом случае они будут разбираться при запуске приложения, а не тогда, когда это требуется странице.
Поведение при поиске ресурсов
При ссылке на ресурс с помощью расширения разметки StaticResource или DynamicResource происходит следующий процесс поиска:
- Запрашиваемый ключ проверяется в словаре ресурсов, если он существует, для элемента, задающего свойство. Если запрашиваемый ключ найден, то возвращается его значение, и процесс поиска завершается.
- Если совпадение не найдено, то процесс поиска осуществляет поиск по визуальному дереву вверх, проверяя словарь ресурсов каждого родительского элемента. Если запрашиваемый ключ найден, то возвращается его значение и процесс поиска завершается. В противном случае процесс продолжается до тех пор, пока не будет достигнут корневой элемент.
- Если в корневом элементе совпадение не найдено, то проверяется словарь ресурсов уровня приложения.
- Если совпадение так и не найдено, то выдается исключение XamlParseException.
Таким образом, когда XAML-парсер встречает расширение разметки StaticResource или DynamicResource, он ищет соответствующий ключ, поднимаясь вверх по визуальному дереву и используя первое попавшееся совпадение. Если поиск заканчивается на странице, а ключ так и не найден, XAML-парсер обращается к ResourceDictionary, прикрепленному к объекту App. Если ключ так и не найден, выдается исключение.
Переопределение ресурсов
Когда ресурсы имеют общие ключи, то ресурсы, определенные ниже в визуальном дереве, будут иметь приоритет над теми, которые определены выше. Например, установка ресурса AppBackgroundColor на AliceBlue на уровне приложения будет отменена ресурсом AppBackgroundColor на уровне страницы, установленным на Teal. Аналогично, ресурс AppBackgroundColor на уровне страницы будет отменен ресурсом AppBackgroundColor на уровне управления.
Отдельные словари ресурсов
Класс, полученный из ResourceDictionary, может также находиться в отдельном XAML-файле. Такой XAML-файл может использоваться совместно с другими приложениями.
Чтобы создать такой файл, добавьте в проект новый элемент Content View или Content Page (но не Content View или Content Page, содержащий только файл C#). Удалите code-behind файл, а в XAML-файле измените имя базового класса с ContentView или ContentPage на ResourceDictionary. Кроме того, удалите атрибут x:Class из корневого тега файла.
В следующем примере XAML показан ResourceDictionary с именем 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>
В данном примере словарь ресурсов содержит один ресурс, который является объектом типа DataTemplate. MyResourceDictionary.xaml может быть использован путем слияния с другим словарем ресурсов.
По умолчанию компоновщик удаляет автономные XAML-файлы из релизных сборок, когда поведение компоновщика установлено на компоновку всех сборок. Для того, чтобы автономные XAML-файлы оставались в релизной сборке:
Добавьте пользовательский атрибут Preserve к сборке, содержащей автономные XAML-файлы.
Установите атрибут Preserve на уровне сборки:
C#
[assembly:Preserve(AllMembers = true)]
Объединенные словари ресурсов
Такие словари объединяют один или несколько объектов ResourceDictionary в другой ResourceDictionary.
Слияние локальных словарей ресурсов
Локальный файл ResourceDictionary может быть объединен в другой ResourceDictionary путем создания объекта ResourceDictionary, свойство Source которого установлено в имя XAML-файла с ресурсами:
<ContentPage ...> <ContentPage.Resources> <!-- Add more resources here --> <ResourceDictionary Source="MyResourceDictionary.xaml" /> <!-- Add more resources here --> </ContentPage.Resources> ... </ContentPage>
В этом синтаксисе класс MyResourceDictionary не создается. Вместо этого он ссылается на XAML-файл. Поэтому при установке свойства Source файл code-behind не требуется, а атрибут x:Class может быть удален из корневого тега файла MyResourceDictionary.xaml.
Важно. Свойство Source может быть установлено только из XAML.
Объединение словарей ресурсов из других сборок
ResourceDictionary можно также объединить с другим ResourceDictionary, добавив его в свойство MergedDictionaries словаря ResourceDictionary. Эта техника позволяет объединять словари ресурсов независимо от того, в какой сборке они находятся. Для объединения словарей ресурсов из внешних сборок необходимо, чтобы для словаря ResourceDictionary было установлено действие сборки EmbeddedResource, чтобы он имел файл code-behind и чтобы в корневом теге файла был определен атрибут x:Class.
Предупреждение. Класс ResourceDictionary также определяет свойство MergedWith. Однако это свойство устарело и больше не должно использоваться.
В приведенном ниже примере кода показано добавление двух словарей ресурсов в коллекцию MergedDictionaries словаря 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>
В данном примере в словарь ресурсов на уровне страницы объединяются словарь ресурсов из той же сборки и словарь ресурсов из внешней сборки. Кроме того, можно добавлять другие объекты ResourceDictionary в теги элементов свойств MergedDictionaries и другие ресурсы вне этих тегов.
Важно. В ResourceDictionary может быть только один тег свойства-элемента MergedDictionaries, но в него можно поместить столько объектов ResourceDictionary, сколько необходимо.
Когда объединенные ресурсы ResourceDictionary имеют одинаковые значения атрибута x:Key, Xamarin.Forms использует следующий приоритет ресурсов:
- Ресурсы, локальные для словаря ресурсов.
- Ресурсы, содержащиеся в словарях ресурсов, которые были объединены с помощью коллекции MergedDictionaries, в обратном порядке, в котором они перечислены в свойстве MergedDictionaries.
Примечание. Если приложение содержит несколько больших словарей ресурсов, поиск в них может потребовать больших вычислительных затрат. Поэтому, чтобы избежать лишнего поиска, следует убедиться, что каждая страница приложения использует только те словари ресурсов, которые соответствуют данной странице.