четверг, 30 июня 2011 г.

Создание простого триггера в WPF


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

Property Trigger

Первй из них это Property Trigger. Триггеры свойств активизируются, когда изменяется значение свойства.
WPF определяет свойства, которые соответствуют действиям пользователя, например, свойство IsMouseOver, которое получает значение true, когда пользователь наводит курсор на UIElement, или соответствующее свойство IsMouseOver объекта ContentElement. Представление действий пользователя в значениях свойств, а также использование элемента Trigger позволяет стилям WPF изменять значения свойств, основанные на действиях пользователя (все из кода).
Свойства, измененные триггерами, автоматически сбрасываются до предыдущего значения, когда условие триггера больше не удовлетворяется. Триггеры оптимизированы для промежуточных состояний, которые должны изменяться и возвращаться к исходному состоянию, например, состояние IsPressed для кнопки Button и состояние IsSelected для элемента ListBoxItem. Данное свойство Property является свойством зависимостей.

Обратите внимание, что необходимо указать свойства Property и Value для объекта Trigger, чтобы триггер работал правильно. Если одно из свойств или оба свойства не заданы, генерируется исключение.

Свойство Setters объекта Trigger может состоять только из объектов Setter. При добавлении дочернего объекта Setter в объект Trigger он неявно добавляется в коллекцию SetterBaseCollection объекта Trigger. Объекты EventSetter не поддерживаются; только Style.Setters поддерживает объекты EventSetter.

В следующем примере демонстрируется именованный стиль Style, доступный для элементов управления Button. Стиль Style определяет два элемента Trigger, первый из которых изменяет свойство Foreground и FontSize кнопки, когда свойство IsMouseOver равно true., а второй делает то е самое только по событию IsPressed равно true.

<Window x:Class="TriggerDemo.PropertyTriggerWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="PropertyTriggerWindow" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Background" Value="LightBlue" />
            <Setter Property="FontSize" Value="17" />
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Foreground" Value="Red" />
                    <Setter Property="FontSize" Value="22" />
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter Property="Foreground" Value="Yellow" />
                    <Setter Property="FontSize" Value="22" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <Button Width="200" Height="30" Content="Click me!" />
    </Grid>
</Window>



Multi Trigger

Если необходимо установить триггер, когда одно или более свойств имеют определённое значене, можно воспользоваться классом MultiTrigger.

MultiTrigger позволяет задать значения свойств или запускать действия на основе коллекции условий Condition. Условие выполняется, если значение свойства (указанное свойством Property класса Condition) элемента соответствует указанному Value. Данное сравнение является проверкой равенства ссылок. Можно использовать переключатели или свойства EnterActions и ExitActions, чтобы применить изменения или запустить действия, когда соблюдаются все условия.

Свойство Setters объекта MultiTrigger может состоять только из объектов Setter. Добавление дочернего Setter к объекту MultiTrigger неявно добавляет его к SetterBaseCollection для объекта MultiTrigger. Объекты EventSetter не поддерживаются; только свойство Style.Setters поддерживает объекты EventSetter.

В следующем примере определяется стиль для элементов TextBox. Триггер смотрит,  если  "IsEnabled"="True и "Text ="Test, то то свойство Foreground устанавливается в Red.

<Window x:Class="TriggerDemo.MultiTriggerWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MultiTriggerWindow" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsEnabled" Value="True" />
                        <Condition Property="Text" Value="Test" />
                    </MultiTrigger.Conditions>
                    <MultiTrigger.Setters>
                        <Setter Property="Foreground" Value="Red" />
                    </MultiTrigger.Setters>
                </MultiTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <TextBox />
    </Grid>
</Window>



Data Trigger

Представляет триггер, применяющий значения свойства или выполняющий действия при соответствии связанных данных указанному условию. DataTrigger позволяет устанавливать значения свойств, если значение свойства объекта данных соответствует заданному Value. Например, при отображении перечня объектов Employee может потребоваться изменить основной цвет в соответствии с текущим присутствием каждого Employee's. Например, Employees, находящиеся в данный момент в отпуске, отображаются фиолетовым цветом. В некоторых сценариях более предпочтительным может быть создание преобразователя или использование DataTemplateSelector. Дополнительные сведения см. в разделе Общие сведения о шаблонах данных.

Обратите внимание на необходимость указания свойств Binding и Value на DataTrigger для обеспечения возможности использования триггера данных. Если одно из свойств или оба свойства не указаны, генерируется исключение.
Свойство Setters объекта DataTrigger может состоять только из объектов Setter. Добавление дочернего Setter к объекту DataTrigger явно добавляет его к SetterBaseCollection для объекта DataTrigger. Объекты EventSetter не поддерживаются; только Style.Setters поддерживает объекты EventSetter.

В следующем примере ItemsSource, принадлежащий ListBox привязан к коллекции объектов класса Book. Объекты Book имеют свойства Title и Publisher.
Каждый элемент ListBoxItem списка ListBox отображает объект Book. Style в данном примере применяется к каждому элементу ListBoxItem.

DataTrigger указан таким образом, что если в качестве Publisher значение " Wrox Press ", то основным цветом соответствующего ListBoxItem является красный, а если Publisher="Dummies" или “Sybex”, то бэкграунд устанавливается в жёлтый и голубой соответственно.

<Window x:Class="TriggerDemo.DataTriggerWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Data Trigger Window" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="ListBoxItem">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=Publisher}" Value="Wrox Press">
                    <Setter Property="Background" Value="Red" />
                </DataTrigger>
                <DataTrigger Binding="{Binding Path=Publisher}" Value="Dummies">
                    <Setter Property="Background" Value="Yellow" />
                </DataTrigger>
                <DataTrigger Binding="{Binding Path=Publisher}" Value="Sybex">
                    <Setter Property="Background" Value="LightBlue" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <ListBox x:Name="list1" />
    </Grid>
</Window>



Вот класс Book, объекты которого мы и отображаем в ListBox.

    public class Book
    {
        public string Title { get; set; }
        public string Publisher { get; set; }

        public override string ToString()
        {
            return Title;
        }
    }

И напоследок опишу код главного окна который запускает все эти демонстрационные приложения.

<Window x:Class="TriggerDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="240" Width="500">
    <StackPanel>
        <Button Content="Property Trigger" Click="OnPropertyTrigger" Margin="5" />
        <Button Content="Multi Trigger" Click="OnMultiTrigger" Margin="5" />
        <Button Content="Data Trigger" Click="OnDataTrigger" Margin="5" />
    </StackPanel>

</Window>



И приведу cs код, в котором описаны обработчики кнопок главного окна.

   public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnPropertyTrigger(object sender, RoutedEventArgs e)
        {
            new PropertyTriggerWindow().Show();
        }

        private void OnMultiTrigger(object sender, RoutedEventArgs e)
        {
            new MultiTriggerWindow().Show();
        }

        private void OnDataTrigger(object sender, RoutedEventArgs e)
        {
            new DataTriggerWindow().Show();
        }
    }

 Вот что должно было примерно получится. Надеюсь статья была интересна для вас.

Стили и ресурсы в WPF


Стили – это одна из наиболее мощных и полезных особенностей WPF и XAML. Стили позволяют практически до неузнаваемости изменять внешний вид (и поведение) элементов управления и форм.
Предположим, что мы хотим иметь возможность менять внешний вид интерфейса на лету. В таком случае мы можем просто создать несколько различных стилей и менять их в ответ на какие-либо действия пользователя.

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

В этой статье мы рассмотрим небольшой пример работы  со стилями в WPF.
Итак, пуская мы хотим задать цвет фона и размер шрифта для кнопки. Самый простой и очевидный способ сделать это – это просто задать все нужные стили прямо в теге кнопки:


        <Button Name="button1" Width="150" FontSize="12" Background="AliceBlue" 


Content="Click Me!" Margin="3" Click="OnResources" />


Есть еще один способ задать стиль элемента прямо в самом элементе:


<Button Width="150" Content="Click Me!" Margin="3">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Button.Style>
</Button>


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

К счастью, стили можно создавать отдельно от самих элементов управления и хранить их в коллекциях ресурсов. Например, вот так:

<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="LemonChiffon" />
<Setter Property="FontSize" Value="18" />
</Style>
</Window.Resources>

Такой стили станет стилем по умолчанию для кнопок на текущей странице.

 
        <Button Width="200" Content="Uses default style" Margin="3" />
 
Давайте зададим еще несколько стилей.


<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="LemonChiffon" />
<Setter Property="FontSize" Value="18" />
</Style>
<Style x:Key="ButtonStyle">
<Setter Property="Button.Background" Value="Red" />
<Setter Property="Button.Foreground" Value="White" />
<Setter Property="Button.FontSize" Value="18" />
</Style>

<Style x:Key="FancyButtonStyle">
<Setter Property="Button.FontSize" Value="22" />
<Setter Property="Button.Foreground" Value="White" />
<Setter Property="Button.Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.0" Color="LightCyan" />
<GradientStop Offset="0.14" Color="Cyan" />
<GradientStop Offset="0.7" Color="DarkCyan" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>


 Применим их к нескольким новым кнопкам.
        
         <Button Width="200" Content="Uses named style" Style="{StaticResource ButtonStyle}" Margin="3" />

        <Button Width="200" Content="Fancy button style" Style="{StaticResource FancyButtonStyle}" Margin="3" />
 

Еще одной особенностью стилей в WPF является то, что стили могут наследовать свойства других стилей. Вот пример:
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="LemonChiffon" />
<Setter Property="FontSize" Value="18" />
</Style>
<Style x:Key="ButtonStyle">
<Setter Property="Button.Background" Value="Red" />
<Setter Property="Button.Foreground" Value="White" />
<Setter Property="Button.FontSize" Value="18" />
</Style>

<Style x:Key="FancyButtonStyle">
<Setter Property="Button.FontSize" Value="22" />
<Setter Property="Button.Foreground" Value="White" />
<Setter Property="Button.Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.0" Color="LightCyan" />
<GradientStop Offset="0.14" Color="Cyan" />
<GradientStop Offset="0.7" Color="DarkCyan" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="AnotherButtonStyle" BasedOn="{StaticResource FancyButtonStyle}" TargetType="Button">
<Setter Property="Foreground">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Offset="0.2" Color="White" />
<GradientStop Offset="0.5" Color="LightYellow" />
<GradientStop Offset="0.9" Color="Orange" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>


</Window.Resources>

Стиль AnotherButtonStyle наследует свойства от FancyButtonStyle и переопределяет лишь свойство ForeGround. Можно убедиться, что цвет фона и размер шрифта идентичен для двух этих стилей.

        <Button Width="200" Content="Style inheritance" Style="{StaticResource AnotherButtonStyle}" Margin="3" />

Так будет выглядеть весь код страницы:

<Window x:Class="StylesAndResources.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="240" Width="500">
    <Window.Resources>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Background" Value="LemonChiffon" />
            <Setter Property="FontSize" Value="18" />
        </Style>
        <Style x:Key="ButtonStyle">
            <Setter Property="Button.Background" Value="Red" />
            <Setter Property="Button.Foreground" Value="White" />
            <Setter Property="Button.FontSize" Value="18" />
        </Style>

        <Style x:Key="FancyButtonStyle">
            <Setter Property="Button.FontSize" Value="22" />
            <Setter Property="Button.Foreground" Value="White" />
            <Setter Property="Button.Background">
                <Setter.Value>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                        <GradientStop Offset="0.0" Color="LightCyan" />
                        <GradientStop Offset="0.14" Color="Cyan" />
                        <GradientStop Offset="0.7" Color="DarkCyan" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
        </Style>
       
        <Style x:Key="AnotherButtonStyle" BasedOn="{StaticResource FancyButtonStyle}" TargetType="Button">
            <Setter Property="Foreground">
                <Setter.Value>
                    <LinearGradientBrush>
                        <GradientStop Offset="0.2" Color="White" />
                        <GradientStop Offset="0.5" Color="LightYellow" />
                        <GradientStop Offset="0.9" Color="Orange" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
        </Style>


    </Window.Resources>
    <StackPanel>
        <Button Name="button1" Width="150" FontSize="12" Background="AliceBlue" Content="Click Me!" Margin="3" Click="OnResources" />
        <Button Width="150" Content="Click Me!" Margin="3">
            <Button.Style>
                <Style TargetType="Button">
                    <Setter Property="Background" Value="Yellow" />
                    <Setter Property="FontSize" Value="14" />
                    <Setter Property="FontWeight" Value="Bold" />
                </Style>
            </Button.Style>
        </Button>
        <Button Width="200" Content="Uses default style" Margin="3" />
        <Button Width="200" Content="Uses named style" Style="{StaticResource ButtonStyle}" Margin="3" />
        <Button Width="200" Content="Fancy button style" Style="{StaticResource FancyButtonStyle}" Margin="3" />
        <Button Width="200" Content="Style inheritance" Style="{StaticResource AnotherButtonStyle}" Margin="3" />

    </StackPanel>
</Window>

В результате должно получиться вот такое окошко с шестью кнопками.

Стили также можно создавать и применять динамически в коде приложения. Давайте посмотрим на простой пример создания такого стиля.
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

 

namespace StylesAndResources

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();

        }

 

        private void OnResources(object sender, RoutedEventArgs e)

        {

            new ResourceDemo().Show();

        }

    }

}


<Window x:Class="StylesAndResources.ResourceDemo"

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

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

        Title="Resource Demo" Height="300" Width="300">

    <StackPanel x:Name="myContainer">

        <StackPanel.Resources>

            <LinearGradientBrush x:Key="MyGradientBrush" StartPoint="0,0" EndPoint="0.3,1">

                <GradientStop Offset="0.0" Color="LightCyan" />

                <GradientStop Offset="0.14" Color="Cyan" />

                <GradientStop Offset="0.7" Color="DarkCyan" />

            </LinearGradientBrush>

        </StackPanel.Resources>

        <Button Width="200" Height="50" Foreground="White" Margin="5" 
Background="{StaticResource MyGradientBrush}" Content="Click Me!" />

 

        <Button Name="button1" Width="220" Height="50" Margin="5" Click="button1_Click">

            Apply Resource Programmatically

        </Button>

 

        <Button Name="button2" Width="200" Height="50" Foreground

="White" 
Margin="5" Background="{DynamicResource MyGradientBrush}" Content="Change Resource" 
Click="button2_Click" />

 

        

 

    </StackPanel>

 

</Window>



Создадим еще одну страницу и реализуем обработчик первой кнопки так, чтобы при нажатии на нее открывалась новая страница:


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

 

namespace StylesAndResources

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();

        }

 

        private void OnResources(object sender, RoutedEventArgs e)

        {

            new ResourceDemo().Show();

        }

    }

}

<Window x:Class="StylesAndResources.ResourceDemo"

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

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

        Title="Resource Demo" Height="300" Width="300">

    <StackPanel x:Name="myContainer">

        <StackPanel.Resources>

            <LinearGradientBrush x:Key="MyGradientBrush" StartPoint="0,0" EndPoint="0.3,1">

                <GradientStop Offset="0.0" Color="LightCyan" />

                <GradientStop Offset="0.14" Color="Cyan" />

                <GradientStop Offset="0.7" Color="DarkCyan" />

            </LinearGradientBrush>

        </StackPanel.Resources>

        <Button Width="200" Height="50" Foreground="White" Margin="5" Background="{StaticResource MyGradientBrush}" Content="Click Me!" />

 

        <Button Name="button1" Width="220" Height="50" Margin="5" Click="button1_Click">

            Apply Resource Programmatically

        </Button>

 

        <Button Name="button2" Width="200" Height="50" Foreground="White" Margin="5" Background="{DynamicResource MyGradientBrush}" Content="Change Resource" Click="button2_Click" />

 

        

 

    </StackPanel>

 

</Window>

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

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            Control ctrl = sender as Control;

            ctrl.Background = ctrl.FindResource("MyGradientBrush"as Brush;

        }

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

        private void button2_Click(object sender, RoutedEventArgs e)

        {

            myContainer.Resources.Clear();

            var brush = new LinearGradientBrush { StartPoint = new Point(0, 0), EndPoint = new Point(0, 1) };

 

            brush.GradientStops = new GradientStopCollection()

            {

                new GradientStop(Colors.White, 0.0),

                new GradientStop(Colors.Yellow, 0.14),

                new GradientStop(Colors.YellowGreen, 0.7)

            };

            myContainer.Resources.Add("MyGradientBrush", brush);

 

 

        }

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

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

 

namespace StylesAndResources

{

    /// <summary>

    /// Interaction logic for ResourceDemo.xaml

    /// </summary>

    public partial class ResourceDemo : Window

    {

        public ResourceDemo()

        {

            InitializeComponent();

        }

 

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            Control ctrl = sender as Control;

            ctrl.Background = ctrl.FindResource("MyGradientBrush"as Brush;

        }

 

        private void button2_Click(object sender, RoutedEventArgs e)

        {

            myContainer.Resources.Clear();

            var brush = new LinearGradientBrush { StartPoint = new Point(0, 0), EndPoint = new Point(0, 1) };

 

            brush.GradientStops = new GradientStopCollection()

            {

                new GradientStop(Colors.White, 0.0),

                new GradientStop(Colors.Yellow, 0.14),

                new GradientStop(Colors.YellowGreen, 0.7)

            };

            myContainer.Resources.Add("MyGradientBrush", brush);

 

 

        }

    }

}

Стили – это очень важный инструмент при работе с XAML, Windows Presentation Foundation и Silverlight. Так что надеюсь, что это простая вводная статья будет полезной.