Стили

47

Система ресурсов WPF позволяет определять объекты в одном месте и затем повторно использовать их в других частях разметки. Хотя ресурсы можно применять для хранения самых различных объектов, чаще всего в них хранятся стили.

Стилем называется коллекция значений свойств, которые могут применяться к элементу. Система стилей WPF играет ту же роль, которую играет стандарт каскадных таблиц стилей (Cascading Style Sheet — CSS) в HTML-разметке. Подобно CSS, стили WPF позволяют определять общий набор характеристик форматирования и применять его повсюду в приложении для обеспечения согласованного вида. Как и CSS, они могут работать автоматически, предназначаться для элементов конкретного типа и каскадироваться через дерево элементов.

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

Чтобы понять, каким образом стили вписываются в приложения, следует рассмотреть простой пример. Предположим, что требуется стандартизировать используемый в окне шрифт. Простейший подход предусматривает установку свойств шрифта содержащего окна. Эти свойства, определенные в классе Control, включают FontFamily, FontSize, FontWeight (для полужирного начертания), FontStyle (для курсивного начертания) и FontStretch (для сжатого и разрешенного вариантов). Благодаря функции наследования значений свойств, при установке этих свойств на уровне окна все элементы внутри окна получат одинаковые значения, если только те не будут переопределены в них явным образом.

Функция наследования значений свойств является лишь одной из многих дополнительных средств, которые могут предоставлять свойства зависимости.

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

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

Ресурсы предлагают возможное, но несколько громоздкое решение. Поскольку объекта, представляющего шрифт, в WPF не существует (есть только коллекция связанных со шрифтом свойств), соответствующие ресурсы можно определять только так, как показано ниже:

<Window.Resources>
   <FontFamily х:Key="ButtonFontFamily">Times New Roman</FontFamily>
   <sys:Double x:Key="ButtonFontSize">18</sys:Double>
   <FontWeight x:Key="ButtonFontWeight">Bold</FontWeight>
</Window.Resources>

В этом фрагменте кода разметки в окно добавляются три ресурса: объект FontFamily с именем шрифта, который должен использоваться, объект Double с числом 18 и перечислимое значение FontWeight.Bold. Здесь предполагается, что .NET-пространство имен System было отображено на префикс sys пространства имен XML.

При установке свойств с использованием ресурса важно, чтобы типы данных в точности совпадали. Конвертеры типов, которые применяются при установке значения атрибута напрямую, здесь отсутствуют. Например, при установке атрибута FontFamily в элементе можно использовать строку "Times New Roman", потому что FontFamilyConverter автоматически создаст необходимый объект FontFamily. Однако попытка установить свойство FontFamily с использованием строкового ресурса ничего подобного не произойдет, и анализатор XAML сгенерирует исключение.

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

Этот пример работает и позволяет устранить из разметки детали, касающиеся шрифта. Однако он также приводит к появлению двух новых проблем:

Справиться с первой проблемой можно, определив специальный класс (вроде FontSettings), который объединяет все касающиеся шрифта детали вместе. Затем можно создать один объект FontSettings как ресурс и использовать его различные свойства в разметке. Тем не менее, это не устранит вторую проблему, к тому же потребует довольно много дополнительных усилий. Стили предлагают практически идеальное решение. Как показано ниже, можно определить единственный стиль, который упаковывает все свойства, подлежащие установке:

<Window.Resources>
        <Style x:Key="MyButtonStyle">
            <Setter Property="Control.FontFamily" Value="Calibri"></Setter>
            <Setter Property="Control.FontSize" Value="18"></Setter>
            <Setter Property="Control.FontWeight" Value="Bold"></Setter>
            <Setter Property="Control.Padding" Value="5"></Setter>
            <Setter Property="Control.Margin" Value="5"></Setter>
        </Style>
</Window.Resources>

В этом коде разметки создается один ресурс — объект System.Windows.Style. В этом объекте размещается коллекция Setters с пятью объектами Setter, по одному для каждого свойства, которое подлежит установке. В каждом объекте Setter указывается имя свойства, на которое он влияет, и значение, которое он должен применять к этому свойству. Как и все ресурсы, объект стиля имеет ключевое имя, по которому его можно при необходимости извлекать из коллекции. В данном случае это ключевое имя выглядит как MyButtonStyle. (По общепринятому соглашению ключевые имена стилей обычно заканчиваются словом "Style".)

Каждый элемент WPF может использовать одиночный стиль (или не использовать его). Стиль подключается к элементу через свойство Style (которое определено в базовом классе FrameworkElement). Например, чтобы сконфигурировать кнопку для использования созданного выше стиля, необходимо указать кнопке ресурс стиля:

<Button Style="{StaticResource MyButtonStyle}">Видоизмененная кнопка</Button>

Разумеется, стиль можно устанавливать и программно. Все, что для этого понадобится — это просто извлечь стиль из ближайшей коллекции Resources с помощью уже знакомого метода FindResource(). Ниже приведен код, который можно применить для объекта Button по имени cmd:

cmd.Style = (Style)cmd.FindResource("MyButtonStyle");

На рисунке показано окно с двумя кнопками, которые используют специальный стиль MyButtonStyle:

Использование стилей для кнопок

Стили задают первоначальный внешний вид элемента, но устанавливаемые ими характеристики могут быть переопределены. Например, если применить стиль MyButtonStyle и явно установить свойство FontSize, параметр FontSize в дескрипторе кнопки переопределит этот стиль. В идеале полагаться на такое поведение не стоит — вместо этого лучше создать больше стилей, которые позволяют устанавливать максимальное количество деталей на уровне стиля. В результате обеспечивается большая гибкость при настройке пользовательского интерфейса в будущем.

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