Предыдущая лекция | | Следующая лекция
:----------------:|:----------:|:----------------:
[Вывод данных согласно макета (ListBox, Image)](./articles/wpf_listbox.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Создание окон. Модальные окна](./wpf_window.md)
>Два инициативных студента по итогам прошлой лекции решили сделать переключение вида отображения "список" и "плитка" кнопкой. Нарыли интересную тему про стили...
# [Стили и темы](https://metanit.com/sharp/wpf/10.php)
## Стили
Стили позволяют определить набор некоторых свойств и их значений, которые потом могут применяться к элементам в `xaml`. Стили хранятся в ресурсах и отделяют значения свойств элементов от пользовательского интерфейса. Также стили могут задавать некоторые аспекты поведения элементов с помощью триггеров. Аналогом стилей могут служить каскадные таблицы стилей (CSS), которые применяются в коде html на веб-страницах.
Зачем нужны стили? Стили помогают создать стилевое единообразие для определенных элементов. Допустим, у нас есть следующий код xaml:
```xml
```
Здесь обе кнопки применяют ряд свойств с одними и теми же значениями. Однако в данном случае мы вынуждены повторяться. Частично, проблему могло бы решить использование ресурсов:
```xml
Verdana
```
Однако в реальности код раздувается, опть же приходится писать много повторяющейся информации. И в этом плане стили предлагают более элегантное решение:
```xml
```
Результат будет тот же, однако теперь мы избегаем ненужного повторения. Более того теперь мы можем управлять всеми нужными нам свойствами как единым целым - одним стилем.
Стиль создается как ресурс с помощью объекта **Style**, который представляет класс **System.Windows.Style**. И как любой другой ресурс, он обязательно должен иметь ключ. С помощью коллекции **Setters** определяется группа свойств, входящих в стиль. В нее входят объекты **Setter**, которые имеют следующие свойства:
* **Property**: указывает на свойство, к которому будет применяться данный сеттер. Имеет следующий синтаксис: `Property="Тип_элемента.Свойство_элемента"`. Выше в качестве типа элемента использовался **Control**, как общий для всех элементов. Поэтому данный стиль мы могли бы применить и к **Button**, и к **TextBlock**, и к другим элементам. Однако мы можем и конкретизировать элемент, например, **Button**:
```xml
```
* **Value**: устанавливает значение
Если значение свойства представляет сложный объект, то мы можем его вынести в отдельный элемент:
```xml
```
### TargetType
Hам необязательно прописывать для всех кнопок стиль. Мы можем в самом определении стиля с помощью свойства _TargetType_ задать тип элементов. В этом случае стиль будет автоматически применяться ко всем кнопкам в окне:
```xml
```
Причем в этом случае нам уже не надо указывать у стиля ключ `x:Key` несмотря на то, что это ресурс.
Также если используем свойство _TargetType_, то в значении атрибута _Property_ уже необязательно указывать тип, то есть `Property="Control.FontFamily"`. И в данном случае тип можно просто опустить: `Property="FontFamily"`
Если же необходимо, чтобы к какой-то кнопке не применялся автоматический стиль, то ее стилю присваивают значение null
```xml
```
### Определение обработчиков событий с помощью стилей
Кроме коллекции **Setters** стиль может определить другую коллекцию - **EventSetters**, которая содержит объекты **EventSetter**. Эти объекты позволяют связать события элементов с обработчиками. Например, подключим все кнопки к одному обработчику события **Click**:
```xml
```
Соответственно в файле кода `c#` у нас должен быть определен обработчик **Button_Click**:
```cs
private void Button_Click(object sender, RoutedEventArgs e)
{
Button clickedButton = (Button)sender;
MessageBox.Show(clickedButton.Content.ToString());
}
```
### Наследование стилей и свойство _BasedOn_
У класса **Style** еще есть свойство _BasedOn_, с помощью которого можно наследовать и расширять существующие стили:
```xml
```
Cвойство _BasedOn_ в качестве значения принимает существующий стиль, определяя его как статический ресурс. В итоге он объединяет весь функционал родительского стиля со своим собственным.
Если в дочернем стиле есть сеттеры для свойств, которые также используются в родительском стиле, как в данном случае сеттер для свойства _Button.FontFamily_, то дочерний стиль переопределяет родительский стиль.
### Стили в C#
В `C#` стили представляют объект **System.Windows.Style**. Используя его, мы можем добавлять сеттеры и устанавливать стиль для нужных элементов:
```cs
public MainWindow()
{
InitializeComponent();
Style buttonStyle = new Style();
buttonStyle.Setters.Add(
new Setter {
Property = Control.FontFamilyProperty,
Value = new FontFamily("Verdana") });
buttonStyle.Setters.Add(
new Setter {
Property = Control.MarginProperty,
Value = new Thickness(10) });
buttonStyle.Setters.Add(
new Setter {
Property = Control.BackgroundProperty,
Value = new SolidColorBrush(Colors.Black) });
buttonStyle.Setters.Add(
new Setter {
Property = Control.ForegroundProperty,
Value = new SolidColorBrush(Colors.White) });
buttonStyle.Setters.Add(
new EventSetter {
Event= Button.ClickEvent,
Handler= new RoutedEventHandler( Button_Click) });
button1.Style = buttonStyle;
button2.Style = buttonStyle;
}
```
При создании сеттера нам надо использовать свойство зависимостей, например, `Property = Control.FontFamilyProperty`. Причем для свойства _Value_ у сеттера должен быть установлен объект именно того типа, которое хранится в этом свойстве зависимости. Так, свойство зависимости _MarginProperty_ хранит объект типа **Thickness**, поэтому определение сеттера выглядит следующим образом:
```cs
new Setter {
Property = Control.MarginProperty,
Value = new Thickness(10) }
```
## Темы
Стили позволяют задать стилевые особенности для определенного элемента или элементов одного типа. Но иногда возникает необходимость применить ко всем элементам какое-то общее стилевое единообразие. И в этом случае мы можем объединять стили элементов в темы. Например, все элементы могут выполнены в светлом стиле, или, наоборот, к ним может применяться так называемая "ночная тема". Более того может возникнуть необходимость не просто определить общую тему для всех элементов, но и иметь возможность динамически выбирать понравившуюся тему из списка тем. И в данной статье рассмотрим, как это сделать.
Пусть у нас есть окно приложения с некоторым набором элементов:
```xml
```
Для примера здесь определены кнопка, текстовый блок и выпадающий список, в котором позже будут выбираться темы.
К элементам окна уже применяются некоторые стили. Причем следует отметить, что стили указывают на динамические (не статические) ресурсы. Однако сами эти ресурсы еще не заданы. Поэтому зададим их.
Для этого добавим в проект новый файл словаря ресурсов, который назовем `light.xaml`, и определим в нем некоторый набор ресурсов:
```xml
```
Здесь указаны все те стили, которые применяются элементами окна.
Но теперь также добавим еще один словарь ресурсов, который назовем `dark.xaml` и в котором определим следующий набор ресурсов:
```xml
```
Здесь определены те же самые стили, только их значения уже отличаются. То есть фактически мы создали две темы: для светлого и темного стилей.
Теперь применим эти стили. Для этого изменим файл `MainWindow.xaml.cs` следующим образом:
```cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List styles = new List { "light", "dark" };
styleBox.SelectionChanged += ThemeChange;
styleBox.ItemsSource = styles;
styleBox.SelectedItem = "dark";
}
private void ThemeChange(object sender, SelectionChangedEventArgs e)
{
string style = styleBox.SelectedItem as string;
// определяем путь к файлу ресурсов
var uri = new Uri(style + ".xaml", UriKind.Relative);
// загружаем словарь ресурсов
ResourceDictionary resourceDict =
Application.LoadComponent(uri) as ResourceDictionary;
// очищаем коллекцию ресурсов приложения
Application.Current.Resources.Clear();
// добавляем загруженный словарь ресурсов
Application.Current.Resources.MergedDictionaries.Add(resourceDict);
}
}
```
К элементу **ComboBox** цепляется обработчик _ThemeChange_, который срабатывает при выборе элемента в списке.
В методе **ThemeChange** получаем выделенный элемент, который представляет название темы. По нему загружаем локальный словарь ресурсов и добавляем этот словарь в коллекцию ресурсов приложения.
В итоге при выборе элемента в списке будет меняться применяемая к приложению тема.
## Переключение вида списка
1. Переносим настройки **ListBox** в ресурсы окна (в том числе и шаблон элемента списка):
```xml
```
2. **ListBox**-у задаем стиль по-умолчанию
```xml
` и ``, а для `` возвращаем (хотя он нужен только для стека и его тоже можно перенести в соответствующий стиль):
```xml
```
3. В верхнюю панель (где у нас элементы управления для фильтраци, поиска и т.п.) добавляем кнопку "Сменить стиль" и в её обработчике переопределяем стиль для **ListBox**-а
```cs
// храним текущий стиль
private string currentStyle = "StackStyle";
private void ToggleView_Click(object sender, RoutedEventArgs e)
{
currentStyle = currentStyle == "StackStyle" ? "WrapStyle" : "StackStyle";
var newStyle = (Style)TryFindResource(currentStyle)
if (newStyle != null)
catListBox.Style = newStyle;
}
```
---
## Задание на дом
Реализовать все примеры из лекции. В репозиторий добавить скриншоты результатов работы.
Предыдущая лекция | | Следующая лекция
:----------------:|:----------:|:----------------:
[Вывод данных согласно макета (ListBox, Image)](./articles/wpf_listbox.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Создание окон. Модальные окна](./wpf_window.md)