This article has been localized into Russian by the community.
Ресурсы
Модель WPF предлагает очень удобный функционал: возможность хранить данные как ресурс, локально для элемента управления, локально для всего окна либо глобально для всего приложения. Данные могут быть любыми по факту, начиная от текущей информации, заканчивая иерархией элементов WPF. Это позволяет разместить данные в одном месте и после этого использовать их в разных местах, что может пригодится при разработке.
Этот функционал часто используется для работы со стилями и шаблонами, которые мы еще будем обсуждать в руководстве, но, как будет показано в этой главе - область применения ресурсов очень широкая. Позвольте мне продемонстрировать это простым примером:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourceSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ResourceSample" Height="150" Width="350">
<Window.Resources>
<sys:String x:Key="strHelloWorld">Hello, world!</sys:String>
</Window.Resources>
<StackPanel Margin="10">
<TextBlock Text="{StaticResource strHelloWorld}" FontSize="56" />
<TextBlock>Just another "<TextBlock Text="{StaticResource strHelloWorld}" />" example, but with resources!</TextBlock>
</StackPanel>
</Window>
Ресурсы в WPF имеют ключ (атрибут x:Key), с помощью которого становится возможным сослаться на эти ресурсы из любой другой части приложения, используя ключ с выражением разметки StaticResource. В этом примере я просто сохранил строку в ресурсах, которую позже использовал в двух разных элементах TextBlock.
Статические vs. Динамические ресурсы
В примерах выше, для отсылки к ресурсам, я использовал расширение разметки StaticResource. Однако, существует альтернатива, в виде DynamicResource.
Главным различием этих ресурсов является то, что статические ресурсы (StaticResource) определяются лишь раз, в момент когда загружается файл XAML. Если позже, во время работы приложения, ресурсы будут изменены, то эти изменения не будут отражены в ресурсах, если они статические.
С другой стороны - Динамические ресурсы. Они могут определяться в тот момент, в который это необходимо, а затем, могут быть переопределены во время работы приложения. Представьте это как связывание со статическим значением и связывание с функцией, которая следит за значением и высылает Вам его всякий раз, когда значение меняется. Динамические ресурсы не до конца так работают, но я дал Вам пищу для размышлений, на тему - когда, что использовать. Также динамические ресурсы позволяют Вам использовать ресурсы, которые еще даже не существуют во время разработки приложения, например: вы добавляете эти ресурсы из CodeBehind во время старта приложения.
Другие типы ресурсов
Передача строки была простой задачей, но с помощью ресурсов можно сделать гораздо больше. Я сохраню полноценный массив строк и градиентный цвет фона. Это поможет Вам еще лучше понять, что можно сделать с помощью ресурсов:
<Window x:Class="WpfTutorialSamples.WPF_Application.ExtendedResourceSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ExtendedResourceSample" Height="160" Width="300"
Background="{DynamicResource WindowBackgroundBrush}">
<Window.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
<x:Array x:Key="ComboBoxItems" Type="sys:String">
<sys:String>Item #1</sys:String>
<sys:String>Item #2</sys:String>
<sys:String>Item #3</sys:String>
</x:Array>
<LinearGradientBrush x:Key="WindowBackgroundBrush">
<GradientStop Offset="0" Color="Silver"/>
<GradientStop Offset="1" Color="Gray"/>
</LinearGradientBrush>
</Window.Resources>
<StackPanel Margin="10">
<Label Content="{StaticResource ComboBoxTitle}" />
<ComboBox ItemsSource="{StaticResource ComboBoxItems}" />
</StackPanel>
</Window>
Выше мы добавили несколько дополнительных ресурсов, так, что наше Окно теперь содержит строку, массив строк и градиентную кисть. Строка используется для этикетки, массив строк - это элементы ComboBox, а градиентная кисть использована для фона окна. Как Вы видите, достаточно много интересных и разных вещей может содержаться в качестве ресурсов.
Локальные ресурсы и ресурсы приложения
Сейчас мы расположим ресурсы на уровне Окна, так, что будем в состоянии их использовать с любого места в Окне.
Если Вы нуждаетесь в ресурсе лишь для одного выбранного элемента - можете сделать это локально, путем добавления ресурса к элементу управления, а не всему окну. Это работает так же, как и ресурсы для окна, разница состоит в том, можно ли будет "достучаться" до них с уровня элемента, в котором вы сохранили ресурс.
<StackPanel Margin="10">
<StackPanel.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</StackPanel.Resources>
<Label Content="{StaticResource ComboBoxTitle}" />
</StackPanel>
В этом случае, мы добавили ресурс в StackPanel и, после, использовали его с уровня дочернего элемента - Label'a. Другие элементы внутри StackPanel также смогут его использовать, как и дочерние элементы Label'a. А вот элементы вне StackPanel не будут иметь доступа к введенным ресурсам.
Если Вам необходимо иметь доступ к ресурсу с разных окон, знайте - это тоже возможно. Файл App.xaml может содержать ресурсы так же как и окна (и другие типы элементов). Но когда Вы храните ресурсы в этом файле, то они становятся глобально доступными во всех окнах и UserControl'ах в проекте. Это работает также как и при хранении ресурсов в Окне:
<Application x:Class="WpfTutorialSamples.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="WPF application/ExtendedResourceSample.xaml">
<Application.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</Application.Resources>
</Application>
Использование такое же - WPF автоматически направится в область, где хранится элемент в окне, а позже в файл App.xaml - чтобы найти необходимый ресурс:
<Label Content="{StaticResource ComboBoxTitle}" />
Ресурсы в CodeBehind
Итак, мы имеем прямой доступ ко всем ресурсам из XAML, используя расширения разметки. Однако, Вы можете сделать то же самое из CodeBehind, что может быть вполне полезным в некоторых ситуациях. В предыдущем примере мы увидели, как можно хранить ресурсы в разных местах, ниже будет следующий пример. Мы попытаемся получить доступ к трем различным ресурсам из CodeBehind, причем, каждый из ресурсов будет храниться в разных областях:
App.xaml:
<Application x:Class="WpfTutorialSamples.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="WPF application/ResourcesFromCodeBehindSample.xaml">
<Application.Resources>
<sys:String x:Key="strApp">Hello, Application world!</sys:String>
</Application.Resources>
</Application>
Window:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourcesFromCodeBehindSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ResourcesFromCodeBehindSample" Height="175" Width="250">
<Window.Resources>
<sys:String x:Key="strWindow">Hello, Window world!</sys:String>
</Window.Resources>
<DockPanel Margin="10" Name="pnlMain">
<DockPanel.Resources>
<sys:String x:Key="strPanel">Hello, Panel world!</sys:String>
</DockPanel.Resources>
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10">
<Button Name="btnClickMe" Click="btnClickMe_Click">Click me!</Button>
</WrapPanel>
<ListBox Name="lbResult" />
</DockPanel>
</Window>
CodeBehind:
using System;
using System.Windows;
namespace WpfTutorialSamples.WPF_Application
{
public partial class ResourcesFromCodeBehindSample : Window
{
public ResourcesFromCodeBehindSample()
{
InitializeComponent();
}
private void btnClickMe_Click(object sender, RoutedEventArgs e)
{
lbResult.Items.Add(pnlMain.FindResource("strPanel").ToString());
lbResult.Items.Add(this.FindResource("strWindow").ToString());
lbResult.Items.Add(Application.Current.FindResource("strApp").ToString());
}
}
}
Итак, как Вы можете наблюдать, мы разместили три разных сообщения "Hello, world!": один ресурс из App.xaml, второй - внутри окна, а третий локально для главного контейнера. Интерфейс содержит кнопку и элемент ListBox.
В CodeBehind мы обработали событие нажатия на кнопку, при котором добавили строки в ListBox, как на скриншоте выше. Мы использовали метод FindResource(), который в результате поиска вернул объект (объект мог быть не найден) и сконвертировали результат в строку с помощью метода ToString().
Обратите внимание, мы использовали метод FindResource() на разных областях - первый раз на контейнере, потом на окне и в конце на текущем объекте Application. Становится возможным искать ресурс, так как мы знаем где, но, как уже было сказано, если ресурс не будет найден, поиск пойдет по всей иерархии вверх. Поэтому, мы могли бы использовать этот метод три раза на панели. После панели поиск задержался бы на окне, а позже поднялся бы на уровень приложения, если бы не нашел искомый ресурс.
То же самое не может быть справедливо для обратной ситуации - поиск не будет направлен вниз по иерархии. Инными словами, нельзя начинать поиск с уровня приложения, если ресурс определен локально для элемента либо для окна.