Руководство для начинающих (часть 2)

Судя по реакции на мою статью WPF. Руководство для начинающих. Часть 1 из 6, набралось достаточно заинтересованных людей, ради которых стоит продолжать работу над этой серией. В этой статье я решил затронуть следующие темы:

  • XAML и код;
  • расширения разметки;
  • ресурсы WPF.

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

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

  • что делать в XAML;
  • что делать в коде;
  • как ссылаться на классы и сборки в XAML;
  • расширения разметки;
  • что такое ресурсы и чем они могут оказаться полезными;
  • как использовать файлы ресурсов.

Короче говоря, именно об этом и будет статья. Я подготовил простой проект, который на самом деле просто демонстрирует синтаксис. Я умышленно решил сделать пустое демонстрационное приложение, поскольку на этом этапе не хочу запутывать вас событиями, командами, привязками данных, шаблонами и стилями. Итак, это приложение намеренно сделано пустым. Если вас такой вариант не устраивает, подавайте на меня в суд. У меня отличный адвокат.

Теперь можно без лишних слов переходить к делу.

Что делать в XAML

Оговорка. Это только рекомендация. Вы вправе делать по-своему, а мне нравится делать именно так.

В общем смысле платформу WPF можно отнести к шаблону типа квази-MVC (Model-View-Controller, модель — представление — поведение). Хотя модель и поведение объединены, разделение кода XAML и кода программной части отлично отделяет представление от логики кода программной части так же, как в платформе ASP.NET. Это не классический шаблон MVC, но благодаря XAML представление действительно отделено.

Рекомендую делать в XAML следующие, на мой взгляд, полезные вещи:

  • фактическая разметка, представляющая интерфейс пользователя;
  • добавление необходимых файлов ресурсов;
  • добавление локальных ресурсов (подробнее об этом см. ниже);
  • возможное объявление привязок команд (подробнее об этом см. в следующей статье).

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

Что делать в коде

Оговорка. Это только рекомендация. Вы вправе делать по-своему, а мне нравится делать именно так.

Я считаю, что файл кода программной части (C# или VB.NET) должен выполнять всю обработку событий и пользовательскую логику для управления интерфейсом пользователя. Сам интерфейс пользователя должен быть в некоторой степени пустым. Есть пара исключений, когда необходимые действия, которые трудно реализовать в коде программной части, стоит выполнять в XAML. Естественно, в таких ситуациях следует использовать XAML. Здесь нет однозначного ответа, вы должны решить сами. Тем не менее я бы рекомендовал следующие действия:

  • вся обработка событий (у вас просто нет выбора, это должно делаться в коде программной части);
  • циклы, вычисления и вызовы других промежуточных методов классов, не относящихся напрямую к интерфейсу пользователя;
  • проверки;
  • обработка исключений, отсутствующая в XAML вследствие его декларативного характера;
  • рекурсивные операции.

В сущности, интерфейс пользователя (XAML) должен только представлять данные, все остальное должно делаться в коде программной части.

Недавно Джош Смит (Josh Smith) опубликовал на сайте codeproject подлинный шаблон MVC для платформы WPF. С ним можно ознакомиться здесь, если вы действительно не можете дождаться и хотите во всем разобраться.

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

Мне понадобилось полчаса, чтобы сделать то же, что и он: пришлось немного с этим повозиться.

Как ссылаться на классы и сборки в XAML

Если вы планируете разрабатывать что-то сложнее приложения Window, вам однозначно придется ссылаться в XAML на собственные классы и элементы управления или даже классы и элементы управления из другой сборки .NET. Сделать это в коде программной части очень просто, нужно только написать:

using System.Windows.Controls

//И на VB.NET

imports System.Windows.Controls

И вот мы можем ссылаться на все объекты в пространстве имен System.Windows.Controls. Все очень легко и удобно в коде программной части, но как это все работает в XAML? Да точно так же! Если мы хотим использовать элемент управления из текущего пространства имен, нужно указать это в коде XAML. Для этого используется директива пространства имен в верхней части файла XAML, где требуется использовать элемент управления или класс. И экземпляры таких классов можно создавать прямо в коде XAML при условии, что ссылки на них используют подходящее пространство имен.

Рассмотрим четыре примера.

  1. Ссылка на сборку 1: использование локального (из этого же пространства имен) элемента управления в элементе Window.
  2. Ссылка на сборку 2: использование локального (из этого же пространства имен) класса в элементе Window.
  3. Ссылка на сборку 3: использование внешнего (из другого пространства имен или другой сборки) элемента управления в элементе Window.
  4. Ссылка на сборку 4: использование внешнего (из другого пространства имен или другой сборки) класса в элементе Window.

Код, прилагаемый к этой статье, снабжен комментариями в файле Window1.xaml, которыми можно воспользоваться, чтобы посмотреть ссылки на эти элементы управления сборки. Я пытался сделать все максимально удобным.

Теперь можно смело продолжить.

Ссылка на сборку 1: использование локального (из этого же пространства имен) элемента управления в элементе Window

Это самый простой из четырех способов ссылки на классы в XAML. Поскольку класс, на который требуется ссылаться, находится в том же пространстве имен, достаточно добавить объявление пространства имен xmlns: в открывающий тег элемента Window (обратите внимание, я использовал элемент Window, но это мог быть элемент Frame или любой другой, где можно размещать другие элементы).

Допустим, что в текущем приложении, над которым я работаю, пространство имен WPF_Tour_Beginners_Part_2, а у сгенерированной сборки то же имя. В этом же пространстве имен есть элемент типа UserControl с именем UserConrtrol2.

Можно использовать следующее объявление пространства имен xmlns: в открывающем теге элемента Window:

xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

Это позволит использовать в разметке XAML элемент UserControl с именем UserConrtrol2 следующим образом:

<!-- Ссылка на сборку 1. Это объявление класса, который не входит в стандартное пространство имен Microsoft.

     Объявление xmlns: в верхней части файла можно использовать для ссылок на локальную (имя local:

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

     Если класс находится в другой сборке, требуется указать сборку в объявлении xmlns:, в противном случае

     требуется только пространство имен. Обратите внимание, что строка clr-namespace/assembly вводится С УЧЕТОМ РЕГИСТРА.

    Этот элемент управления входит в текущее пространство имен или текущую сборку.

-->

<local:UserControl2/>

У вас может возникнуть вопрос, почему я использовал слово local в объявлении xmlns: в элементе Window. Конечно, можно использовать любое имя, но имя local: используется во многих книгах и де-факто стало обозначением для объектов из этого же пространства имен. Я полагаю, что это объясняется тем, что они и правда локальны для текущего пространства имен или текущей сборки.

Ссылка на сборку 2: использование локального (из этого же пространства имен) класса в элементе Window

В этом случае необходимы практически те же действия, что и для локального элемента UserControl. Единственное отличие в том, что элемент управления UserControl — визуальный объект WPF (как правило, он наследуется от элементов Visual или Visual3D), то есть он может использоваться в интерфейсе пользователя. Классы могут наследоваться от элементов Visual или Visual3D, а могут и не наследоваться, поэтому они могут быть, а могут и не быть объектами интерфейса пользователя. Если они не наследуются от Visual или Visual3D, в разметке интерфейса их использовать нельзя, но в качестве ресурсов — можно. Мы еще не обсуждали ресурсы, поэтому пока вам просто придется принять на веру тот факт, что мы можем создать экземпляр класса, разместив ссылку на него в разделе ресурсов или в файле ресурсов файла XAML.

Допустим, что в текущем приложении, над которым я работаю, пространство имен WPF_Tour_Beginners_Part_2, а у сгенерированной сборки то же имя. А также что в этом пространстве имен есть пользовательский класс LocalClass.

Как и раньше, можно использовать следующее объявление пространства имен xmlns: в открывающем теге элемента Window:

xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

Это позволит использовать пользовательский класс LocalClass в разметке XAML в виде ресурса.

<StackPanel x:Name="sp1" Orientation="Vertical">

    <!--Я добавил эту строку только для того, чтобы продемонстрировать возможность добавления классов в разметку XAML. Использованные мною классы

         не могут входить в интерфейс пользователя, поскольку они не являются элементами управления интерфейса (не наследуются от класса Visual).

        Поэтому я поместил их в словарь ресурсов.

         -->

    <StackPanel.Resources>

        <!-- Ссылка на сборку 2. В разметке XAML можно использовать локальные классы (из этого же пространства имен) при условии, что

             объявление xmlns: вверху этого файла является корректным -->

        <local:LocalClass x:Key="localClass1" AnIntProp="5"/>

    </StackPanel.Resources>

</StackPanel>

Что можно сказать об этом? Мы создали экземпляр класса LocalClass в XAML и присвоили его свойству AnIntProp значение 5. Обратите внимание, что для создания экземпляра класса в XAML в исходном классе должен быть конструктор без параметров.

При необходимости мы могли бы использовать этот экземпляр класса LocalClass в коде программной части. Можно просто захватить ресурс и использовать созданный объект точно так же, как если бы он был создан и добавлен в кучу с помощью ключевого слова new. Рассмотрим эту ситуацию снова с использованием ресурса (извините, что забегаю вперед, но это очень важно).

Ссылка на сборку 3: использование внешнего (из другого пространства имен или другой сборки) элемента управления в элементе Window

В этом случае необходимы практически те же действия, что и для локального элемента UserControl. Единственное отличие в том, что элемент управления UserControl, который мы пытаемся использовать в текущем объекте Window, фактически находится в другой сборке (в демонстрационном приложении используется сборка SeperateWPFUserControl_Dll). Итак, очевидно, необходимо ссылаться на эту сборку SeperateWPFUserControl_Dll. Но как и ссылаться на нее, и использовать ее в XAML?

Что ж, тут нет ничего отличного от того, что мы уже видели. Единственное изменение объявления пространства имен xmlns: заключается в добавлении имени Assembly.

Предположим, что в текущем приложении, над которым я работаю, пространство имен WPF_Tour_Beginners_Part_2, и мы пытаемся ссылаться на сборку сторонних разработчиков (SeperateWPFUserControl_Dll в демонстрационном приложении), а эта сборка содержит элемент управления UserControl с именем UserControl1.

Как и раньше, можно использовать следующее объявление пространства имен xmlns: в открывающем теге элемента Window, но в этот раз необходимо включить также имя сборки:

xmlns:SeperateWPFUserControlDll="clr-namespace:SeperateWPFUserControl_Dll;assembly=SeperateWPFUserControl_Dll"

Это позволит использовать в разметке XAML элемент UserControl с именем UserConrtrol1 следующим образом:

<!-- Ссылка на сборку 3. Это объявление класса, который не входит в стандартное пространство имен Microsoft.

     Объявление xmlns: в верхней части файла можно использовать для ссылок на локальную (имя local:

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

     Если класс находится в другой сборке, требуется указать сборку в объявлении xmlns:, в противном случае

     требуется только пространство имен. Обратите внимание, что строка clr-namespace/assembly вводится С УЧЕТОМ РЕГИСТРА.

    Этот элемент управления входит в пространство имен SeperateWPFUserControl_Dll и сборку SeperateWPFUserControl_Dll.dll.

-->

<SeperateWPFUserControlDll:UserControl1/>

Как и раньше, вы можете выбрать для пространства имен любое имя, однако желательно присвоить ему имя исходной сборки, в которую входит используемый элемент. Это упростит поиск неполадок, если что-то пойдет не так.

Ссылка на сборку 4: использование внешнего (из другого пространства имен или другой сборки) класса в элементе Window

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

Предположим, что в XAML требуется использовать следующие классы:

  • System.Collections.Hashtable в сборке mscorlib.dll;
  • System.Int32 в сборке mscorlib.dll.

Как и раньше, можно использовать следующее объявление пространства имен xmlns: в открывающем теге элемента Window:

    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

Это позволит использовать эти классы в разметке XAML в виде ресурсов.

<StackPanel x:Name="sp1" Orientation="Vertical">

    <!--Я добавил эту строку только для того, чтобы продемонстрировать возможность добавления классов в разметку XAML. Использованные мною классы

         не могут входить в интерфейс пользователя, поскольку они не являются элементами управления интерфейса (не наследуются от класса Visual).

        Поэтому я поместил их в словарь ресурсов.

         -->

    <StackPanel.Resources>

        <!-- Ссылка на сборку 4. В разметке XAML можно использовать даже стандартные классы из пространства имен System при условии, что

             объявление xmlns: вверху этого файла является корректным -->

        <collections:Hashtable x:Key="ht1">

            <sys:Int32 x:Key="key1">1</sys:Int32>

            <sys:Int32 x:Key="key2">2</sys:Int32>

        </collections:Hashtable>

    </StackPanel.Resources>

</StackPanel>

Это очень похоже на то, что мы видели в примере с локальным классом.

Расширения разметки

Расширения разметки увеличивают возможности выражений XAML. Строка расширения разметки может обрабатываться во время выполнения. На основании этой строки создается соответствующий объект. Расширения разметки реализуются в XAML явно и единообразно.

Если значение атрибута заключено в скобки {}, компилятор-анализатор XAMLобрабатывает его как расширение разметки, а не как строковый литерал.

Например, рассмотрим следующий элемент Rectangle, у которого свойству Background присвоено значение null.

<Rectangle  Fill="{x:Null}" Stroke="Black" StrokeThickness="2" Height="20"/>

Поскольку компилятор-анализатор видит строку, заключенную в скобки {}, он знает, что это расширение разметки. Это всего лишь простенький трюк, хорошо известный анализатору XAML. Расширения разметки в XAML объявляются так же, как и классы. Для этого используется синтаксис элемента свойства.

Синтаксис элемента свойства

Синтаксис элемента свойства позволяет превратить любое свойство в полноценный элемент дерева XAML, в который можно добавлять дочерние элементы. Это станет очевиднее из примера.

Представим, что расширение разметки можно объявлять следующим образом:

<Binding RelativeSource="{RelativeSource modeEnumValue}" .../>

Или, возможно, таким:

<object mode="{Binding RelativeSource={RelativeSource modeEnumValue} ...}" .../>

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

<Binding>

  <Binding.RelativeSource>

    <RelativeSource Mode="modeEnumValue"/>

  </Binding.RelativeSource>

</Binding>

//и более сложный пример

<Binding>

  <Binding.RelativeSource>

    <RelativeSource

      Mode="FindAncestor"

      AncestorType="{x:Type typeName}"

      AncestorLevel="2"

    />

  </Binding.RelativeSource>

</Binding>

Видите, сколько всего можно делать в XAML! Не забывайте, объявлять все эти объекты таким образом можно потому, что они всего лишь классы. Скобки {}, по сути, служат синтаксической оболочкой того, что скрыто за кулисами, то есть за кулисами создается вся масса классов (как только что было продемонстрировано). Такой синтаксис элемента свойства можно применять ко всему. В примере ниже другое свойство используется в качестве элемента для свойства Background типа Button.

<Button Width="auto" Content="Button">

    <Button.Background>

        <SolidColorBrush Color="Blue"/>

    </Button.Background>

</Button>

Поскольку класс System.Windows.Markup.MarkupExtension базовый, от него наследуются все расширения разметки. Вот список стандартных классов расширений разметки:

  • System.Windows.Markup.ArrayExtension
  • System.Windows.Markup.NullExtension
  • System.Windows.Markup.StaticExtension
  • System.Windows.Markup.TypeExtension
  • System.Windows.ResourceKey
  • System.Windows.DynamicResourceExtension
  • System.Windows.ColorConvertedBitmapExtension
  • System.Windows.StaticResourceExtension
  • System.Windows.TemplateBindingExtension
  • System.Windows.ThemeDictionaryExtension
  • System.Windows.Data.BindingBase
  • System.Windows.Data.RelativeSource

Чаще всего при программировании на платформе WPF используются расширения разметки, которые поддерживают ссылки на ресурсы (StaticResourceи DynamicResource) и привязки данных (Binding). Обратите внимание на отсутствие суффикса Extension. Он неявно подразумевается компилятором, поскольку скобки {} указывают на то, что суффикс Extension в расширении разметки НЕ требуется.

Расширение разметки StaticResource предоставляет значение свойства XAML путем замены на значение уже определенного ресурса. Дополнительные сведения см. в разделе «Расширение разметки StaticResource».

Расширение разметки DynamicResource предоставляет значение свойства XAML во время выполнения по ссылке на ресурс. Динамическая ссылка на ресурс приводит к новому поиску значения при каждом обращении к ресурсу. Дополнительные сведения см. в разделе «Расширение разметки DynamicResource».

Расширение разметки Binding предоставляет значение свойства, привязанное к данным, в соответствии с контекстом данных, применяемым для данного элемента. Это расширение разметки относительно сложное, в нем используется встроенный синтаксис для задания привязки данных. Дополнительные сведения см. в разделе «Расширение разметки Binding».

Расширение разметки RelativeSource предоставляет исходную информацию для привязки, которая может использоваться для перехода к нескольким возможным отношениям в дереве элементов во время выполнения. Это расширение предоставляет специальную исходную информацию для привязок, создаваемых в многократно используемых шаблонах и в коде в отсутствие полного знания о дереве элементов. Дополнительные сведения см. в разделе «Расширение разметки RelativeSource».

Существует еще несколько других расширений разметки (см. выше), которые не относятся конкретно к использованию XAML в приложении WPF, а, скорее, являются частью спецификации и пространства имен самого языка XAML. Обычно их можно опознать по префиксу x:. Для их реализации в WPF используется тот же самый базовый класс MarkupExtension.

Расширение разметки x:Type предоставляет объект Typeдля именованного типа. Чаще всего оно используется в стилях и шаблонах. Дополнительные сведения см. в разделе «Расширение разметки x:Type».

Расширение разметки x:Static создает статические значения из сущностей кода типа значения, которые непосредственно не принадлежат к типу значения свойства, но могут быть приведены к нему.

Расширение разметки x:Null присваивает свойству XAMLзначение null.

Расширение разметки x:Array поддерживает создание общих массивов в синтаксисе XAML, когда поддержка коллекций, предоставляемая базовыми элементами и моделями элементов управления, намеренно не используется.

Не волнуйтесь, скоро расширения Binding и RelativeSource станут куда непонятнее. Мы поговорим о них в статье, посвященной привязкам.

ПРИМЕЧАНИЕ. Можно создавать собственные расширения разметки. Вот, например, Томер Шамам (Tomer Shamam) в отличной статье Сохранение состояния элементов управления WPF описывает, как создать расширение разметки, чтобы сохранить состояние элементов WPF в файле.

Я бы также рекомендовал ознакомиться со статьей MSDN Расширения разметки и XAML, где приводятся более подробные сведения о расширениях разметки.

Что такое ресурсы и чем они полезны

Ресурсы — это многократно используемые объекты, которые можно объявлять в различных местах.

  • В приложении в файле App.xaml (или как у вас называется файл приложения). В этом случае у объявленного ресурса будет глобальная область действия приложения.
  • В свойстве Resources текущего объекта Window (при условии работы с объектом Window). В этом случае у объявленного ресурса будет область уровня объекта Window. У всех элементов интерфейса пользователя в объекте Window, где объявлен ресурс, будет доступ к объявленному ресурсу.
  • В свойстве Resources любого элемента FrameworkElement или FrameworkContentElement.
  • В отдельном файле ресурсов XAML.

Ресурс можно представить как один объект, сохраненный в словаре объектов ресурсов. И поэтому у каждого ресурса ДОЛЖЕН быть ключ для безопасного извлечения уникального ресурса из словаря ResourceDictionary.

При определении ресурсов в разметке уникальный ключ присваивается с помощью атрибута x:Key. Как правило, ключ — это строка, но с помощью расширений разметки можно задавать и объекты других типов. Ключи ресурсов, отличные от строк, используются в некоторых функциональных областях WPF, таких как стили, ресурсы компонентов и стили данных. Эти вопросы будут рассмотрены в следующих статьях этой серии.

Каждый элемент уровня платформы (FrameworkElement или FrameworkContentElement) обладает свойством Resources, содержащим ресурсы (в виде словаря ResourceDictionary). Определять ресурсы можно для любого элемента. Однако чаще всего ресурсы определяются для корневого элемента — обычно это элемент Window.

После объявления ресурса его можно использовать в элементе. Например, в следующем коде XAML объявляется новый ресурс SolidColorBrush в элементе Window. Затем этот ресурс используется в качестве фона для двух объектов Button в элементе Window.

<Window x:Class="WPF_Tour_Beginners_Part_2.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:SeperateWPFUserControlDll="clr-namespace:SeperateWPFUserControl_Dll;assembly=SeperateWPFUserControl_Dll"

    xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Title="Window1" Height="300" Width="600"

    WindowStartupLocation="CenterScreen">

    <Window.Resources>

        <SolidColorBrush x:Key="windowLevelResourceBlueBrush" Color="Blue"/>

    </Window.Resources>

    <StackPanel x:Name="sp1" Orientation="Vertical">

        <!--Объявление двух объектов Button, использующих ресурсWindow.windowLevelResourceBlueBrush -->

        <Button Width="auto" Content="1st Button : I use the resourceBrushBlue Window Resource"

        Background="{StaticResource windowLevelResourceBlueBrush}"/>

        <Button Width="auto" Content="2nd Button : I use the resourceBrushBlue Window Resource"

        Background="{StaticResource windowLevelResourceBlueBrush}"/>

    </StackPanel>

</Window>

В этом простом примере использования ресурсов для двух объектов Button в качестве фона будут использоваться синие объекты SolidColorBrush. (Примечание. Мы ничего не сказали о том, как будет выглядеть кнопка при наведении на нее указателя мыши. Синий объект SolidColorBrush применяется в качестве фона, только когда указатель мыши не наведен на кнопку. Этот вопрос мы рассмотрим в статье, посвященной стилям и шаблонам.)

Еще следует отметить, что фактически мы используем расширение разметки StaticResourceExtension (помните, что суффикс Extension можно пропустить). Но что такое расширение StaticResourceExtensionи какие преимущества оно дает? В действительности можно использовать два типа ресурсов.

Обращаться к ресурсу можно как к статическому или динамическому. Для этого используется расширение разметки StaticResourceExtension или DynamicResourceExtension соответственно.

Ниже приведены рекомендации по использованию статических и динамических ресурсов согласно разделу MSDN Ресурсы WPF.

Статические ресурсы

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

  • При отсутствии намерений изменять значение ресурса после первой ссылки на него.
  • Большая часть всех ресурсов в структуре приложения сосредоточена в словарях ресурсов уровня страницы или приложения. Ссылки на статические ресурсы не обрабатываются повторно в зависимости от поведения во время выполнения, например, перезагрузки страницы. Это позволяет получить выигрыш в производительности благодаря отсутствию множества динамических ссылок на ресурсы, если в них нет необходимости.
  • При задании значения свойства, не являющегося объектом DependancyObject или Freezable (это постоянный объект).
  • При создании словаря ресурсов, который будет скомпилирован в библиотеке DLL и упакован в составе приложения либо будет совместно использоваться приложениями.
  • При создании темы для пользовательского элемента управления и определении ресурсов, используемых в темах. В этом случае обычно используется подстановка ссылок не на динамические, а на статические ресурсы, что делает ее прогнозируемой и самодостаточной для темы. При использовании ссылки на динамический ресурс даже ссылка в теме не обрабатывается до момента выполнения. Поэтому при применении темы некоторые локальные элементы могут переопределить ключ, на который тема пытается сослаться, и при подстановке произойдет ошибка локального элемента до самой темы. А это, в конце концов, изменит поведение темы.
  • При использовании ресурсов для задания множества свойств зависимости. Система свойств обеспечивает эффективное кэширование значений, поэтому, если присвоить свойству зависимости значение, которое может быть вычислено во время загрузки, свойству не потребуется проверка пересчета выражений и оно сможет возвращать последнее эффективное значение. Этот метод может неплохо повысить эффективность.

Динамические ресурсы

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

  • Если значение ресурса зависит от условий, не известных до времени выполнения. К таким ресурсам относятся системные ресурсы и ресурсы, которые иным образом задаются пользователем. Например, можно создать значения установщика, которые ссылаются на системные свойства, представляемые объектами SystemColors, SystemFonts или SystemParameters. Эти значения действительно динамические, так как они, в конечном счете, берутся из среды выполнения пользователя и операционной системы. Кроме того, могут использоваться изменяемые темы уровня приложения, при этом изменения должны отслеживаться также при доступе к ресурсу уровня страницы.
  • При создании или ссылке на стили темы для пользовательского элемента управления.
  • Если планируется корректировать содержимое словаря ResourceDictionary во время работы приложения.
  • Если есть сложная структура ресурсов с взаимозависимостями, где могут потребоваться опережающие ссылки. Динамические ресурсы, в отличие от статических, поддерживают опережающие ссылки, поскольку ресурс не требуется оценивать до выполнения, и опережающие ссылки, таким образом, становятся нерелевантными.
  • При ссылке на особенно большой с точки зрения компиляции или рабочего множества ресурс, который может не использоваться непосредственно при загрузке страницы. Ссылки на статические ресурсы всегда загружаются из XAML при загрузке страницы, ссылки на динамические ресурсы не загружаются до фактического использования.
  • При создании стиля, если значения установщика могут браться из других значений, на которые влияют темы или другие параметры пользователя.
  • Если ресурсы применяются к элементам, для которых во время работы приложения может быть изменен порядок наследования в логическом дереве. Изменение родительского элемента может изменить область поиска ресурса, так что если требуется переоценка ресурса для элемента с измененным порядком наследования с учетом новой области, следует использовать ссылку на динамический ресурс.

Варианты использования ресурсов

Итак, это было краткое знакомство с ресурсами (конечно, есть масса других особенностей ресурсов, я не могу рассказать обо всех, так что вам придется почитать что-нибудь по этому поводу).

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

  1. Ресурс уровня приложения (глобальный ресурс).
  2. Ресурс уровня элемента Window.
  3. Ресурс уровня элемента FrameworkElement.
  4. Отдельный свободный ресурс XAML.

Ресурс уровня приложения

Чтобы создать глобальный ресурс уровня приложения, следует просто добавить раздел Resources в файл App.xaml (или как у вас называется файл приложения). Рассмотрим пример.

<Application x:Class="WPF_Tour_Beginners_Part_2.App"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    StartupUri="Window1.xaml">

    <Application.Resources>

        <SolidColorBrush x:Key="appLevelResourceGreenBrush" Color="Green"/>

    </Application.Resources>

</Application>

Это означает, что ресурсом может воспользоваться любой объект в текущем приложении. В прилагаемом демонстрационном приложении я создал один элемент Button в файле Window1.xaml, который использует ресурс уровня приложения.

<!--Объявление объекта Button, который использует ресурс уровня приложения appLevelResourceGreenBrush -->

<Button Width="auto" Content="1st Button : I use the appLevelResourceGreenBrush Application level Resource"

Background="{StaticResource appLevelResourceGreenBrush}"/>

Ресурс уровня элемента Window

<Window x:Class="WPF_Tour_Beginners_Part_2.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:SeperateWPFUserControlDll="clr-namespace:SeperateWPFUserControl_Dll;assembly=SeperateWPFUserControl_Dll"

    xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Title="Window1" Height="300" Width="600"

    WindowStartupLocation="CenterScreen">

    <Window.Resources>

        <SolidColorBrush x:Key="windowLevelResourceBlueBrush" Color="Blue"/>

    </Window.Resources>

    <StackPanel x:Name="sp1" Orientation="Vertical">

        <!--Объявление двух объектов Button, использующих ресурс Window.windowLevelResourceBlueBrush -->

        <Button Width="auto" Content="1st Button : I use the resourceBrushBlue Window Resource"

        Background="{StaticResource windowLevelResourceBlueBrush}"/>

        <Button Width="auto" Content="2nd Button : I use the resourceBrushBlue Window Resource"

        Background="{StaticResource windowLevelResourceBlueBrush}"/>

    </StackPanel>

</Window>

Я просто использую ресурс windowLevelResourceBlueBrush в двух объектах Button.

Ресурс уровня элемента платформы

Как говорилось ранее, каждый такой элемент (FrameworkElement или FrameworkContentElement) обладает свойством Resources, что позволяет создать локальный (то есть доступный только текущему элементу и его дочерним элементам) ресурс.

<Window x:Class="WPF_Tour_Beginners_Part_2.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:SeperateWPFUserControlDll="clr-namespace:SeperateWPFUserControl_Dll;assembly=SeperateWPFUserControl_Dll"

    xmlns:local="clr-namespace:WPF_Tour_Beginners_Part_2;assembly="

    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Title="Window1" Height="300" Width="600"

    WindowStartupLocation="CenterScreen">

    .....

    <StackPanel x:Name="sp1" Orientation="Vertical">

        <!--Я добавил эту строку только для того, чтобы продемонстрировать возможность добавления классов в разметку XAML. Использованные мною классы

             не могут входить в интерфейс пользователя, поскольку они не являются элементами управления интерфейса (не наследуются от класса Visual).

            Поэтому я поместил их в словарь ресурсов.

             -->

        <StackPanel.Resources>

            <SolidColorBrush x:Key="parentLevelResourceOrangeBrush" Color="Orange"/>

        </StackPanel.Resources>

          ......

          ......

        <!--Объявление объекта Button, который использует ресурсparentLevelResourceOrangeBrush родительского элемента (StackPanel) -->

        <Button Width="auto" Content="Button : I use the parentLevelResourceOrangeBrush parent (the StackPanel) level Resource"

          Background="{StaticResource parentLevelResourceOrangeBrush}"/>

    </StackPanel>

</Window>

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

Отдельные свободные ресурсы XAML

Ресурсы Windows Presentation Foundation (WPF) поддерживают функцию объединенного словаря ресурсов. Эта функция позволяет определять ресурсы приложения WPF вне скомпилированного приложения XAML. Затем такие ресурсы можно использовать в различных приложениях. Это также обеспечивает более удобную изоляцию ресурсов для локализации.

Обратите внимание, что элемент Resource Dictionary не имеет атрибута x:Key, который обычно требуется для всех элементов в коллекции ресурсов. При этом другая ссылка ResourceDictionary в коллекции Merged Dictionaries является особым случаем, зарезервированным для этого сценария объединенного словаря ресурсов. Словарь ResourceDictionary, представляющий объединенный словарь ресурсов, не может обладать атрибутом x:Key. Как правило, каждый объект Resource Dictionary в коллекции Merged Dictionaries задает атрибут Source. Значение атрибута Source должно быть в виде кода URI, который разрешает путь к объединяемому файлу ресурсов. Местом назначения этого кода URI должен быть другой файл XAML с объектом ResourceDictionary в качестве корневого элемента. Рассмотрим пример.

Начнем с того, как определять свободный файл XAML.

<ResourceDictionary xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">

    <SolidColorBrush x:Key="seperateResourceFilePinkBrush" Color="Pink"/>

</ResourceDictionary>

Все. Осталось только присвоить свойству Build Action значение Resource или Page в Visual Studio.

Теперь можно использовать этот файл. Допустим, нужно использовать этот свободный файл XAML в свойстве Resources элемента Button. Для этого можно написать следующий код:

<!--Объявление объекта Button, который использует свободный ресурсseperateResourceFilePinkBrush уровня XAML -->

<Button Width="auto" Content="Button : Uses a seperate loose XAML level Resource, namely the seperateResourceFilePinkBrush resource">

    <Button.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="SeperateResourceDictionary1.xaml"/>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Button.Resources>

    <Button.Background>

        <StaticResourceExtension ResourceKey="seperateResourceFilePinkBrush"/>

    </Button.Background>

</Button>

Поскольку в этом примере элемент Button объявляет свойство MergedDictionaries, можно использовать ВСЕ ресурсы, объявленные в свободном файле XAML, как если бы эти ресурсы были объявлены локально (локально для элемента Button).

На этом закончим

Как я уже говорил, это еще не самая сложная статья. Не сомневайтесь: дальше будет потруднее.

С уважением,

Саша Барбер.