Разделение ресурсов между сборками

87

Если необходимо использовать словарь ресурсов во множестве приложений, можно копировать и распространять содержащий его XAML-файл. Это самый простой подход, но он не поддерживает управление версиями. Более структурированный прием предусматривает компиляцию словаря ресурсов в отдельную сборку типа библиотеки классов и распространение его в виде такого компонента.

При разделении скомпилированной сборки с одним или несколькими словарями ресурсов возникает еще одна трудность, а именно — нужен какой-нибудь способ для извлечения необходимого ресурса и его использования в приложении. Здесь возможны два варианта. Самым простым решением является использование кода, который создает соответствующий объект ResourceDictionary. Например, если словарь ресурсов находится в сборке типа библиотеки классов по имени ReusableDictionary.xaml, для его создания вручную можно воспользоваться следующим кодом:

ResourceDictionary resourceDictionary = new ResourceDictionary();
resourceDictionary.Source = new Uri(
   "ResourceLibrary;component/ReusableDictlonary.xaml", UriKind.Relative);

В этом фрагменте кода применяется синтаксис упакованных URI. Код конструирует относительный URI, указывающий на скомпилированный XAML-pecypc по имени ReusableDictionary.xaml, который расположен в другой сборке. После создания объекта ResourceDictionary необходимый ресурс можно извлекать из коллекции вручную:

cmd.Background = (Brush)resourceDictionary["TileBrush"];

Однако назначать ресурсы вручную не понадобится. После загрузки нового словаря ресурсов любые имеющиеся в окне ссылки DynamicResource будут автоматически вычисляться заново.

Для тех, кто не желает писать никакого кода, доступен другой вариант. Можно использовать специально предназначенное для этой цели расширение разметки ComponentResourceKey. Данное расширение указывает WPF, что ресурс планируется разделять между сборками.

До этого момента демонстрировались только ресурсы, у которых для имен ключей использовались строки (такие как "TileBrush"). Строки являются самым типичным способом для именования ресурсов. Тем не менее, WPF обладает интеллектуальной функцией расширяемости ресурсов, которая активизируется автоматически, когда в качестве имен ключей применяются некоторые типы, не являющиеся строками.

Прежде чем двигаться дальше, необходимо позаботиться о назначении словарю ресурсов корректного имени. Для этого словарь ресурсов должен находиться в файле по имени generic.xaml, а этот файл — в подпапке Themes приложения. Ресурсы в файлах generic.xaml воспринимаются как часть темы по умолчанию и потому всегда делаются доступными. Этот прием будет встречаться еще не раз, в частности, во время построения специальных элементов управления.

На рисунке показано, как выглядит надлежащая организация файлов. Верхний проект, именуемый ResourceLibrary, включает файл generic.xaml в правильной папке. Нижний проект, имеющий имя Resources, включает ссылку на проект ResourceLibrary и потому может пользоваться содержащимися в нем ресурсами:

Разделение ресурсов с помощью библиотеки классов

Чтобы организовать наилучшим образом большое количество ресурсов, можно создавать отдельные словари ресурсов так, как было описано раньше. Однако эти словари должны быть обязательно включены в файл generic.xaml, чтобы к ним можно было получать доступ.

Следующим шагом является создание имени ключа для разделяемого ресурса, который хранится в сборке ResourceLibrary. В случае использования ComponentResourceKey необходимо предоставить два фрагмента информации — ссылку на соответствующий класс в сборке библиотеки классов и описательным идентификатор ресурса. Ссылка на класс — это часть той "магии", которая позволяет WPF разделять ресурс с другими сборками. При использовании этого ресурса сборки будут предоставлять ту же самую ссылку на класс и тот же самый идентификатор ресурса.

То, как класс выглядит на самом деле, роли не играет, да и содержать код ему вовсе не обязательно. Сборка, в которой определен данный тип, представляет собой ту же сборку, в которой ComponentResourceKey будет искать ресурс. В примере, показанном на рисунке, используется класс CustomResources, который не содержит какой-либо код.

Теперь можно создать имя ключа с использованием этого класса и идентификатора ресурса:

х:Кеу="{ComponentResourceKey TypeInTargetAssembly={х:Type local:CustomResources},
   ResourceId=SadTileBrush}"

Ниже приведен весь необходимый для файла generic.xaml код разметки, который включает в себя единственный ресурс ImageBrush, использующий другое графическое изображение:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ResourceLibrary"
    >

    <ImageBrush
      x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:CustomResources}, ResourceId=SadTileBrush}"
      TileMode="Tile"
      ViewportUnits="Absolute" Viewport="0 0 32 32"
      ImageSource="ResourceLibrary;component/sadface.jpg" Opacity="0.3">
    </ImageBrush>

</ResourceDictionary>

В этом примере имеется одна неожиданная деталь — свойство ImageSource больше не устанавливается с использованием имени изображения (sadface.jpg). Взамен применяется более сложный относительный URI, который четко указывает, что изображение является частью компонента ResourceLibrary. Такой шаг является обязательным, поскольку данный ресурс будет использоваться в контексте еще одного приложения.

Если указать просто имя изображения, то приложение будет искать изображение только в собственных ресурсах. Именно потому и необходим относительный URI, указывающий на компонент, в котором хранится изображение.

Теперь, когда словарь ресурсов создан, его можно использовать в еще одном приложении. Для начала нужно позаботиться об определении префикса для сборки библиотеки классов, как показано ниже:

xmlns:res="clr-namespace:ResourceLibrary;assembly=ResourceLibrary"

Затем можно использовать объект DynamicResource, содержащий ComponentResourceKey. (В этом есть смысл, поскольку ComponentResourceKey является именем ресурса.) Применяемый клиентом ComponentResourceKey ничем не отличается от ComponentResourceKey из библиотеки классов. Вы предоставляете ссылку на тот же самый класс и тот же идентификатор ресурса. Единственное отличие заключается в том, что здесь нельзя применять тот же самый префикс пространства имен XML. В приведенном примере вместо префикса local используется res, чтобы подчеркнуть тот факт, что определение класса CustomResources размещается в другой сборке.

Ниже представлен полный код файла разметки второго приложения:

<Window x:Class="Resources.ResourceFromLibrary"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:res="clr-namespace:ResourceLibrary;assembly=ResourceLibrary"
    Title="ResourceFromLibrary" Height="300" Width="300"    
    >
  <Window.Resources>
    <ImageBrush x:Key="TileBrush" TileMode="Tile"
                ViewportUnits="Absolute" Viewport="0 0 32 32"
                ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
  </Window.Resources>
  <StackPanel Margin="5">
    <Button Background="{StaticResource TileBrush}" Padding="5"
            FontWeight="Bold" FontSize="14" Margin="5"
              >A Resource From This Assembly</Button>

    <Button Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type res:CustomResources}, ResourceId=SadTileBrush}}"
            Padding="5" Margin="5"
            FontWeight="Bold" FontSize="14">
      A Resource From ResourceLibrary</Button>

    <Button Background="{DynamicResource {x:Static res:CustomResources.SadTileBrush}}"
        Padding="5" Margin="5"
        FontWeight="Bold" FontSize="14">
      A Resource From ResourceLibrary
    </Button>

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