Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Три важнейших класса:
System.Windows.FrameworkElementSystem.Windows.FrameworkContentElementSystem.Windows.Applicationсодержат свойство Resources типа System.Windows.ResourceDictionary. Соответственно, поскольку большинство элементов управления WPF наследуют от этих классов, то любой из них содержит свою коллекцию Resources как экземпляр класса-словаря ResourceDictionary, которую можно наполнить. Ресурсы, определенные в элементе, доступны только этому элементу и его потомкам.
Чаще всего ресурсы определяют в коллекции корневого элемента-окна <Window.Resources> или в коллекции приложения <Application.Resources>. Там их разработчику привычнее найти и область видимости таких ресурсов распространяется на все дочерние элементы, то есть на все визуальное дерево. По умолчанию, если не принимать специальных мер, ресурсы окна невидимы в других окнах приложения, а ресурсы одного приложения (сборки) видимы во всех его окнах, но невидимы в других сборках.
Ресурсы представляют собой XAML -эквивалент создания констант в коде (константных переменных или полей). Они позволяют определить объект любого типа один раз, а затем многократно использовать его в коде по мере необходимости, чаще всего - для настройки визуальных элементов пользовательского интерфейса. Помещенная в ресурсы информация отделяется от процедурного кода, но может этим кодом избирательно фильтроваться в зависимости от предпочтений пользователя, культуры, текущего региона или настроек операционной системы. Для различных культур и свойств можно использовать различные коллекции ресурсов, настраивающих интерфейс.
Каждому объекту, задекларированному (определенному) как ресурс, необходимо задать значение свойства x:Key. Префикс 'x:' означает пространство имен XAML, а не пространство имен WPF. Свойство x:Key означает имя, по которому другие элементы WPF будут получать доступ к ресурсу. Это свойство должно быть уникальным не во всем приложении, а лишь в той коллекции-словаре, где определено. Имена ресурсов определяются с учетом регистра.
При синтаксическом разборе ссылки на ресурс в XAML, синтаксический анализатор XAML ( парсер XAML, parse - разбор, разбирать) выполняет поиск ресурса с соответствующим ключем вначале в словаре текущего элемента, подключающего ресурс. Если ресурс не был найдет, поиск последовательно продолжается в словарях родителей элемента в направлении к корню визуального дерева окна, а затем в словаре приложения и далее - в присоединенных сборках или ресурсах системы. Если ресурс нигде не был найден, выдается исключение XamlParseException.
В этом упражнении мы рассмотрим особенности использования объектных ресурсов на этапе проектирования в разметочной части (декларативный режим) и управление ими в кодовой части (режим runtime ). Код упражнения построим так, чтобы он иллюстрировал по возможности больше разных сторон работы с объектными ресурсами. Вначале приведем работоспособный код, а потом его разберем.
Многие студенты, выполняя работы по WPF или ASP.NET в среде проектирования Visual Studio, где разметочная часть класса окна представлена вкладками Design и XAML (в ASP.NET: вкладками Design, Split и Source, а еще раньше - Design и HTML ), не пользуясь режимом Design, все же оставляют частично эту вкладку открытой. То же самое происходит и с ненужными в данный момент выдвижными панелями оболочки. Ничего иного, как занять полезное место на экране, эта вкладка нам не дает, поскольку мы в основном работаем только с дескрипторным кодом. Поэтому, лучше полностью раскрыть вкладку XAML на весь экран, пользы будет больше. А режим Design оставим дизайнеру, домохозяйке или "будущему программисту Васе". Хотя сами туда тоже иногда будем заглядывать (для справки).
Создайте новое решение с именем
Добавьте к корню проекта
Выделяя последовательно эти файлы рисунков, установите (или проверьте) в панели Properties для них следующие свойстваBuild Action=ResourceCopy to Output Directory=Do not copyBuild Action=NoneCopy to Output Directory=Copy if newerЭти растровые изображения мы в дальнейшем используем для создания ресурсов, обеспечивающих фон кнопок интерфейса окна.
Откройте файл App.xaml и добавьте в коллекцию ресурсов приложения объект SolidColorBrush со следующими настройками<Application x:Class="UseResource.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<!-- Определение ресурсов в коллекции приложения -->
<Application.Resources>
<SolidColorBrush x:Key="ControlColorBrush"
Color="{x:Static SystemColors.ControlColor}" />
</Application.Resources>
</Application>
Цвет для настройки помещаемого в ресурсы объекта SolidColorBrush извлекается из принадлежащего WPF статического свойства ControlColor статического класса SystemColors. Этот ресурс мы используем в параметрах окна для задания его цвета фона. В дальнейшем мы более подробно рассмотрим механизм использования статических свойств и полей в объектных ресурсах, а пока заметим, что псевдоним x в строке
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
отображает пространство имен WPF на разметку для синтаксического анализатора XAML.
Заполните файл Window1.xaml следующим содержимым (разметка файла приводится полностью)<Window x:Class="UseResource.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UseResource"
MinHeight="300"
MinWidth="300"
Height="300"
Width="300"
WindowStartupLocation="CenterScreen"
Background="{StaticResource ControlColorBrush}">
<!-- Определение ресурсов в коллекции окна -->
<Window.Resources>
<TextBlock x:Key="Title1"
TextAlignment="Center"
Text="1. Модификация объекта ресурса"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush1"
Color="Blue" />
<ImageBrush x:Key="BackgroundBrush1"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5"
/>
<TextBlock x:Key="Title2"
TextAlignment="Center"
Text="2. Полная замена объекта ресурса"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush2"
Color="Red"
/>
<ImageBrush x:Key="BackgroundBrush2"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3"
/>
</Window.Resources>
<!-- Содержимое окна -->
<StackPanel>
<!-- Это не ресурс, а элемент визуального дерева -->
<TextBlock HorizontalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,5">
Применение ресурсов
</TextBlock>
<!-- Синтаксис подключения ресурсов 'как элемента свойства' -->
<Label HorizontalAlignment="Center">
<Label.Content>
<StaticResource ResourceKey="Title1" />
</Label.Content>
</Label>
<Button Name="btn1" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16" >
<Button.Foreground>
<DynamicResource ResourceKey="ForegroundBrush1" />
</Button.Foreground>
<Button.Background>
<DynamicResource ResourceKey="BackgroundBrush1" />
</Button.Background>
Использует DynamicResource
</Button>
<Button Name="btn2" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16" >
<Button.Foreground>
<StaticResource ResourceKey="ForegroundBrush1" />
</Button.Foreground>
<Button.Background>
<StaticResource ResourceKey="BackgroundBrush1" />
</Button.Background>
Использует StaticResource
</Button>
<!-- Синтаксис подключения ресурсов 'как расширения разметки' -->
<Label HorizontalAlignment="Center"
Content="{StaticResource ResourceKey=Title2}" />
<Button Click="btn3_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource ResourceKey=ForegroundBrush2}"
Background="{DynamicResource ResourceKey=BackgroundBrush2}">
Использует DynamicResource
</Button>
<!-- Упрощенный синтаксис без указания ResourceKey -->
<Button Click="btn4_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{StaticResource ForegroundBrush2}"
Background="{StaticResource BackgroundBrush2}">
Использует StaticResource
</Button>
</StackPanel>
</Window>
Представление разметки в режиме Design на данном этапе должно выглядеть так
Вначале мы создали 6 объектов с настройками и разместили их в коллекции Window.Resources ресурсов окна. Два объекта TextBlock будут отображать заголовки. Два объекта SolidColorBrush будут использованы для определения цвета надписей на кнопках. Два объекта ImageBrush будут задавать фон кнопок. В настройках объектов, помещенных в ресурсы, в качестве обязательного параметра должен присутствовать ключ x:Key, по которому эти ресурсы будут извлечены элементами интерфейса окна.
Затем мы разместили элементы пользовательского интерфейса - надписи и кнопки. В первой половине элементов мы подключили ресурсы по синтаксису " элемента свойства ", а в остальных - по синтаксису " расширения разметки ". В обоих случаях главным атрибутом в подключении является параметр ResourceKey. Обратите внимание, что при использовании синтаксиса расширения разметки этот параметр можно не указывать и запрещается ключевое слово размещать в кавычках. Но оба синтаксиса подключения являются совершенно равнозначными.
Обязательным при подключении ресурсов к элементу является указание способа их применения с помощью ключевого слова DynamicResource или StaticResource. Отсюда и название - динамические или статические ресурсы. Если ресурс в процедурном коде полностью не подменяется новым объектом, а остается постоянным или только редактируется (адрес объекта не меняется), то каким бы способом он не был подключен к использующим его элементам, внешне эти элементы ведут себя одинаково.
Но если помещенный в ресурс объект в процедурном коде заменить на новый (адрес объекта изменится), то элемент со статическим способом подключения этого не заметит и будет использовать старую копию ресурса. Элемент же с динамическим способом подключения это заметит и сразу использует новый ресурс. В WPF операция смены ресурса преобразуется в вызов метода InvalidateVisual(), означающий, что элемент должен быть немедленно перерисован вызовом виртуального метода OnRender().
Итак, если в коллекции ресурсов полностью не менять адрес объекта ресурса на новый, то элементы с разными способами подключения ведут себя одинаково: замечают все изменения в существующем ресурсе. А объекты ресурсов, соответственно, исправно посылают элементам уведомления о своих изменениях. Обычно необходимость в полной смене ресурса встречается редко, поэтому лучше использовать статический способ подключения. Тем более, что динамический ресурс требует повышенных накладных расходов при обработке.
Для разнообразия, заголовки на форме окна мы тоже определили как ресурсы TextBlock. Но чтобы их присоединить к интерфейсу и отобразить, пришлось использовать элементы Label с универсальным свойством Content типа Object.
Следующим шагом мы создадим процедурный код, который будет управлять ресурсами и наглядно продемонстрирует разницу между способами подключения DynamicResource и StaticResource.
Заполните файл Window1.xaml.cs следующим процедурным кодомusing System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace UseResource
{
public partial class Window1 : Window
{
public Window1()
{
// Инициализация интерфейсных элементов
InitializeComponent();
// Создаем и добавляем программно объекты ресурсов
// для будущей замены декларативных ресурсов целиком
//
// ... alternateColor
SolidColorBrush solidColorBrush = new SolidColorBrush();
solidColorBrush.Color = Colors.Blue;
this.Resources.Add("alternateColor", solidColorBrush);
//
// ... alternateImage
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(
new Uri(@"Images\10440.jpg", UriKind.Relative)
);
imageBrush.TileMode = TileMode.Tile;
imageBrush.ViewportUnits = BrushMappingMode.Absolute;// Не путать с ViewboxUnits
imageBrush.Viewport = new Rect(0, 0, 32, 32);
imageBrush.Opacity = 0.5D;// Можно и без D, преобразует неявно из float в double
this.Resources.Add("alternateImage", imageBrush);
// Регистрируем часть обработчиков программно
btn1.Click += btn1_Click;
btn2.Click += btn2_Click;
/*
// Эти ресурсы не будем сохранять, потому что мы их будем только модифицировать
resourceDictionary.Add("ForegroundBrush1", this.Resources["ForegroundBrush1"]);
resourceDictionary.Add("BackgroundBrush1", this.Resources["BackgroundBrush1"]);
//*/
// Заполняем словарь ресурсов: дублируем ссылки на ресурсы
resourceDictionary.Add("originColor", this.Resources["ForegroundBrush2"]);
resourceDictionary.Add("originImage", this.Resources["BackgroundBrush2"]);
}
// Поле-ссылка на объект, может объявляться в любом месте как член класса
ResourceDictionary resourceDictionary = new ResourceDictionary();
// Просто модифицируем объект ресурса
bool changeResourceFlag1 = true;
void btn1_Click(object sender, RoutedEventArgs e)
{
// Извлекаем ресурсы из словаря окна и приводим к типу двумя вариантами
SolidColorBrush colorRes = (SolidColorBrush)this.Resources["ForegroundBrush1"];
ImageBrush imageRes = this.Resources["BackgroundBrush1"] as ImageBrush;
if (changeResourceFlag1)// Редактируем существующий объект ресурса
{
colorRes.Color = Colors.Red;
imageRes.Viewport = new Rect(0, 0, 16, 16);
}
else // Возвращаем к прежнему
{
colorRes.Color = Colors.Blue;
imageRes.Viewport = new Rect(0, 0, 10, 10);
/*
// Восстанавливать из словаря нельзя - ссылаются на один и тот же объект
colorRes = resourceDictionary["ForegroundBrush1"];
imageRes = resourceDictionary["BackgroundBrush1"];
//*/
}
changeResourceFlag1 = !changeResourceFlag1;// Готовим другой вариант
}
// Просто модифицируем объект ресурса
void btn2_Click(object sender, RoutedEventArgs e)
{
// Ищем ресурс по ключу, начиная с текущего элемента к корню дерева
// Метод TryFindResource защищать не нужно, потому что при неудаче он
// не генерирует исключение, а просто возвращает нулевую ссылку
SolidColorBrush colorRes = ((FrameworkElement)sender).
TryFindResource("ForegroundBrush1") as SolidColorBrush;
ImageBrush imageRes = (ImageBrush)((FrameworkElement)sender).
TryFindResource("BackgroundBrush1");
if (colorRes == null || imageRes == null)// Ресурс не найден
return;
if (changeResourceFlag1)// Редактируем существующий объект ресурса
{
colorRes.Color = Colors.Red;
imageRes.Viewport = new Rect(0, 0, 16, 16);
}
else // Возвращаем к прежнему
{
colorRes.Color = Colors.Blue;
imageRes.Viewport = new Rect(0, 0, 10, 10);
}
changeResourceFlag1 = !changeResourceFlag1;// Готовим другой вариант
}
// Заменяем ресурс новым объектом
bool changeResourceFlag2 = true;
private void btn3_Click(object sender, RoutedEventArgs e)
{
if (changeResourceFlag2)// Полностью заменяем объекты ресурсов на новые
{
this.Resources["ForegroundBrush2"] = this.Resources["alternateColor"];
this.Resources["BackgroundBrush2"] = this.Resources["alternateImage"];
}
else // Восстанавливаем объекты из словаря ресурсов
{
this.Resources["ForegroundBrush2"] = resourceDictionary["originColor"];
this.Resources["BackgroundBrush2"] = resourceDictionary["originImage"];
}
changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
}
// Заменяем ресурс новым объектом
private void btn4_Click(object sender, RoutedEventArgs e)
{
// Повышаем полномочия ссылки, чтобы применить метод FindResource. Можно повысить
// только до FrameworkElement, там уже есть методы FindResource и TryFindResource
Button btn = (Button)sender;
// Объявляем локальные ссылки
Object colorRes, imageRes;
if (changeResourceFlag2)// Полностью заменяем объекты ресурсов на новые
{
// Защищенно ищем ресурс методом FindResource, который
// при отсутствии ресурса генерирует исключение
try
{
colorRes = btn.FindResource("alternateColor");
imageRes = btn.FindResource("alternateImage");
}
catch { return; } // Если не нашли, все оставляем как есть
this.Resources["ForegroundBrush2"] = colorRes;
this.Resources["BackgroundBrush2"] = imageRes;
}
else // Восстанавливаем объекты из словаря ресурсов
{
this.Resources["ForegroundBrush2"] = resourceDictionary["originColor"];
this.Resources["BackgroundBrush2"] = resourceDictionary["originImage"];
}
changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
}
}
}
Запустите приложение и испытайте работу ресурсовСнимки окна до изменения и после изменения объектов ресурсов в коде будут такими
![]() |
![]() |
Кнопки в нашем приложении мы использовали только для того, чтобы совместить элементы, отображающие ресурсы, с элементами, инициирующими код их изменения. Первые две кнопки (сверху) реализуют обработчики, которые модифицируют существующие ресурсы, не меняя адреса подключенных объектов. Они решают одну и ту же задачу, но разными способами. Последние две кнопки в своих обработчиках полностью меняют объекты ресурсов, но тоже делают это по разному.
Последняя кнопка, к которой ресурс подключен как статический, не замечает полной замены объектов ресурсов и продолжает отображать прежние ресурсы. Вторая сверху кнопка, которая тоже использует статический способ подключения ресурсов, реагирует на изменения, поскольку адресует прежний объект, хотя и с измененными настройками.
Мы точно знаем, что наши используемые элементами ресурсы размещены в коллекции ресурсов окна. Поэтому мы жестко адресуемся в коде к этим ресурсам по ссылке this, указывающей на объект окна. Такой способ не совсем гибкий - при переносе разметки ресурсов в коллекцию приложения придется переделывать процедурный код. Использование методов TryFindResource() и FindResource(), наследуемых объектами от класса FrameworkElement, делает код более универсальным. Но метод FindResource() генерирует исключение, если ресурс не будет найден, поэтому его разумнее поместить в контейнер обработки исключений.
Для разнообразия, мы использовали класс-словарь ResourceDictionary, но он сохраняет только ссылки на существующие ресурсы. Поэтому для восстановления модифицированных ресурсов мы его на применяли, поскольку он ссылается на те же уже модифицированные объекты ресурсов. Зато при полной смене объектов ресурсов, когда ссылки коллекции окна получат адреса новых объектов, ссылки словаря продолжат адресовать прежние ресурсы и словарь правомерно использовать для восстановления адресации.
В конструкторе класса окна мы разместили код программного создания ресурсов. Поскольку при срабатывании конструктора сам объект окна уже существует, то этот код можно было бы разместить и до вызова метода InitializeComponent(). Там же в конструкторе мы динамически выполнили регистрацию части обработчиков в дополнение к регистрации другой их части в разметке.
Разберитесь с кодом, который снабжен подробными комментариямиВ приведенной разметке мы определили ресурсы в коллекции ресурсов окна, затем извлекаем их как в элементах интерфейса декларативно, так и программно в процедурном коде. Но ресурсы можно выносить в отдельные файлы и использовать не только в одном, но во множестве проектов. Эта возможность достижима с помощью применения ресурсных словарей. Ресурсный словарь - это независимая коллекция ресурсов в отдельном файле XAML, которую можно импортировать в коллекцию Resources любого элемента приложения WPF.
Продемонстрируем применение ресурсного словаря в нашем приложении. Вынесем определение ресурсов в отдельный файл и подключим этот файл к коллекции ресурсов окна.
В панели Solution Explorer выделите корень проекта UseResource и командой Project/Add Resource Dictionary создайте файл с именем ResBrushes1.xaml
Перенесите (вырезать в одном, вставить в другом) в контейнер <ResourceDictionary> из файла Window1.xaml определенное там ранее содержимое контейнера <Window.Resources>, чтобы файл ResBrushes1.xaml стал таким<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Key="Title1"
TextAlignment="Center"
Text="1. Модификация объекта ресурса"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush1"
Color="Blue" />
<ImageBrush x:Key="BackgroundBrush1"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5"
/>
<TextBlock x:Key="Title2"
TextAlignment="Center"
Text="2. Полная замена объекта ресурса"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush2"
Color="Red"
/>
<ImageBrush x:Key="BackgroundBrush2"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3"
/>
</ResourceDictionary>
Теперь осталось подключить файл ресурсов ResBrushes1.xaml к контейнеру ресурсов окна Window1.xaml.
Добавьте в контейнер <Window.Resources> файла Window1.xaml следующий код разметки<!-- Определение ресурсов в коллекции окна -->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResBrushes1.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Запустите приложение - оно работает как и прежде, как будто бы мы и не переносили ресурсы во внешний файлРассмотренный прием позволяет размещать ресурсы не только в одном, но и в нескольких файлах и использовать их в нужном месте по мере необходимости. Продемонстрируем это.
Выделите корень проекта и добавьте командой Project/Add Resource Dictionary еще один файл с именем ResBrushes2.xaml
Перенесите (вырезать в одном, вставить в другом) из файла ResBrushes1.xaml в контейнер <ResourceDictionary> нового файла ResBrushes2.xaml вторую половину объектов, чтобы содержимое файлов-словарей стало таким<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Key="Title1"
TextAlignment="Center"
Text="1. Модификация объекта ресурса"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush1"
Color="Blue" />
<ImageBrush x:Key="BackgroundBrush1"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5"
/>
</ResourceDictionary>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Key="Title2"
TextAlignment="Center"
Text="2. Полная замена объекта ресурса"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush2"
Color="Red"
/>
<ImageBrush x:Key="BackgroundBrush2"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3"
/>
</ResourceDictionary>
Добавьте в контейнер <Window.Resources> файла Window1.xaml следующий код разметки<!-- Определение ресурсов в коллекции окна -->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResBrushes1.xaml" />
<ResourceDictionary Source="ResBrushes2.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Запустите приложение - оно работает как и прежде, как будто бы мы и не распределили ресурсы по отдельным файлам
Переключите редактор файла Window1.xaml в режим Design и убедитесь, что визуальное представление элементов окна совпадает с тем, которое оно имело до вынесения нами ресурсов в отдельные файлы. Значит дизайнер оболочки видит эти ресурсы на этапе проектирования, ну а компилятор их тем более видит!При добавлении в приложение новых файлов-словарей ресурсов нужно следить за тем, чтобы для них свойство Build Action было установлено в значение Page (такое значение имеют все XAML -файлы). Это гарантирует компиляцию словаря ресурсов для достижения лучшей производительности. Допустимо также значение Resource, но в этом случае ресурс просто встраивается в сборку, но не компилируется. Компиляция такого ресурса происходит только во время выполнения, из-за чего синтаксический анализ в реальном времени немного (незаметно) замедляет работу приложения.
TryFindResource() и FindResource(), передав им имя несуществующего ресурсаFindResource() убрать контейнер обработки исключений и передать ему имя несуществующего ресурсаМеханизм доступа к ресурсам построен таким образом, что ресурсы можно создавать в обычном классе, а затем по мере надобности извлекать их оттуда. Это относится как к пользовательским, так и к библиотечным классам.
В WPF заготовлены 3 специальных библиотечных класса, хранящие наиболее востребованные ресурсы. Это классы SystemColors, SystemFonts и SystemParameters - все они находятся в пространстве имен System.Windows сборки PresentationFramework.dll и являются частью WPF. В пространстве имен System.Drawing тоже имеются одноименные классы SystemColors и SystemFonts, но они относятся к технологии Windows Forms и никакого отношения к рассматриваемым ресурсам не имеют.
Тот механизм хранения и извлечения ресурсов, который мы здесь будем применять, основан на использовании статических свойств и статических полей классов. Существует и другой подход к решению подобной задачи - хранение и извлечение свойств и полей экземпляров классов. Но такой способ относится к теме привязки данных и в этом разделе рассматриваться не будет.
Ключевым компонентом в месте извлечения статического свойства или поля является служебное слово x:Static, настраивающее синтаксический анализатор на поиск такого ресурса. В предыдущем упражнении мы уже подобным синтакисом пользовались для извлечения системного цвета при определении кисти в ресурсе файла App.xaml так
<!-- Определение ресурсов в коллекции приложения -->
<Application.Resources>
<SolidColorBrush x:Key="ControlColorBrush"
Color="{x:Static SystemColors.ControlColor}" />
</Application.Resources>
Присоединение ресурса кисти к целевому элементу в файле Window1.xaml мы задали в синтаксисе расширения разметки так
<Window x:Class="UseResource.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UseResource"
MinHeight="300"
MinWidth="300"
Height="300"
Width="300"
Background="{StaticResource ControlColorBrush}">
...............................................
</Window>
Если посмотреть свойства любого из классов SystemColors, SystemFonts или SystemParameters, то они существуют парами: одно свойство представляет объект, а парное свойство представляет ключ с добавкой Key. Например, MenuBarBrush и MenuBarBrushKey. Все свойства с окончанием Key возвращают объект ключа типа ComponentResourceKey. Соответственно, существуют и два синтаксиса: извлечение самого объекта и извлечение объекта по ключу.
Извлекаемое статическое свойство или поле должны соответствовать по типу целевому элементу так, чтобы синтаксический анализатор мог легко их конвертировать. Если свойство представляет нужный тип, то его сразу можно использовать без предварительного размещения в коллекции ресурсов окна. Так например, цвет фона окна можно сразу задать одним из следующих способов (все способы эквивалентны)
<Window
....................................................
Background="{x:Static SystemColors.MenuBarBrush}"
>
....................................................
</Window>
<Window
....................................................
Background="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}"
>
....................................................
</Window>
<Window
....................................................
>
<Window.Background>
<x:Static Member="SystemColors.MenuBarBrush" />
</Window.Background>
....................................................
</Window>
<Window
....................................................
>
<Window.Background>
<DynamicResourceExtension>
<DynamicResourceExtension.ResourceKey>
<x:Static Member="SystemColors.MenuBarBrushKey" />
</DynamicResourceExtension.ResourceKey>
</DynamicResourceExtension>
</Window.Background>
....................................................
</Window>
Извлекать можно не только статические поля и свойства упомянутых классов WPF, но и любых библиотечных классов, подходящих по типу целевому элементу. Но если применяются классы, не принадлежащие WPF, то соответствующее пространство имен и сборку нужно отобразить на разметку, использовав произвольный псевдоним (префикс). Долее этот префикс будет ассоциировать с пространством имен класса, в котором находятся статические поля или свойства. При использовании нескольких пространств имен и сборок, не принадлежащих WPF, все их нужно указать, чтобы у синтаксического анализатора не было проблем с видимостью классов. Например
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom1="clr-namespace:System;assembly=mscorlib"
xmlns:custom2="clr-namespace:MyNamespace"
.................................................
>
.................................................
</Window>
Продемонстрируем сказанное на простом приложении.
Добавьте к решению командой File/Add/New Project новый проект типа WPF Application с именем UseStaticMember и назначьте его стартовым
Заполните файл Window1.xaml следующим исходным кодом разметки<Window x:Class="UseStaticMember.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Использование статических членов классов как ресурсы"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
>
<StackPanel>
</StackPanel>
</Window>
Добавьте в открывающий дескриптор окна библиотечный ресурс (объект) определения параметра цвета фона по синтаксису расширения разметки<Window x:Class="UseStaticMember.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Использование статических членов классов как ресурсы"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
Background="{x:Static SystemColors.MenuBarBrush}"
>
<StackPanel>
</StackPanel>
</Window>
Добавьте в открывающий дескриптор компоновочной панели код определения префиксов пространств имен системной сборки и сборки текущего проекта (для текущего проекта сборку можно не указывать, если ее имя совпадает с пространством имен)<StackPanel
xmlns:s="clr-namespace:System;assembly=System"
xmlns:my="clr-namespace:UseStaticMember"
>
</StackPanel>
Поместите в компоновочную панель файла Window1.xaml следующий код разметки (файл приводится полностью)<Window x:Class="UseStaticMember.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Использование статических членов классов как ресурсы"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
Background="{x:Static SystemColors.MenuBarBrush}"
>
<StackPanel
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:my="clr-namespace:UseStaticMember"
>
<TextBlock
TextAlignment="Center"
FontFamily="Arial"
Foreground="Red"
FontWeight="Bold"
FontSize="14">
<LineBreak />
Ресурсы из статических свойств системных классов
</TextBlock>
<TextBlock>
<Label Content="OSVersion:" />
<Label Content="{x:Static my:UserClass.OSVersion}" />
<LineBreak />
<Label Content="Version:" />
<Label Content="{x:Static my:UserClass.Version}" />
<LineBreak />
<Label Content="DateTime:" />
<Label Content="{x:Static my:UserClass.DateTime}" />
<LineBreak />
<LineBreak />
<Label Content="MachineName:" />
<Label Content="{x:Static s:Environment.MachineName}" />
<LineBreak />
<Label Content="UserName:" />
<Label Content="{x:Static s:Environment.UserName}" />
<LineBreak />
<Label Content="UserDomainName:" />
<Label Content="{x:Static s:Environment.UserDomainName}" />
<LineBreak />
<Label Content="SystemDirectory:" />
<Label Content="{x:Static s:Environment.SystemDirectory}" />
<LineBreak />
<Label Content="CurrentDirectory:" />
<Label Content="{x:Static s:Environment.CurrentDirectory}" />
<LineBreak />
<Label Content="CommandLine:" />
<Label Content="{x:Static s:Environment.CommandLine}" />
<LineBreak />
<Label Content="ExitCode:" />
<Label Content="{x:Static s:Environment.ExitCode}" />
<LineBreak />
<Label Content="HasShutdownStarted:" />
<Label Content="{x:Static s:Environment.HasShutdownStarted}" />
<LineBreak />
<TextBlock Padding="10,0,10,0"
Background="{x:Static my:UserClass.backgroundBrush}"
Foreground="{x:Static my:UserClass.foregroundBrush}"
FontSize="{x:Static my:UserClass.FontSize}"
>
<TextBlock.FontFamily>
<x:Static Member="my:UserClass.FontFamily" />
</TextBlock.FontFamily>
Ресурсы из статических полей нашего класса
</TextBlock>
</TextBlock>
</StackPanel>
</Window>
В застраничном файле процедурного кода Window1.xaml.cs создайте класс с именем UserClass и определите в нем статические члены (static members) следующим образом (файл приводится полностью)using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace UseStaticMember
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public static class UserClass
{
// Статические поля только для чтения
public static readonly LinearGradientBrush backgroundBrush = // Имя любое
new LinearGradientBrush(Colors.Blue,
Colors.White, new Point(0.5, 0.5), new Point(1, 1));
public static readonly LinearGradientBrush foregroundBrush = // Имя любое
new LinearGradientBrush(Colors.Red,
Colors.White, new Point(1, 1), new Point(0, 0));
public static readonly Double FontSize = 24; // Имя любое
public static readonly FontFamily FontFamily = // Имя любое
new FontFamily("Times New Roman Italic");
// Статические свойства доступа
public static String OSVersion // Имя любое
{
get { return Environment.OSVersion.ToString(); }
}
public static String Version // Имя любое
{
get { return Environment.Version.ToString(); }
}
public static String DateTime // Имя любое
{
get { return System.DateTime.Now.ToString(); }
}
}
}
Запустите приложение - результат будет следующим
К приведенному коду следует сделать несколько замечаний.
string, int, bool, double. Более сложные типы он конвертировать не может и мы три типа: OSVersion, Version, DateTime.Now преобразуем в коде статических свойств классаreadonly можно из кода убратьstatic из объявления заголовка класса тоже можно убрать. Компилятор сам знает, что по классу со статическими членами нельзя создавать экземпляры и, в случае необходимости, присечет такие попытки программиста еще на этапе компиляцииDateTime, применяется системное имя, то его нужно прописывать полностью с префиксом библиотечного пространства имен - System.DateTime
Разберитесь с кодом упражненияИногда возникает необходимость использовать одни и те же ресурсы во многих проектах. Чтобы их не копировать каждый раз в новые приложения, существует отличный способ упаковать ресурсы один раз в отдельную библиотечную сборку, которую можно будет подключать к любому приложению. В этом упражнении мы и познакомимся с таким способом. Нового здесь очень мало, многое мы уже знаем из предыдущего материала, поэтому сразу приступим.
Командой меню File/Add/New Project оболочки добавьте к решению новый проект DLL -библиотеки с именем
В панели Solution Explorer переименуйте файл с дежурным именем CustomControl1.cs в CustomResources.cs
Откройте файл CustomResources.cs на редактирование, ознакомтесь с инструкцией подключения библиотеки к исполнимой сборке и удалите ее из файла, чтобы не заслоняла будущий код
Полностью удалите из класса CustomResources статический конструктор, который в данной задаче нам не пригодится (или не удаляйте, пусть болтается как есть)using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LibraryResource
{
public class CustomResources : Control
{
/*
// Статический конструктор в данной задаче не используется
static CustomResources()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomResources),
new FrameworkPropertyMetadata(typeof(CustomResources)));
}
//*/
}
}
Наполните класс CustomResources следующим кодом, определяющим ключи доступа к ресурсамnamespace LibraryResource
{
public class CustomResources : Control
{
// Статические поля (для разнообразия)
public static readonly ComponentResourceKey Title1Key =
new ComponentResourceKey(typeof(CustomResources), "Title1");
public static readonly ComponentResourceKey Title2Key =
new ComponentResourceKey(typeof(CustomResources), "Title2");
// Добавили флаг для регулирования извлечения в элементах разметки
public static bool changeResourceKeyFlag = true;
// Добавляем статические свойства
public static ComponentResourceKey ForegroundBrushKey
{
get
{
if (changeResourceKeyFlag)
return new ComponentResourceKey(
typeof(CustomResources), "ForegroundBrush1");
else
return new ComponentResourceKey(
typeof(CustomResources), "ForegroundBrush2");
}
}
public static ComponentResourceKey BackgroundBrushKey
{
get
{
if (changeResourceKeyFlag)
return new ComponentResourceKey(
typeof(CustomResources), "BackgroundBrush1");
else
return new ComponentResourceKey(
typeof(CustomResources), "BackgroundBrush2");
}
}
}
}
В автоматически созданной папке Themes откройте файл Generic.xaml и удалите из него заготовку контейнера <Style>...</Style> (имя файла зарезервировано и его менять нельзя!)<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LibraryResource">
<Style TargetType="{x:Type local:CustomResources}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomResources}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Обратите внимание, что мастер оболочки автоматически сгенерировал оператор подключения пространства имен нашей библиотеки к разметке с псевдонимом local. Мы вольны изменить этот префикс на любой другой, но делать это ни к чему и будем в далнейшем использовать именно его для доступа к статическим свойствам класса CustomResources при определении наших ресурсов.
Наполните ресурсный словарь <ResourceDictionary>...</ResourceDictionary> следующей разметкой определения ресурсов (окончательная разметка файла Generic.xaml приводится полностью)<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LibraryResource">
<TextBlock x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=Title1}"
TextAlignment="Center"
Text="Группа кнопок 1"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet" />
<SolidColorBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=ForegroundBrush1}"
Color="Blue" />
<ImageBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=BackgroundBrush1}"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5" />
<TextBlock x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=Title2}"
TextAlignment="Center"
Text="Группа кнопок 2"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet" />
<SolidColorBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=ForegroundBrush2}"
Color="Red" />
<ImageBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=BackgroundBrush2}"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3" />
</ResourceDictionary>
В приведенной разметке мы использовали такие же ресурсы, что и в первом упражнении, чтобы не слишком напрягаться. Обратите внимание на новый синтаксис определения ключа. В атрибутах расширения разметки указана вся необходимая информация для синтаксического анализатора. Она указывает, в каком классе искать статическое свойство идентификатора ресурса и в каком пространстве имен текущей библиотечной сборки находится этот класс (об этом сообщает префикс).
В ресурсы мы поместили объекты, которые будем извлекать в исполнимом приложении при настройке интерфейсных элементов визуального дерева окна. Каждый из атрибутов визуальных элементов имеет свой тип и надеется извлечь ресурсный объект точно такого же типа.
В панели Solution Explorer вызовите контекстное меню для проекта LibraryResource и выполните команду Build (или Rebuild ), чтобы откомпилировать проект и создать библиотечную сборку. Эта сборка неисполнимая, поэтому запускать проект на выполнение не следует, компилятор все равно выдаст ошибку
В панели Solution Explorer выделите узел проекта LibraryResource и щелкните на пиктограмме Show All Files этой панели для раскрытия дерева файлов проекта
Мы видим, что оболочка откомпилировала проект и создала библиотечную сборку LibraryResource.dll, которую теперь можно использовать вместе с любой исполнимой сборкой для извлечения заложенных в нее ресурсов. Разумеется, в промышленных проектах следующим шагом следовало бы написать подробную инструкцию к пользованию этой библиотекой (выпустить документацию - почти как MSDN ), но мы этого делать не будем.
Теперь осталось применить нашу библиотеку ресурсов в исполнимом приложении, где мы разместим в окне интерфейсные элементы и для их настройки будем извлекать соответствующие объекты как ресурсы из откомпилированной библиотечной сборки.
Командой меню File/Add/New Project оболочки добавьте к решению новый проект с именем
В панели Solution Explorer раскройте узел проекта UseResource первого упражнения и перетащите из него мышью папку Images на корневой узел проекта UseLibraryResource. Оболочка скопирует эту папку вместе с содержимым в наш новый проект UseLibraryResource
Выделяя Build Action=ResourceCopy to Output Directory=Do not copyBuild Action=NoneCopy to Output Directory=Copy if newer
В панели Solution Explorer выделите узел проекта UseLibraryResource и щелкните на пиктограмме Show All Files этой панели для раскрытия дерева файлов проекта
Раскройте узел
В панели Solution Explorer вызовите контекстное меню для узла References и выполните команду Add Reference, чтобы вызвать окно подключения библиотечных сборок
В окне Ссылка на эту библиотеку будет подключена к текущему проекту UseLibraryResource и появится в его узле References.
Откомпилируйте (или запустите) пустой проект UseLibraryResource и в панели Solution Explorer щелкните на кнопке Refresh, чтобы обновить представление панели
Мы видим, что оболочка скопировала готовую библиотечную сборку LibraryResource.dll в один каталог с исполнимой сборкой UseLibraryResource.exe. Важно помнить, что для нормальной работы приложения, использующего ресурсы нашей библиотечной сборки (или нуждающегося в любой другой пользовательской библиотеке), файл LibraryResource.dll при развертывании на клиентской машине всегда должен находиться в одном каталоге с исполнимым файлом приложения. Либо при развертывании библиотеку нужно будет размещать и специальным образом регистрировать в глобальном хранилище сборок GAC (Global Assembly Cache) на клиентской машине, что гораздо сложнее (но выполнимо).
Заполните файл Window1.xaml проекта UseLibraryResource следующей разметкой<Window x:Class="UseLibraryResource.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:resLib="clr-namespace:LibraryResource;assembly=LibraryResource"
Title="UseLibraryResource"
MinHeight="300"
MinWidth="300"
Height="300"
Width="300"
WindowStartupLocation="CenterScreen"
Background="{x:Static SystemColors.MenuBarBrush}">
<!-- Содержимое окна -->
<StackPanel>
<!-- Это не ресурс, а элемент визуального дерева -->
<TextBlock HorizontalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,5">
Применение ресурсов
</TextBlock>
<!-- Синтаксис подключения ресурсов как статических полей и свойств библиотеки по ключу -->
<Label HorizontalAlignment="Center"
Content="{DynamicResource {x:Static resLib:CustomResources.Title1Key}}" />
<Button Name="btn1" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {x:Static resLib:CustomResources.ForegroundBrushKey}}"
Background="{DynamicResource {x:Static resLib:CustomResources.BackgroundBrushKey}}"
Content="Только DynamicResource" />
<Button Name="btn2" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {x:Static resLib:CustomResources.ForegroundBrushKey}}"
Background="{DynamicResource {x:Static resLib:CustomResources.BackgroundBrushKey}}"
Content="Только DynamicResource" />
<!-- Синтаксис подключения ресурсов из ресурсного словаря библиотеки по имени ресурса -->
<Label HorizontalAlignment="Center"
Content="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=Title2}}" />
<Button Click="Button_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=ForegroundBrush2}}"
Background="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=BackgroundBrush2}}"
Content="Только DynamicResource" />
<Button Click="Button_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=ForegroundBrush2}}"
Background="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=BackgroundBrush2}}"
Content="Только DynamicResource" />
</StackPanel>
</Window>
Переключите редактор разметки в режим Design - оболочка видит библиотечные ресурсы уже на этапе проектирования
Заполните файл Window1.xaml.cs проекта UseLibraryResource следующим процедурным кодом (код файла Window1.xaml.cs приводится целиком)using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using LibraryResource;// Подключаем пространство имен библиотеки с ресурсами
namespace UseLibraryResource
{
public partial class Window1 : Window
{
public Window1()
{
// Инициализация интерфейсных элементов
InitializeComponent();
// Создаем и добавляем программно объекты ресурсов в коллекцию окна
//
// ... alternateColor
SolidColorBrush solidColorBrush = new SolidColorBrush();
solidColorBrush.Color = Colors.Blue;
this.Resources.Add("alternateColor", solidColorBrush);
//
// ... alternateImage
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(
new Uri(@"Images\10440.jpg", UriKind.Relative)
);
imageBrush.TileMode = TileMode.Tile;
imageBrush.ViewportUnits = BrushMappingMode.Absolute;// Не путать с ViewboxUnits
imageBrush.Viewport = new Rect(0, 0, 32, 32);
imageBrush.Opacity = 0.5D;// Можно и без D, преобразует неявно из float в double
this.Resources.Add("alternateImage", imageBrush);
// Регистрируем обработчики программно
btn1.Click += btn1_Click;
btn2.Click += btn2_Click;
}
// Используем внешние ресурсы
void btn1_Click(object sender, RoutedEventArgs e)
{
CustomResources.changeResourceKeyFlag =
!CustomResources.changeResourceKeyFlag;
btn1.Foreground = btn1.TryFindResource(
CustomResources.ForegroundBrushKey) as SolidColorBrush;
btn1.Background = (ImageBrush)btn1.TryFindResource(
CustomResources.BackgroundBrushKey);
}
// Используем внешние ресурсы
void btn2_Click(object sender, RoutedEventArgs e)
{
CustomResources.changeResourceKeyFlag =
!CustomResources.changeResourceKeyFlag;
try
{
btn2.Foreground = btn2.FindResource(
CustomResources.ForegroundBrushKey) as SolidColorBrush;
btn2.Background = (ImageBrush)btn2.FindResource(
CustomResources.BackgroundBrushKey);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
}
bool changeResourceFlag2 = true;
private void Button_Click(object sender, RoutedEventArgs e)
{
// Приводим ссылку к типу Button
Button btn = sender as Button;
if (btn == null)
return;
if (changeResourceFlag2)
{
// Используем внутренние ресурсы окна
btn.Foreground= (SolidColorBrush)this.Resources["alternateColor"];
btn.Background = (ImageBrush)this.Resources["alternateImage"];
}
else
{
// Используем внешние ресурсы
btn.Foreground = btn.TryFindResource(
CustomResources.ForegroundBrushKey) as SolidColorBrush;
btn.Background = (ImageBrush)btn.TryFindResource(
CustomResources.BackgroundBrushKey);
}
changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
}
}
}
Запустите и испытайте работу приложения UseLibraryResource, разберитесь с кодомНе стоит забывать, что при извлечении ресурсов через ключи типа ComponentResourceKey следует всегда использовать обязательно динамические, а не статические ресурсы.
Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Три важнейших класса:
System.Windows.FrameworkElementSystem.Windows.FrameworkContentElementSystem.Windows.Applicationсодержат свойство Resources типа System.Windows.ResourceDictionary. Соответственно, поскольку большинство элементов управления WPF наследуют от этих классов, то любой из них содержит свою коллекцию Resources как экземпляр класса-словаря ResourceDictionary, которую можно наполнить. Ресурсы, определенные в элементе, доступны только этому элементу и его потомкам.
Чаще всего ресурсы определяют в коллекции корневого элемента-окна <Window.Resources> или в коллекции приложения <Application.Resources>. Там их разработчику привычнее найти и область видимости таких ресурсов распространяется на все дочерние элементы, то есть на все визуальное дерево. По умолчанию, если не принимать специальных мер, ресурсы окна невидимы в других окнах приложения, а ресурсы одного приложения (сборки) видимы во всех его окнах, но невидимы в других сборках.
Ресурсы представляют собой XAML -эквивалент создания констант в коде (константных переменных или полей). Они позволяют определить объект любого типа один раз, а затем многократно использовать его в коде по мере необходимости, чаще всего - для настройки визуальных элементов пользовательского интерфейса. Помещенная в ресурсы информация отделяется от процедурного кода, но может этим кодом избирательно фильтроваться в зависимости от предпочтений пользователя, культуры, текущего региона или настроек операционной системы. Для различных культур и свойств можно использовать различные коллекции ресурсов, настраивающих интерфейс.
Каждому объекту, задекларированному (определенному) как ресурс, необходимо задать значение свойства x:Key. Префикс 'x:' означает пространство имен XAML, а не пространство имен WPF. Свойство x:Key означает имя, по которому другие элементы WPF будут получать доступ к ресурсу. Это свойство должно быть уникальным не во всем приложении, а лишь в той коллекции-словаре, где определено. Имена ресурсов определяются с учетом регистра.
При синтаксическом разборе ссылки на ресурс в XAML, синтаксический анализатор XAML ( парсер XAML, parse - разбор, разбирать) выполняет поиск ресурса с соответствующим ключем вначале в словаре текущего элемента, подключающего ресурс. Если ресурс не был найдет, поиск последовательно продолжается в словарях родителей элемента в направлении к корню визуального дерева окна, а затем в словаре приложения и далее - в присоединенных сборках или ресурсах системы. Если ресурс нигде не был найден, выдается исключение XamlParseException.
В этом упражнении мы рассмотрим особенности использования объектных ресурсов на этапе проектирования в разметочной части (декларативный режим) и управление ими в кодовой части (режим runtime ). Код упражнения построим так, чтобы он иллюстрировал по возможности больше разных сторон работы с объектными ресурсами. Вначале приведем работоспособный код, а потом его разберем.
Многие студенты, выполняя работы по WPF или ASP.NET в среде проектирования Visual Studio, где разметочная часть класса окна представлена вкладками Design и XAML (в ASP.NET: вкладками Design, Split и Source, а еще раньше - Design и HTML ), не пользуясь режимом Design, все же оставляют частично эту вкладку открытой. То же самое происходит и с ненужными в данный момент выдвижными панелями оболочки. Ничего иного, как занять полезное место на экране, эта вкладка нам не дает, поскольку мы в основном работаем только с дескрипторным кодом. Поэтому, лучше полностью раскрыть вкладку XAML на весь экран, пользы будет больше. А режим Design оставим дизайнеру, домохозяйке или "будущему программисту Васе". Хотя сами туда тоже иногда будем заглядывать (для справки).
Создайте новое решение с именем
Добавьте к корню проекта
Выделяя последовательно эти файлы рисунков, установите (или проверьте) в панели Properties для них следующие свойстваBuild Action=ResourceCopy to Output Directory=Do not copyBuild Action=NoneCopy to Output Directory=Copy if newerЭти растровые изображения мы в дальнейшем используем для создания ресурсов, обеспечивающих фон кнопок интерфейса окна.
Откройте файл App.xaml и добавьте в коллекцию ресурсов приложения объект SolidColorBrush со следующими настройками<Application x:Class="UseResource.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<!-- Определение ресурсов в коллекции приложения -->
<Application.Resources>
<SolidColorBrush x:Key="ControlColorBrush"
Color="{x:Static SystemColors.ControlColor}" />
</Application.Resources>
</Application>
Цвет для настройки помещаемого в ресурсы объекта SolidColorBrush извлекается из принадлежащего WPF статического свойства ControlColor статического класса SystemColors. Этот ресурс мы используем в параметрах окна для задания его цвета фона. В дальнейшем мы более подробно рассмотрим механизм использования статических свойств и полей в объектных ресурсах, а пока заметим, что псевдоним x в строке
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
отображает пространство имен WPF на разметку для синтаксического анализатора XAML.
Заполните файл Window1.xaml следующим содержимым (разметка файла приводится полностью)<Window x:Class="UseResource.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UseResource"
MinHeight="300"
MinWidth="300"
Height="300"
Width="300"
WindowStartupLocation="CenterScreen"
Background="{StaticResource ControlColorBrush}">
<!-- Определение ресурсов в коллекции окна -->
<Window.Resources>
<TextBlock x:Key="Title1"
TextAlignment="Center"
Text="1. Модификация объекта ресурса"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush1"
Color="Blue" />
<ImageBrush x:Key="BackgroundBrush1"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5"
/>
<TextBlock x:Key="Title2"
TextAlignment="Center"
Text="2. Полная замена объекта ресурса"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush2"
Color="Red"
/>
<ImageBrush x:Key="BackgroundBrush2"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3"
/>
</Window.Resources>
<!-- Содержимое окна -->
<StackPanel>
<!-- Это не ресурс, а элемент визуального дерева -->
<TextBlock HorizontalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,5">
Применение ресурсов
</TextBlock>
<!-- Синтаксис подключения ресурсов 'как элемента свойства' -->
<Label HorizontalAlignment="Center">
<Label.Content>
<StaticResource ResourceKey="Title1" />
</Label.Content>
</Label>
<Button Name="btn1" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16" >
<Button.Foreground>
<DynamicResource ResourceKey="ForegroundBrush1" />
</Button.Foreground>
<Button.Background>
<DynamicResource ResourceKey="BackgroundBrush1" />
</Button.Background>
Использует DynamicResource
</Button>
<Button Name="btn2" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16" >
<Button.Foreground>
<StaticResource ResourceKey="ForegroundBrush1" />
</Button.Foreground>
<Button.Background>
<StaticResource ResourceKey="BackgroundBrush1" />
</Button.Background>
Использует StaticResource
</Button>
<!-- Синтаксис подключения ресурсов 'как расширения разметки' -->
<Label HorizontalAlignment="Center"
Content="{StaticResource ResourceKey=Title2}" />
<Button Click="btn3_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource ResourceKey=ForegroundBrush2}"
Background="{DynamicResource ResourceKey=BackgroundBrush2}">
Использует DynamicResource
</Button>
<!-- Упрощенный синтаксис без указания ResourceKey -->
<Button Click="btn4_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{StaticResource ForegroundBrush2}"
Background="{StaticResource BackgroundBrush2}">
Использует StaticResource
</Button>
</StackPanel>
</Window>
Представление разметки в режиме Design на данном этапе должно выглядеть так

Вначале мы создали 6 объектов с настройками и разместили их в коллекции Window.Resources ресурсов окна. Два объекта TextBlock будут отображать заголовки. Два объекта SolidColorBrush будут использованы для определения цвета надписей на кнопках. Два объекта ImageBrush будут задавать фон кнопок. В настройках объектов, помещенных в ресурсы, в качестве обязательного параметра должен присутствовать ключ x:Key, по которому эти ресурсы будут извлечены элементами интерфейса окна.
Затем мы разместили элементы пользовательского интерфейса - надписи и кнопки. В первой половине элементов мы подключили ресурсы по синтаксису " элемента свойства ", а в остальных - по синтаксису " расширения разметки ". В обоих случаях главным атрибутом в подключении является параметр ResourceKey. Обратите внимание, что при использовании синтаксиса расширения разметки этот параметр можно не указывать и запрещается ключевое слово размещать в кавычках. Но оба синтаксиса подключения являются совершенно равнозначными.
Обязательным при подключении ресурсов к элементу является указание способа их применения с помощью ключевого слова DynamicResource или StaticResource. Отсюда и название - динамические или статические ресурсы. Если ресурс в процедурном коде полностью не подменяется новым объектом, а остается постоянным или только редактируется (адрес объекта не меняется), то каким бы способом он не был подключен к использующим его элементам, внешне эти элементы ведут себя одинаково.
Но если помещенный в ресурс объект в процедурном коде заменить на новый (адрес объекта изменится), то элемент со статическим способом подключения этого не заметит и будет использовать старую копию ресурса. Элемент же с динамическим способом подключения это заметит и сразу использует новый ресурс. В WPF операция смены ресурса преобразуется в вызов метода InvalidateVisual(), означающий, что элемент должен быть немедленно перерисован вызовом виртуального метода OnRender().
Итак, если в коллекции ресурсов полностью не менять адрес объекта ресурса на новый, то элементы с разными способами подключения ведут себя одинаково: замечают все изменения в существующем ресурсе. А объекты ресурсов, соответственно, исправно посылают элементам уведомления о своих изменениях. Обычно необходимость в полной смене ресурса встречается редко, поэтому лучше использовать статический способ подключения. Тем более, что динамический ресурс требует повышенных накладных расходов при обработке.
Для разнообразия, заголовки на форме окна мы тоже определили как ресурсы TextBlock. Но чтобы их присоединить к интерфейсу и отобразить, пришлось использовать элементы Label с универсальным свойством Content типа Object.
Следующим шагом мы создадим процедурный код, который будет управлять ресурсами и наглядно продемонстрирует разницу между способами подключения DynamicResource и StaticResource.
Заполните файл Window1.xaml.cs следующим процедурным кодомusing System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace UseResource
{
public partial class Window1 : Window
{
public Window1()
{
// Инициализация интерфейсных элементов
InitializeComponent();
// Создаем и добавляем программно объекты ресурсов
// для будущей замены декларативных ресурсов целиком
//
// ... alternateColor
SolidColorBrush solidColorBrush = new SolidColorBrush();
solidColorBrush.Color = Colors.Blue;
this.Resources.Add("alternateColor", solidColorBrush);
//
// ... alternateImage
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(
new Uri(@"Images\10440.jpg", UriKind.Relative)
);
imageBrush.TileMode = TileMode.Tile;
imageBrush.ViewportUnits = BrushMappingMode.Absolute;// Не путать с ViewboxUnits
imageBrush.Viewport = new Rect(0, 0, 32, 32);
imageBrush.Opacity = 0.5D;// Можно и без D, преобразует неявно из float в double
this.Resources.Add("alternateImage", imageBrush);
// Регистрируем часть обработчиков программно
btn1.Click += btn1_Click;
btn2.Click += btn2_Click;
/*
// Эти ресурсы не будем сохранять, потому что мы их будем только модифицировать
resourceDictionary.Add("ForegroundBrush1", this.Resources["ForegroundBrush1"]);
resourceDictionary.Add("BackgroundBrush1", this.Resources["BackgroundBrush1"]);
//*/
// Заполняем словарь ресурсов: дублируем ссылки на ресурсы
resourceDictionary.Add("originColor", this.Resources["ForegroundBrush2"]);
resourceDictionary.Add("originImage", this.Resources["BackgroundBrush2"]);
}
// Поле-ссылка на объект, может объявляться в любом месте как член класса
ResourceDictionary resourceDictionary = new ResourceDictionary();
// Просто модифицируем объект ресурса
bool changeResourceFlag1 = true;
void btn1_Click(object sender, RoutedEventArgs e)
{
// Извлекаем ресурсы из словаря окна и приводим к типу двумя вариантами
SolidColorBrush colorRes = (SolidColorBrush)this.Resources["ForegroundBrush1"];
ImageBrush imageRes = this.Resources["BackgroundBrush1"] as ImageBrush;
if (changeResourceFlag1)// Редактируем существующий объект ресурса
{
colorRes.Color = Colors.Red;
imageRes.Viewport = new Rect(0, 0, 16, 16);
}
else // Возвращаем к прежнему
{
colorRes.Color = Colors.Blue;
imageRes.Viewport = new Rect(0, 0, 10, 10);
/*
// Восстанавливать из словаря нельзя - ссылаются на один и тот же объект
colorRes = resourceDictionary["ForegroundBrush1"];
imageRes = resourceDictionary["BackgroundBrush1"];
//*/
}
changeResourceFlag1 = !changeResourceFlag1;// Готовим другой вариант
}
// Просто модифицируем объект ресурса
void btn2_Click(object sender, RoutedEventArgs e)
{
// Ищем ресурс по ключу, начиная с текущего элемента к корню дерева
// Метод TryFindResource защищать не нужно, потому что при неудаче он
// не генерирует исключение, а просто возвращает нулевую ссылку
SolidColorBrush colorRes = ((FrameworkElement)sender).
TryFindResource("ForegroundBrush1") as SolidColorBrush;
ImageBrush imageRes = (ImageBrush)((FrameworkElement)sender).
TryFindResource("BackgroundBrush1");
if (colorRes == null || imageRes == null)// Ресурс не найден
return;
if (changeResourceFlag1)// Редактируем существующий объект ресурса
{
colorRes.Color = Colors.Red;
imageRes.Viewport = new Rect(0, 0, 16, 16);
}
else // Возвращаем к прежнему
{
colorRes.Color = Colors.Blue;
imageRes.Viewport = new Rect(0, 0, 10, 10);
}
changeResourceFlag1 = !changeResourceFlag1;// Готовим другой вариант
}
// Заменяем ресурс новым объектом
bool changeResourceFlag2 = true;
private void btn3_Click(object sender, RoutedEventArgs e)
{
if (changeResourceFlag2)// Полностью заменяем объекты ресурсов на новые
{
this.Resources["ForegroundBrush2"] = this.Resources["alternateColor"];
this.Resources["BackgroundBrush2"] = this.Resources["alternateImage"];
}
else // Восстанавливаем объекты из словаря ресурсов
{
this.Resources["ForegroundBrush2"] = resourceDictionary["originColor"];
this.Resources["BackgroundBrush2"] = resourceDictionary["originImage"];
}
changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
}
// Заменяем ресурс новым объектом
private void btn4_Click(object sender, RoutedEventArgs e)
{
// Повышаем полномочия ссылки, чтобы применить метод FindResource. Можно повысить
// только до FrameworkElement, там уже есть методы FindResource и TryFindResource
Button btn = (Button)sender;
// Объявляем локальные ссылки
Object colorRes, imageRes;
if (changeResourceFlag2)// Полностью заменяем объекты ресурсов на новые
{
// Защищенно ищем ресурс методом FindResource, который
// при отсутствии ресурса генерирует исключение
try
{
colorRes = btn.FindResource("alternateColor");
imageRes = btn.FindResource("alternateImage");
}
catch { return; } // Если не нашли, все оставляем как есть
this.Resources["ForegroundBrush2"] = colorRes;
this.Resources["BackgroundBrush2"] = imageRes;
}
else // Восстанавливаем объекты из словаря ресурсов
{
this.Resources["ForegroundBrush2"] = resourceDictionary["originColor"];
this.Resources["BackgroundBrush2"] = resourceDictionary["originImage"];
}
changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
}
}
}
Запустите приложение и испытайте работу ресурсовСнимки окна до изменения и после изменения объектов ресурсов в коде будут такими
![]() |
![]() |
Кнопки в нашем приложении мы использовали только для того, чтобы совместить элементы, отображающие ресурсы, с элементами, инициирующими код их изменения. Первые две кнопки (сверху) реализуют обработчики, которые модифицируют существующие ресурсы, не меняя адреса подключенных объектов. Они решают одну и ту же задачу, но разными способами. Последние две кнопки в своих обработчиках полностью меняют объекты ресурсов, но тоже делают это по разному.
Последняя кнопка, к которой ресурс подключен как статический, не замечает полной замены объектов ресурсов и продолжает отображать прежние ресурсы. Вторая сверху кнопка, которая тоже использует статический способ подключения ресурсов, реагирует на изменения, поскольку адресует прежний объект, хотя и с измененными настройками.
Мы точно знаем, что наши используемые элементами ресурсы размещены в коллекции ресурсов окна. Поэтому мы жестко адресуемся в коде к этим ресурсам по ссылке this, указывающей на объект окна. Такой способ не совсем гибкий - при переносе разметки ресурсов в коллекцию приложения придется переделывать процедурный код. Использование методов TryFindResource() и FindResource(), наследуемых объектами от класса FrameworkElement, делает код более универсальным. Но метод FindResource() генерирует исключение, если ресурс не будет найден, поэтому его разумнее поместить в контейнер обработки исключений.
Для разнообразия, мы использовали класс-словарь ResourceDictionary, но он сохраняет только ссылки на существующие ресурсы. Поэтому для восстановления модифицированных ресурсов мы его на применяли, поскольку он ссылается на те же уже модифицированные объекты ресурсов. Зато при полной смене объектов ресурсов, когда ссылки коллекции окна получат адреса новых объектов, ссылки словаря продолжат адресовать прежние ресурсы и словарь правомерно использовать для восстановления адресации.
В конструкторе класса окна мы разместили код программного создания ресурсов. Поскольку при срабатывании конструктора сам объект окна уже существует, то этот код можно было бы разместить и до вызова метода InitializeComponent(). Там же в конструкторе мы динамически выполнили регистрацию части обработчиков в дополнение к регистрации другой их части в разметке.
Разберитесь с кодом, который снабжен подробными комментариямиВ приведенной разметке мы определили ресурсы в коллекции ресурсов окна, затем извлекаем их как в элементах интерфейса декларативно, так и программно в процедурном коде. Но ресурсы можно выносить в отдельные файлы и использовать не только в одном, но во множестве проектов. Эта возможность достижима с помощью применения ресурсных словарей. Ресурсный словарь - это независимая коллекция ресурсов в отдельном файле XAML, которую можно импортировать в коллекцию Resources любого элемента приложения WPF.
Продемонстрируем применение ресурсного словаря в нашем приложении. Вынесем определение ресурсов в отдельный файл и подключим этот файл к коллекции ресурсов окна.
В панели Solution Explorer выделите корень проекта UseResource и командой Project/Add Resource Dictionary создайте файл с именем ResBrushes1.xaml
Перенесите (вырезать в одном, вставить в другом) в контейнер <ResourceDictionary> из файла Window1.xaml определенное там ранее содержимое контейнера <Window.Resources>, чтобы файл ResBrushes1.xaml стал таким<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Key="Title1"
TextAlignment="Center"
Text="1. Модификация объекта ресурса"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush1"
Color="Blue" />
<ImageBrush x:Key="BackgroundBrush1"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5"
/>
<TextBlock x:Key="Title2"
TextAlignment="Center"
Text="2. Полная замена объекта ресурса"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush2"
Color="Red"
/>
<ImageBrush x:Key="BackgroundBrush2"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3"
/>
</ResourceDictionary>
Теперь осталось подключить файл ресурсов ResBrushes1.xaml к контейнеру ресурсов окна Window1.xaml.
Добавьте в контейнер <Window.Resources> файла Window1.xaml следующий код разметки<!-- Определение ресурсов в коллекции окна -->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResBrushes1.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Запустите приложение - оно работает как и прежде, как будто бы мы и не переносили ресурсы во внешний файлРассмотренный прием позволяет размещать ресурсы не только в одном, но и в нескольких файлах и использовать их в нужном месте по мере необходимости. Продемонстрируем это.
Выделите корень проекта и добавьте командой Project/Add Resource Dictionary еще один файл с именем ResBrushes2.xaml
Перенесите (вырезать в одном, вставить в другом) из файла ResBrushes1.xaml в контейнер <ResourceDictionary> нового файла ResBrushes2.xaml вторую половину объектов, чтобы содержимое файлов-словарей стало таким<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Key="Title1"
TextAlignment="Center"
Text="1. Модификация объекта ресурса"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush1"
Color="Blue" />
<ImageBrush x:Key="BackgroundBrush1"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5"
/>
</ResourceDictionary>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Key="Title2"
TextAlignment="Center"
Text="2. Полная замена объекта ресурса"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet"
/>
<SolidColorBrush x:Key="ForegroundBrush2"
Color="Red"
/>
<ImageBrush x:Key="BackgroundBrush2"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3"
/>
</ResourceDictionary>
Добавьте в контейнер <Window.Resources> файла Window1.xaml следующий код разметки<!-- Определение ресурсов в коллекции окна -->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResBrushes1.xaml" />
<ResourceDictionary Source="ResBrushes2.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Запустите приложение - оно работает как и прежде, как будто бы мы и не распределили ресурсы по отдельным файлам
Переключите редактор файла Window1.xaml в режим Design и убедитесь, что визуальное представление элементов окна совпадает с тем, которое оно имело до вынесения нами ресурсов в отдельные файлы. Значит дизайнер оболочки видит эти ресурсы на этапе проектирования, ну а компилятор их тем более видит!При добавлении в приложение новых файлов-словарей ресурсов нужно следить за тем, чтобы для них свойство Build Action было установлено в значение Page (такое значение имеют все XAML -файлы). Это гарантирует компиляцию словаря ресурсов для достижения лучшей производительности. Допустимо также значение Resource, но в этом случае ресурс просто встраивается в сборку, но не компилируется. Компиляция такого ресурса происходит только во время выполнения, из-за чего синтаксический анализ в реальном времени немного (незаметно) замедляет работу приложения.
TryFindResource() и FindResource(), передав им имя несуществующего ресурсаFindResource() убрать контейнер обработки исключений и передать ему имя несуществующего ресурсаМеханизм доступа к ресурсам построен таким образом, что ресурсы можно создавать в обычном классе, а затем по мере надобности извлекать их оттуда. Это относится как к пользовательским, так и к библиотечным классам.
В WPF заготовлены 3 специальных библиотечных класса, хранящие наиболее востребованные ресурсы. Это классы SystemColors, SystemFonts и SystemParameters - все они находятся в пространстве имен System.Windows сборки PresentationFramework.dll и являются частью WPF. В пространстве имен System.Drawing тоже имеются одноименные классы SystemColors и SystemFonts, но они относятся к технологии Windows Forms и никакого отношения к рассматриваемым ресурсам не имеют.
Тот механизм хранения и извлечения ресурсов, который мы здесь будем применять, основан на использовании статических свойств и статических полей классов. Существует и другой подход к решению подобной задачи - хранение и извлечение свойств и полей экземпляров классов. Но такой способ относится к теме привязки данных и в этом разделе рассматриваться не будет.
Ключевым компонентом в месте извлечения статического свойства или поля является служебное слово x:Static, настраивающее синтаксический анализатор на поиск такого ресурса. В предыдущем упражнении мы уже подобным синтакисом пользовались для извлечения системного цвета при определении кисти в ресурсе файла App.xaml так
<!-- Определение ресурсов в коллекции приложения -->
<Application.Resources>
<SolidColorBrush x:Key="ControlColorBrush"
Color="{x:Static SystemColors.ControlColor}" />
</Application.Resources>
Присоединение ресурса кисти к целевому элементу в файле Window1.xaml мы задали в синтаксисе расширения разметки так
<Window x:Class="UseResource.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UseResource"
MinHeight="300"
MinWidth="300"
Height="300"
Width="300"
Background="{StaticResource ControlColorBrush}">
...............................................
</Window>
Если посмотреть свойства любого из классов SystemColors, SystemFonts или SystemParameters, то они существуют парами: одно свойство представляет объект, а парное свойство представляет ключ с добавкой Key. Например, MenuBarBrush и MenuBarBrushKey. Все свойства с окончанием Key возвращают объект ключа типа ComponentResourceKey. Соответственно, существуют и два синтаксиса: извлечение самого объекта и извлечение объекта по ключу.
Извлекаемое статическое свойство или поле должны соответствовать по типу целевому элементу так, чтобы синтаксический анализатор мог легко их конвертировать. Если свойство представляет нужный тип, то его сразу можно использовать без предварительного размещения в коллекции ресурсов окна. Так например, цвет фона окна можно сразу задать одним из следующих способов (все способы эквивалентны)
<Window
....................................................
Background="{x:Static SystemColors.MenuBarBrush}"
>
....................................................
</Window>
<Window
....................................................
Background="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}"
>
....................................................
</Window>
<Window
....................................................
>
<Window.Background>
<x:Static Member="SystemColors.MenuBarBrush" />
</Window.Background>
....................................................
</Window>
<Window
....................................................
>
<Window.Background>
<DynamicResourceExtension>
<DynamicResourceExtension.ResourceKey>
<x:Static Member="SystemColors.MenuBarBrushKey" />
</DynamicResourceExtension.ResourceKey>
</DynamicResourceExtension>
</Window.Background>
....................................................
</Window>
Извлекать можно не только статические поля и свойства упомянутых классов WPF, но и любых библиотечных классов, подходящих по типу целевому элементу. Но если применяются классы, не принадлежащие WPF, то соответствующее пространство имен и сборку нужно отобразить на разметку, использовав произвольный псевдоним (префикс). Долее этот префикс будет ассоциировать с пространством имен класса, в котором находятся статические поля или свойства. При использовании нескольких пространств имен и сборок, не принадлежащих WPF, все их нужно указать, чтобы у синтаксического анализатора не было проблем с видимостью классов. Например
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom1="clr-namespace:System;assembly=mscorlib"
xmlns:custom2="clr-namespace:MyNamespace"
.................................................
>
.................................................
</Window>
Продемонстрируем сказанное на простом приложении.
Добавьте к решению командой File/Add/New Project новый проект типа WPF Application с именем UseStaticMember и назначьте его стартовым
Заполните файл Window1.xaml следующим исходным кодом разметки<Window x:Class="UseStaticMember.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Использование статических членов классов как ресурсы"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
>
<StackPanel>
</StackPanel>
</Window>
Добавьте в открывающий дескриптор окна библиотечный ресурс (объект) определения параметра цвета фона по синтаксису расширения разметки<Window x:Class="UseStaticMember.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Использование статических членов классов как ресурсы"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
Background="{x:Static SystemColors.MenuBarBrush}"
>
<StackPanel>
</StackPanel>
</Window>
Добавьте в открывающий дескриптор компоновочной панели код определения префиксов пространств имен системной сборки и сборки текущего проекта (для текущего проекта сборку можно не указывать, если ее имя совпадает с пространством имен)<StackPanel
xmlns:s="clr-namespace:System;assembly=System"
xmlns:my="clr-namespace:UseStaticMember"
>
</StackPanel>
Поместите в компоновочную панель файла Window1.xaml следующий код разметки (файл приводится полностью)<Window x:Class="UseStaticMember.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Использование статических членов классов как ресурсы"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
Background="{x:Static SystemColors.MenuBarBrush}"
>
<StackPanel
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:my="clr-namespace:UseStaticMember"
>
<TextBlock
TextAlignment="Center"
FontFamily="Arial"
Foreground="Red"
FontWeight="Bold"
FontSize="14">
<LineBreak />
Ресурсы из статических свойств системных классов
</TextBlock>
<TextBlock>
<Label Content="OSVersion:" />
<Label Content="{x:Static my:UserClass.OSVersion}" />
<LineBreak />
<Label Content="Version:" />
<Label Content="{x:Static my:UserClass.Version}" />
<LineBreak />
<Label Content="DateTime:" />
<Label Content="{x:Static my:UserClass.DateTime}" />
<LineBreak />
<LineBreak />
<Label Content="MachineName:" />
<Label Content="{x:Static s:Environment.MachineName}" />
<LineBreak />
<Label Content="UserName:" />
<Label Content="{x:Static s:Environment.UserName}" />
<LineBreak />
<Label Content="UserDomainName:" />
<Label Content="{x:Static s:Environment.UserDomainName}" />
<LineBreak />
<Label Content="SystemDirectory:" />
<Label Content="{x:Static s:Environment.SystemDirectory}" />
<LineBreak />
<Label Content="CurrentDirectory:" />
<Label Content="{x:Static s:Environment.CurrentDirectory}" />
<LineBreak />
<Label Content="CommandLine:" />
<Label Content="{x:Static s:Environment.CommandLine}" />
<LineBreak />
<Label Content="ExitCode:" />
<Label Content="{x:Static s:Environment.ExitCode}" />
<LineBreak />
<Label Content="HasShutdownStarted:" />
<Label Content="{x:Static s:Environment.HasShutdownStarted}" />
<LineBreak />
<TextBlock Padding="10,0,10,0"
Background="{x:Static my:UserClass.backgroundBrush}"
Foreground="{x:Static my:UserClass.foregroundBrush}"
FontSize="{x:Static my:UserClass.FontSize}"
>
<TextBlock.FontFamily>
<x:Static Member="my:UserClass.FontFamily" />
</TextBlock.FontFamily>
Ресурсы из статических полей нашего класса
</TextBlock>
</TextBlock>
</StackPanel>
</Window>
В застраничном файле процедурного кода Window1.xaml.cs создайте класс с именем UserClass и определите в нем статические члены (static members) следующим образом (файл приводится полностью)using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace UseStaticMember
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public static class UserClass
{
// Статические поля только для чтения
public static readonly LinearGradientBrush backgroundBrush = // Имя любое
new LinearGradientBrush(Colors.Blue,
Colors.White, new Point(0.5, 0.5), new Point(1, 1));
public static readonly LinearGradientBrush foregroundBrush = // Имя любое
new LinearGradientBrush(Colors.Red,
Colors.White, new Point(1, 1), new Point(0, 0));
public static readonly Double FontSize = 24; // Имя любое
public static readonly FontFamily FontFamily = // Имя любое
new FontFamily("Times New Roman Italic");
// Статические свойства доступа
public static String OSVersion // Имя любое
{
get { return Environment.OSVersion.ToString(); }
}
public static String Version // Имя любое
{
get { return Environment.Version.ToString(); }
}
public static String DateTime // Имя любое
{
get { return System.DateTime.Now.ToString(); }
}
}
}
Запустите приложение - результат будет следующим
К приведенному коду следует сделать несколько замечаний.
string, int, bool, double. Более сложные типы он конвертировать не может и мы три типа: OSVersion, Version, DateTime.Now преобразуем в коде статических свойств классаreadonly можно из кода убратьstatic из объявления заголовка класса тоже можно убрать. Компилятор сам знает, что по классу со статическими членами нельзя создавать экземпляры и, в случае необходимости, присечет такие попытки программиста еще на этапе компиляцииDateTime, применяется системное имя, то его нужно прописывать полностью с префиксом библиотечного пространства имен - System.DateTime
Разберитесь с кодом упражненияИногда возникает необходимость использовать одни и те же ресурсы во многих проектах. Чтобы их не копировать каждый раз в новые приложения, существует отличный способ упаковать ресурсы один раз в отдельную библиотечную сборку, которую можно будет подключать к любому приложению. В этом упражнении мы и познакомимся с таким способом. Нового здесь очень мало, многое мы уже знаем из предыдущего материала, поэтому сразу приступим.
Командой меню File/Add/New Project оболочки добавьте к решению новый проект DLL -библиотеки с именем
В панели Solution Explorer переименуйте файл с дежурным именем CustomControl1.cs в CustomResources.cs
Откройте файл CustomResources.cs на редактирование, ознакомтесь с инструкцией подключения библиотеки к исполнимой сборке и удалите ее из файла, чтобы не заслоняла будущий код
Полностью удалите из класса CustomResources статический конструктор, который в данной задаче нам не пригодится (или не удаляйте, пусть болтается как есть)using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LibraryResource
{
public class CustomResources : Control
{
/*
// Статический конструктор в данной задаче не используется
static CustomResources()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomResources),
new FrameworkPropertyMetadata(typeof(CustomResources)));
}
//*/
}
}
Наполните класс CustomResources следующим кодом, определяющим ключи доступа к ресурсамnamespace LibraryResource
{
public class CustomResources : Control
{
// Статические поля (для разнообразия)
public static readonly ComponentResourceKey Title1Key =
new ComponentResourceKey(typeof(CustomResources), "Title1");
public static readonly ComponentResourceKey Title2Key =
new ComponentResourceKey(typeof(CustomResources), "Title2");
// Добавили флаг для регулирования извлечения в элементах разметки
public static bool changeResourceKeyFlag = true;
// Добавляем статические свойства
public static ComponentResourceKey ForegroundBrushKey
{
get
{
if (changeResourceKeyFlag)
return new ComponentResourceKey(
typeof(CustomResources), "ForegroundBrush1");
else
return new ComponentResourceKey(
typeof(CustomResources), "ForegroundBrush2");
}
}
public static ComponentResourceKey BackgroundBrushKey
{
get
{
if (changeResourceKeyFlag)
return new ComponentResourceKey(
typeof(CustomResources), "BackgroundBrush1");
else
return new ComponentResourceKey(
typeof(CustomResources), "BackgroundBrush2");
}
}
}
}
В автоматически созданной папке Themes откройте файл Generic.xaml и удалите из него заготовку контейнера <Style>...</Style> (имя файла зарезервировано и его менять нельзя!)<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LibraryResource">
<Style TargetType="{x:Type local:CustomResources}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomResources}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Обратите внимание, что мастер оболочки автоматически сгенерировал оператор подключения пространства имен нашей библиотеки к разметке с псевдонимом local. Мы вольны изменить этот префикс на любой другой, но делать это ни к чему и будем в далнейшем использовать именно его для доступа к статическим свойствам класса CustomResources при определении наших ресурсов.
Наполните ресурсный словарь <ResourceDictionary>...</ResourceDictionary> следующей разметкой определения ресурсов (окончательная разметка файла Generic.xaml приводится полностью)<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LibraryResource">
<TextBlock x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=Title1}"
TextAlignment="Center"
Text="Группа кнопок 1"
FontSize="14" FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet" />
<SolidColorBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=ForegroundBrush1}"
Color="Blue" />
<ImageBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=BackgroundBrush1}"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 10 10"
ImageSource="Images/FACE02.ICO"
Opacity="0.5" />
<TextBlock x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=Title2}"
TextAlignment="Center"
Text="Группа кнопок 2"
FontSize="14"
FontWeight="Bold"
FontFamily="Arial"
FontStyle="Italic"
TextDecorations="Underline"
Foreground="DarkViolet" />
<SolidColorBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=ForegroundBrush2}"
Color="Red" />
<ImageBrush x:Key="{ComponentResourceKey
TypeInTargetAssembly={x:Type local:CustomResources},
ResourceId=BackgroundBrush2}"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 24 24"
ImageSource="Images/FACE04.ICO"
Opacity="0.3" />
</ResourceDictionary>
В приведенной разметке мы использовали такие же ресурсы, что и в первом упражнении, чтобы не слишком напрягаться. Обратите внимание на новый синтаксис определения ключа. В атрибутах расширения разметки указана вся необходимая информация для синтаксического анализатора. Она указывает, в каком классе искать статическое свойство идентификатора ресурса и в каком пространстве имен текущей библиотечной сборки находится этот класс (об этом сообщает префикс).
В ресурсы мы поместили объекты, которые будем извлекать в исполнимом приложении при настройке интерфейсных элементов визуального дерева окна. Каждый из атрибутов визуальных элементов имеет свой тип и надеется извлечь ресурсный объект точно такого же типа.
В панели Solution Explorer вызовите контекстное меню для проекта LibraryResource и выполните команду Build (или Rebuild ), чтобы откомпилировать проект и создать библиотечную сборку. Эта сборка неисполнимая, поэтому запускать проект на выполнение не следует, компилятор все равно выдаст ошибку
В панели Solution Explorer выделите узел проекта LibraryResource и щелкните на пиктограмме Show All Files этой панели для раскрытия дерева файлов проекта
Мы видим, что оболочка откомпилировала проект и создала библиотечную сборку LibraryResource.dll, которую теперь можно использовать вместе с любой исполнимой сборкой для извлечения заложенных в нее ресурсов. Разумеется, в промышленных проектах следующим шагом следовало бы написать подробную инструкцию к пользованию этой библиотекой (выпустить документацию - почти как MSDN ), но мы этого делать не будем.
Теперь осталось применить нашу библиотеку ресурсов в исполнимом приложении, где мы разместим в окне интерфейсные элементы и для их настройки будем извлекать соответствующие объекты как ресурсы из откомпилированной библиотечной сборки.
Командой меню File/Add/New Project оболочки добавьте к решению новый проект с именем
В панели Solution Explorer раскройте узел проекта UseResource первого упражнения и перетащите из него мышью папку Images на корневой узел проекта UseLibraryResource. Оболочка скопирует эту папку вместе с содержимым в наш новый проект UseLibraryResource
Выделяя Build Action=ResourceCopy to Output Directory=Do not copyBuild Action=NoneCopy to Output Directory=Copy if newer
В панели Solution Explorer выделите узел проекта UseLibraryResource и щелкните на пиктограмме Show All Files этой панели для раскрытия дерева файлов проекта
Раскройте узел
В панели Solution Explorer вызовите контекстное меню для узла References и выполните команду Add Reference, чтобы вызвать окно подключения библиотечных сборок
В окне Ссылка на эту библиотеку будет подключена к текущему проекту UseLibraryResource и появится в его узле References.
Откомпилируйте (или запустите) пустой проект UseLibraryResource и в панели Solution Explorer щелкните на кнопке Refresh, чтобы обновить представление панели
Мы видим, что оболочка скопировала готовую библиотечную сборку LibraryResource.dll в один каталог с исполнимой сборкой UseLibraryResource.exe. Важно помнить, что для нормальной работы приложения, использующего ресурсы нашей библиотечной сборки (или нуждающегося в любой другой пользовательской библиотеке), файл LibraryResource.dll при развертывании на клиентской машине всегда должен находиться в одном каталоге с исполнимым файлом приложения. Либо при развертывании библиотеку нужно будет размещать и специальным образом регистрировать в глобальном хранилище сборок GAC (Global Assembly Cache) на клиентской машине, что гораздо сложнее (но выполнимо).
Заполните файл Window1.xaml проекта UseLibraryResource следующей разметкой<Window x:Class="UseLibraryResource.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:resLib="clr-namespace:LibraryResource;assembly=LibraryResource"
Title="UseLibraryResource"
MinHeight="300"
MinWidth="300"
Height="300"
Width="300"
WindowStartupLocation="CenterScreen"
Background="{x:Static SystemColors.MenuBarBrush}">
<!-- Содержимое окна -->
<StackPanel>
<!-- Это не ресурс, а элемент визуального дерева -->
<TextBlock HorizontalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,5">
Применение ресурсов
</TextBlock>
<!-- Синтаксис подключения ресурсов как статических полей и свойств библиотеки по ключу -->
<Label HorizontalAlignment="Center"
Content="{DynamicResource {x:Static resLib:CustomResources.Title1Key}}" />
<Button Name="btn1" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {x:Static resLib:CustomResources.ForegroundBrushKey}}"
Background="{DynamicResource {x:Static resLib:CustomResources.BackgroundBrushKey}}"
Content="Только DynamicResource" />
<Button Name="btn2" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {x:Static resLib:CustomResources.ForegroundBrushKey}}"
Background="{DynamicResource {x:Static resLib:CustomResources.BackgroundBrushKey}}"
Content="Только DynamicResource" />
<!-- Синтаксис подключения ресурсов из ресурсного словаря библиотеки по имени ресурса -->
<Label HorizontalAlignment="Center"
Content="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=Title2}}" />
<Button Click="Button_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=ForegroundBrush2}}"
Background="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=BackgroundBrush2}}"
Content="Только DynamicResource" />
<Button Click="Button_Click" Padding="5" Margin="5"
FontWeight="Bold" FontSize="16"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=ForegroundBrush2}}"
Background="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly={x:Type resLib:CustomResources},
ResourceId=BackgroundBrush2}}"
Content="Только DynamicResource" />
</StackPanel>
</Window>
Переключите редактор разметки в режим Design - оболочка видит библиотечные ресурсы уже на этапе проектирования
Заполните файл Window1.xaml.cs проекта UseLibraryResource следующим процедурным кодом (код файла Window1.xaml.cs приводится целиком)using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using LibraryResource;// Подключаем пространство имен библиотеки с ресурсами
namespace UseLibraryResource
{
public partial class Window1 : Window
{
public Window1()
{
// Инициализация интерфейсных элементов
InitializeComponent();
// Создаем и добавляем программно объекты ресурсов в коллекцию окна
//
// ... alternateColor
SolidColorBrush solidColorBrush = new SolidColorBrush();
solidColorBrush.Color = Colors.Blue;
this.Resources.Add("alternateColor", solidColorBrush);
//
// ... alternateImage
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(
new Uri(@"Images\10440.jpg", UriKind.Relative)
);
imageBrush.TileMode = TileMode.Tile;
imageBrush.ViewportUnits = BrushMappingMode.Absolute;// Не путать с ViewboxUnits
imageBrush.Viewport = new Rect(0, 0, 32, 32);
imageBrush.Opacity = 0.5D;// Можно и без D, преобразует неявно из float в double
this.Resources.Add("alternateImage", imageBrush);
// Регистрируем обработчики программно
btn1.Click += btn1_Click;
btn2.Click += btn2_Click;
}
// Используем внешние ресурсы
void btn1_Click(object sender, RoutedEventArgs e)
{
CustomResources.changeResourceKeyFlag =
!CustomResources.changeResourceKeyFlag;
btn1.Foreground = btn1.TryFindResource(
CustomResources.ForegroundBrushKey) as SolidColorBrush;
btn1.Background = (ImageBrush)btn1.TryFindResource(
CustomResources.BackgroundBrushKey);
}
// Используем внешние ресурсы
void btn2_Click(object sender, RoutedEventArgs e)
{
CustomResources.changeResourceKeyFlag =
!CustomResources.changeResourceKeyFlag;
try
{
btn2.Foreground = btn2.FindResource(
CustomResources.ForegroundBrushKey) as SolidColorBrush;
btn2.Background = (ImageBrush)btn2.FindResource(
CustomResources.BackgroundBrushKey);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
}
bool changeResourceFlag2 = true;
private void Button_Click(object sender, RoutedEventArgs e)
{
// Приводим ссылку к типу Button
Button btn = sender as Button;
if (btn == null)
return;
if (changeResourceFlag2)
{
// Используем внутренние ресурсы окна
btn.Foreground= (SolidColorBrush)this.Resources["alternateColor"];
btn.Background = (ImageBrush)this.Resources["alternateImage"];
}
else
{
// Используем внешние ресурсы
btn.Foreground = btn.TryFindResource(
CustomResources.ForegroundBrushKey) as SolidColorBrush;
btn.Background = (ImageBrush)btn.TryFindResource(
CustomResources.BackgroundBrushKey);
}
changeResourceFlag2 = !changeResourceFlag2;// Готовим другой вариант
}
}
}
Запустите и испытайте работу приложения UseLibraryResource, разберитесь с кодомНе стоит забывать, что при извлечении ресурсов через ключи типа ComponentResourceKey следует всегда использовать обязательно динамические, а не статические ресурсы.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.