Ресурсы в WinRT

160

Предположим, страница содержит несколько элементов TextBlock, часть из которых используют одинаковую кисть. Если это SolidColorBrush с относительно простой разметкой, проблем нет, но с LinearGradientBrush ситуация усложняется. Определение LinearGradientBrush требует минимум шести тегов, а повторение разметки становится крайне неприятным, особенно если в будущем потребуется что-то изменить.

В Windows Runtime поддерживаются так называемые ресурсы XAML, открывающие возможность совместного использования объектов несколькими элементами. Совместное использование кистей - одно из характерных применений ресурсов XAML, но чаще всего они применяются для определения и совместного использования стилей.

Ресурсы XAML хранятся в ResourceDictionary - словаре, у которого ключи и значения относятся к типу object. Впрочем, очень часто ключи представляют собой строки. И в FrameworkElement, и в Application определяется свойство с именем Resources типа ResourceDictionary.

Ниже показан пример проекта, который наглядно демонстрирует совместное использование LinearGradientBrush (и пары других объектов) несколькими элементами страницы. В начале файла XAML определяется элемент свойства Resources для коллекции ресурсов данной страницы:

<Page ...>
    
    <Page.Resources>
        <x:String x:Key="title">Настройки кистей</x:String>

        <LinearGradientBrush x:Key="rainbowBrush">
            <GradientStop Offset="0" Color="Red" />
            <GradientStop Offset="0.17" Color="Orange" />
            <GradientStop Offset="0.34" Color="Yellow" />
            <GradientStop Offset="0.5" Color="Green" />
            <GradientStop Offset="0.66" Color="Blue" />
            <GradientStop Offset="0.83" Color="Indigo" />
            <GradientStop Offset="1" Color="Violet" />
        </LinearGradientBrush>

        <FontFamily x:Key="fontFamily">Arial</FontFamily>

        <x:Double x:Key="fontSize">80</x:Double>
    </Page.Resources>

    ...
</Page>

Определения ресурсов в начале файла XAML часто называются «секцией ресурсов». В приведенном примере словарь Resources инициализируется четырьмя элементами четырех разных типов: String, LinearGradientBrush, FontFamily и Double. Обратите внимание на префикс "x" в ссылках на String и Double. Конечно, это примитивные типы .NET, но они не являются типами Windows Runtime, а следовательно, не принадлежат пространству XAML по умолчанию. Также возможно использование типов x:Boolean и x:Int32.

Обратите внимание на то, что каждый из указанных объектов обладает атрибутом x:Key. Атрибут x:Key действителен только в словаре Resources. Как можно предположить по названию, атрибут x:Key содержит ключ элемента в словаре. В теле файла XAML элемент ссылается на ресурс посредством использования ключа в специальной конструкции, называемой расширением разметки XAML.

Расширений разметки XAML относительно немного и их всегда можно узнать по фигурным скобкам. Расширение разметки для обращения к ресурсу состоит из ключевого слова StaticResource и имени ключа. Собственно, вы уже неоднократно встречали расширение разметки StaticResource: оно предоставляет фоновую кисть стандартному элементу Grid. В остальной разметке файла XAML StaticResource используется для получения элементов, определенных в словаре Resources:

<Page ...>
    
    <Page.Resources>
        ...
    </Page.Resources>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{StaticResource title}"
                   FontSize="42"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст сверху"
                   Foreground="{StaticResource rainbowBrush}"
                   FontFamily="{StaticResource fontFamily}"
                   FontSize="{StaticResource fontSize}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Top" />

        <TextBlock Text="Текст слева"
                   Foreground="{StaticResource rainbowBrush}"
                   FontFamily="{StaticResource fontFamily}"
                   FontSize="{StaticResource fontSize}"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст справа"
                   Foreground="{StaticResource rainbowBrush}"
                   FontFamily="{StaticResource fontFamily}"
                   FontSize="{StaticResource fontSize}"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст снизу"
                   Foreground="{StaticResource rainbowBrush}"
                   FontFamily="{StaticResource fontFamily}"
                   FontSize="{StaticResource fontSize}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Bottom" />
    </Grid>
</Page>
Пример использования ресурсов для нескольких элементов TextBlock

Обращение к трем одинаковым ресурсам в четырех элементах TextBlock наводит на мысль о более эффективном решении - использование стилей, о которых речь пойдет в одной из следующих статей.

Ресурсы должны определяться в файле XAML в точке, лексически предшествующей их использованию. Именно по этой причине словарь Resources размещается в начале файла XAML - обычно удобнее всего его размещать в корневом элементе.

Однако потомки FrameworkElement тоже могут поддерживать словарь Resources, поэтому они должны размещаться ниже в визуальном дереве. Ключи каждого словаря Resources должны быть уникальными, но в других словарях Resources могут использоваться дубликаты. Когда парсер XAML встречает расширение разметки StaticResource, он начинает искать по визуальному дереву словарь Resources с подходящим ключом и использует первый найденный словарь. Фактически вы можете переопределять значения ключей Resources в локальных словарях.

Если парсер XAML не удается найти подходящий ключ в визуальном дереве, он проверяет словарь Resources объекта Application. Файл App.xaml идеально подходит для определения ресурсов, используемых в приложении. Чтобы использовать набор ресурсов в нескольких приложениях, определите их в отдельном файле XAML (словаре ресурсов) с корневым элементом ResourceDictionary. Вы можете добавить файл словаря в проект, щелкнув правой кнопкой мыши по имени проекта в окне Solution Explorer и выбрав в контекстном меню пункт Add --> New Item. В списке шаблонов выберите вариант Resource Dictionary. Включите файл в проект используя ссылку на него в файл App.xaml, после чего вы сможете использовать элементы этого словаря. Ниже показана разметка App.xaml для включения словаря ресурсов Resources.xaml:

<Application ...>

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    
</Application>

Теперь вы можете вынести все ресурсы из окна MainPage.xaml в словарь ресурсов.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinRTTestApp">
    
    ...
    
</ResourceDictionary>

Со словарем Resources также можно работать в программном коде. После вызова InitializeComponent для выборки элемента словаря можно воспользоваться индексированием:

FontFamily arial = this.Resources["fontFamily"] as FontFamily;

Проведем эксперимент: закомментируйте элемент fontFamily в файле MainPage.xaml, но добавьте его в словарь в конструкторе MainPage до вызова InitializeComponent():

this.Resources.Add("fontFamily", new FontFamily("Arial"));

После того как InitializeComponent выполнит разбор файла XAML, этот объект становится доступным в файле XAML.

Класс ResourceDictionary не определяет открытый метод для поиска словарей классов-предков вверх по визуальному дереву. Если вам понадобится нечто похожее для поиска ресурсов в коде, вы сможете легко реализовать такой механизм в своем коде; для «подъема» по визуальному дереву используется свойство Parent, определяемое FrameworkElement, или класс VisualTreeHelper из пространства имен Windows.UI.Xaml.Media. Для получения объекта Application приложения следует использовать статическое свойство App.Current.

Предопределенные ресурсы (например, ApplicationPageBackgroundThemeBrush, на который ссылается Grid) не документированы, но список их значений (в темах Dark и Light) находится в следующем файле:

C:\Program Files (x86)\Windows Kits\8.1\Include\winrt\xaml\design\themeresources.xaml

Следующим по важности предопределенным идентификатором ресурса после ApplicationPageBackgroundThemeBrush является кисть основного цвета ApplicationForegroundThemeBrush. В светлой теме оформления используется черный цвет, а в темной - белый. Если вы хотите, чтобы цвет контрастировал с фоном, это именно то, что нужно. Для создания выделения, контрастирующего как с фоном, так и основным цветом, создайте объект SolidColorBrush на основании цвета Highlight, полученного от метода UIElementColor класса UISettings.

Действительно ли ресурсы используются совместно?

Может возникнуть вопрос, действительно ли объекты ресурсов совместно используются элементами, которые ссылаются на них? Или для каждой ссылки StaticResource создается отдельный экземпляр?

Попробуйте вставить следующий фрагмент после вызова InitializeComponent:

public MainPage()
{
    this.InitializeComponent();

    TextBlock txb = (this.Content as Grid).Children[1] as TextBlock;
    LinearGradientBrush brush = txb.Foreground as LinearGradientBrush;
    brush.StartPoint = new Point(0, 1);
    brush.EndPoint = new Point(0, 0);
}

Код ссылается на атрибут LinearGradientBrush второго элемента TextBlock из коллекции Children элемента Grid и изменяет свойства StartPoint и EndPoint. Кто бы мог подумать: изменяются все элементы TextBlock, ссылающиеся на эту кисть LinearGradientBrush:

Совместное использование ресурсов в приложении Windows Runtime

Вывод: ресурсы действительно используются совместно. Также легко убедиться в том, что даже если ни один элемент не содержит ссылки на ресурс, экземпляр этого ресурса все равно создается.

Пройди тесты
Лучший чат для C# программистов