#013 Стилизация контролов WPF / Работа с ресурсами
Сегодня мы рассмотрим применение стилей WPF с новой стороны. Мы изменим шаблоны контролов Avalon, чтобы придать нашему приложению индивидуальный вид. Также мы изучим словари ресурсов, которые позволяют оптимизировать исходный код программы.
Мы продолжим работу с проектом, который мы создали в предыдущей статье (№ 012). Откройте проект "MyWPFcontrols" и перейдите в режим редактирования XAML-кода главного окна.
В секции <Window.Resources> расположены описания двух стилей для контейнера типа HeaderedItemsControl. Уже сейчас код нашего проекта достаточно громоздкий, в процессе работы над ним он будет только увеличиваться и, как следствие, становиться неудобным для проверки, поиска ошибок и контроля. Давайте начнем оптимизацию кода. Для этого мы применим словарь ресурсов.
Добавьте с помощью меню Project -> Add New Item новый файл типа WinFX ResourceDictionary и задайте ему имя Shared.xaml :
Теперь перенесите содержимое секции <Window.Resources> в файл Shared.xaml.
После этого секция ресурсов вашего окна останется пустой:
<Window.Resources >
</Window.Resources>
А содержимое файла Shared.xaml примет такой вид (схематично):
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Style x:Key="VerticalHIC" TargetType="{x:Type HeaderedItemsControl}">
Содержание стиля
</Style>
<Style x:Key="HorizontalHIC" TargetType="{x:Type HeaderedItemsControl}">
Содержание стиля
</Style>
</ResourceDictionary>
ictionary>[/code]
Если сейчас откомпилировать и запустить проект - будет получено сообщение об ошибке. Это связано с тем, что файл с ресурсами Shared.xaml не подключен к коду нашего окна. Вернитесь к редактированию XAML-кода окна и добавьте такой код в секцию ресурсов:
[code]<Window.Resources >
<ResourceDictionary >
<ResourceDictionary.MergedDictionaries >
<ResourceDictionary Source ="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>[/code]
Обратите внимание на то, как задается ссылка на словарь ресурсов. Запустите проект и убедитесь, что приложение работает именно так, как работало до внесения изменений в код.
Теперь перейдем к изменению внешнего вида контролов, размещенных на форме. Мы будем изменять шаблон каждого контрола, чтобы внешне его преобразить. И вполне логично, что для того, чтобы выполнить все контролы в одном стиле, мы будем использовать одни и те же заливки и кисти. Чтобы не описывать эти заливки и кисти каждый раз для каждого конкретного контрола, а также, чтобы иметь возможность изменить в последствии внешний вид всех контролов сразу, не выполняя одну и туже работу с каждым из них, мы заранее опишем эти заливки и кисти .
Добавьте код для кистей заливки в тело файла Shared.xaml:
[code]<!-- Кисти заливки -->
<LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#CCC" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="HorizontalNormalBrush" StartPoint="0,0"
EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#CCC" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#EEE" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="HorizontalLightBrush" StartPoint="0,0"
EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#EEE" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DarkBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#AAA" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#BBB" Offset="0.0"/>
<GradientStop Color="#EEE" Offset="0.1"/>
<GradientStop Color="#EEE" Offset="0.9"/>
<GradientStop Color="#FFF" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
<SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />[/code]
Добавьте код для контурных кистей в тело файла Shared.xaml:
[code]<!-- Контурные кисти -->
<LinearGradientBrush x:Key="NormalBorderBrush" StartPoint="0,0"
EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#CCC" Offset="0.0"/>
<GradientStop Color="#444" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="HorizontalNormalBorderBrush" StartPoint="0,0"
EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#CCC" Offset="0.0"/>
<GradientStop Color="#444" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultedBorderBrush" StartPoint="0,0"
EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#777" Offset="0.0"/>
<GradientStop Color="#000" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="PressedBorderBrush" StartPoint="0,0"
EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#444" Offset="0.0"/>
<GradientStop Color="#888" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<SolidColorBrush x:Key="DisabledBorderBrush" Color="#AAA" />
<SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />
<SolidColorBrush x:Key="LightBorderBrush" Color="#AAA" />[/code]
Добавьте код для двух дополнительных кистей в тело файла Shared.xaml:
[code]<!-- Дополнительные кисти -->
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />
<SolidColorBrush x:Key="LightColorBrush" Color="#DDD" />[/code]
Этот код объемен, но для вас не составит труда скопировать его в нужный файл. О кистях мы уже неоднократно говорили, поэтому я не буду их комментировать. Скажу лишь, что все подготовленные кисти нам понадобятся для преображения наших контролов.
Надеюсь вы уже поняли смысл словаря ресурсов. Представьте, как тяжело было бы работать если бы весь код нашего проекта хранился в одном файле! Сейчас же у нас есть файл окна - в нем расположена только информация о контролах и их расположении, и файл Shared.xaml в котором хранятся все дополнительные ресурсы. В дальнейшей работе с проектом мы будем поступать именно так. По аналогии с файлом shared.xaml добавьте еще 15 файлов типа "словарь ресурсов" со следующими именами:
Button.xaml
CheckBox.xaml
ComboBox.xaml
Expander.xaml
GroupBox.xaml
ListBox.xaml
Menu.xaml
ProgressBar.xaml
RadioButton.xaml
ScrollBar.xaml
Slider.xaml
TabControl.xaml
TextBox.xaml
ToolTip.xaml
TreeView.xaml
Как вы уже догадались - исходя из имен файлов, в каждом из них будет храниться описание соответствующего контрола. Добавьте в каждый из этих файлов ссылку на Shared.xaml так как кисти которые будут использоваться для всех контролов определены в нем. Это можно сделать так:
[code]<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>[/code]
Не забудьте что этот небольшой блок нужно добавить во все созданные файлы!
После этого добавьте ссылки на созданные файлы в секцию ресурсов окна, так же как вы это делали с файлом Shared.xaml:
[code]<Window.Resources >
<ResourceDictionary >
<ResourceDictionary.MergedDictionaries >
<ResourceDictionary Source ="Shared.xaml" />
<ResourceDictionary Source ="Button.xaml" />
<ResourceDictionary Source ="CheckBox.xaml" />
и так далее для всех созданных файлов
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>[/code]
Итак, первый контрол на нашей форме - это кнопка (Button). Она будет первым элементом, для которого мы изменим внешний вид.
Измените код файла Button.xaml в соответствии с этим листингом:
[code]<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Focus Visual -->
<Style x:Key="ButtonFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle
Margin="2"
StrokeThickness="1"
Stroke="#60000000"
StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Стиль для контрола: Button -->
<Style TargetType="{x:Type Button}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="FocusVisualStyle"
Value="{StaticResource ButtonFocusVisual}"/>
<Setter Property="MinHeight" Value="23"/>
<Setter Property="MinWidth" Value="75"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border
x:Name="Border"
CornerRadius="2"
BorderThickness="1"
Background="{StaticResource NormalBrush}"
BorderBrush="{StaticResource NormalBorderBrush}">
<ContentPresenter
Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter TargetName="Border" Property="BorderBrush"
Value="{StaticResource DefaultedBorderBrush}" />
</Trigger>
<Trigger Property="IsDefaulted" Value="true">
<Setter TargetName="Border" Property="BorderBrush"
Value="{StaticResource DefaultedBorderBrush}" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource DarkBrush}" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource PressedBrush}" />
<Setter TargetName="Border" Property="BorderBrush"
Value="{StaticResource PressedBorderBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource DisabledBackgroundBrush}" />
<Setter TargetName="Border" Property="BorderBrush"
Value="{StaticResource DisabledBorderBrush}" />
<Setter Property="Foreground"
Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>[/code]
Давайте разберемся с этим кодом. Прежде всего обратите внимание на второй стиль (тот, который указан с комментарием "Стиль для контрола: Button"
Этот стиль отличается от тех, что мы создавали ранее. Прежде всего, у данного стиля не задан параметр x:Key - это значит, что данный стиль будет применен автоматически для всех элементов типа Button - на это указывает свойство x:Type="Button" . Во вторых, он не только изменяет внешний вид контрола, устанавливая значения для простых свойств типа ширины и высоты, но и изменяет шаблон контрола, путем редактирования свойства "Template".
Редактируя свойство Template мы можем вручную определить, как будет выглядеть контрол, в данном случае мы используем рамку Border , чтобы нарисовать кнопку. Но как же быть со стандартным содержанием контрола? Для кнопки таковым является текст… Для этого группа разработки WPF подготовила для нас особый элемент - ContentPresenter который автоматически разместит нужное содержание для любого контрола. Поэтому мы помещаем и его в наш шаблон, иначе наша кнопка хоть и будет красивой, но будет совершенно неинформативной!
Далее остается самое простое. Редактируя свойство Triggers нашего шаблона (это свойство должно быть вам уже знакомо - мы неоднократно говорили о нем, изучая анимацию) , мы задаем различные параметры отображения кнопки в зависимости от ее состояния - если на нее наведен фокус, если мы ее нажали, если провели над ней мышью - для каждого состояния мы можем определить индивидуальное поведение.
Также мы создали вспомагательный стиль ButtonFocusVisual - он носит второстепенное значение и очень прост, думаю вы без труда с ним разберетесь.
Давайте запустим проект и посмотрим, что получилось:
Посмотрите как здорово выглядят наши кнопки! Такого можно добиться только изменив шаблон контрола. А замечательный элемент ContentPresenter сохранил нашей кнопке функциональность!
Итак, у нас впереди еще масса работы - ведь целью сегодняшней статьи было изменение внешнего вида всех контролов нашей программы.
Коды для каждого контрола будут очень объемны и поэтому, чтобы не превращать статью в эпопею и не вызывать инфаркт у редактора, я выложил для вас архив,
После того, как вы внедрите скачанный код в свое приложение, вы увидите такой результат:
Впечатляет, не правда ли? Мы смогли изменить абсолютно все контролы Avalon и придать нашему приложению индивидуальный вид!
Июль, 2006
По теме
- Создаем контекстно-зависимое WPF-приложение
- #024 – Знакомство с WPF/E
- #023 – Введение в WPF, reloaded…
- #022 Введение в Microsoft Interactive Designer RC1
- #021 Применение 3D в WPF - Часть 2
- #020 Применение 3D в WPF - Часть 1
- #019 Введение в возможности 3D на WPF
- #018 Размещение контрола NET 2.0 на форме WPF
- #017 Первое Web-приложение / Подробнее о Grid / Элемент Frame
- #016 EXE, XBAP, XAML? - Все равно!