Обложки

64

В некоторых приложениях может понадобиться изменять шаблоны динамически — обычно в соответствии с предпочтениями пользователя. Сделать это довольно просто, хотя процесс недостаточно хорошо документирован. Базовый прием состоит в загрузке нового словаря ресурсов во время выполнения и его использование для замены текущего словаря ресурсов. (При этом не обязательно заменять все ваши ресурсы, а только те, что задействованы в обложке.)

Фокус состоит в извлечении объекта ResourceDictionary, который компилируется и встраивается в виде ресурса в приложение. Для загрузки нужных ресурсов легче всего применять класс ResourceManager.

Например, предположим, что созданы два ресурса, которые определяют альтернативные версии одного и того же шаблона элемента управления для кнопки. Один находится в файле GradientButton.xaml, а другой — в GradientButtonVariant.xaml. Оба файла для лучшей организации помещены в подпапку Resources текущего проекта. Теперь можно создать простое окно, использующее один из этих ресурсов через коллекцию Resources, как показано ниже:

<Window.Resources>
   <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
         <ResourceDictionary
           Source="Resources/GradientButton.xaml"></ResourceDictionary>
      </ResourceDiсtionarу.MergedDictionaries>
   </ResourceDictionary>
</Window.Resources>

Замена разных словарей ресурсов осуществляется с помощью примерно такого кода:

ResourceDictionary newDictionary = new ResourceDictionary();
newDictionary.Source = new Uri("Resources/GradientButtonVariant.xaml", UriKind.Relative);
this.Resources.MergedDictionanes[0] = newDictionary;

Этот код загружает словарь ресурсов по имени GradientButtonVariant и помещает его в первый слот коллекции MergedDictionaries. Он не очищает коллекцию MergedDictionaries (или любой другой из ресурсов окна), т.к. возможно, что существуют привязки к другим словарям ресурсов, которые должны продолжать использоваться.

Он не добавляет нового элемента в коллекцию MergedDictionaries, потому что может возникнуть конфликт между одноименными ресурсами в разных коллекциях.

Для изменения обложки всего приложения необходимо применить тот же подход, но при этом использовать словарь ресурсов приложения. Обновить этот словарь ресурсов можно с помощью следующего кода:

Application.Current.Resources.MergedDictionaries[0] = newDictionary;

Можно также загрузить словарь ресурсов, определенный в другой сборке, с использованием синтаксиса упакованных URI.

При загрузке нового словаря ресурсов все кнопки автоматически обновляются для использования нового шаблона. Если радикальная модификация элемента управления не нужна, в качестве части обложки могут применяться базовые стили.

В этом примере предполагается, что ресурсы GradientButton.xaml и Gradient ButtonVariant.xaml используют типизированный в отношении элементов стиль для автоматического изменения кнопок. Как известно, существует и другой подход — на новый шаблон можно переключиться, вручную установив свойство Template или Style объектов Button. При таком подходе следует использовать ссылку DynamicResource вместо StaticResource. Если применяется StaticResource, шаблон кнопки не будет обновлен при смене обложки.

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

Доступен и другой способ программной загрузки ресурсов. Можно создать класс отделенного кода для вашего словаря ресурсов, подобно тому, как создаете такие классы для окон. Затем вместо использования свойства ResourceDictionary.Source следует непосредственно создать экземпляр этого класса. Такой подход обладает преимуществом строгой типизации (нет шансов ввести неверный URI в свойстве Source), и он позволяет добавлять свойства, методы и прочую функциональность в класс ресурсов.

Хотя создать класс отделенного кода для словаря ресурсов совсем не сложно, Visual Studio не делает это автоматически. Вместо этого понадобится добавить файл кода с частичным классом, который унаследован от ResourceDictionary и вызывает InitializeComponent в своем конструкторе.

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