3.Šablony & Trigry
Templates
Zatímco styly slouží pouze ke kosmetickým úpravám prvků, pomocí vlastnosti
Template můžeme vzhled celé kontroly úplně přepsat.
Podívejme se na standardní WPF tlačítko.
Skládá se z textového pole a obdelníku. U tlačítka můžeme změnit barvu pozadí nebo velikost písma, protože tlačítko má tyto vlastnosti
FontSize a
Background. Pokud ale chceme, aby tlačítko mělo jiný vzhled (například aby bylo kulaté), nezbývá nám nic jiného než přepsat šablonu (
Button.Template) této kontroly, neboli znovu nadefinovat z jakých prvků se má skládat.
<Button Width="150" Height="50">
<Button.Template>
<ControlTemplate>
<Grid>
<Ellipse Fill="LightGreen" />
<Label VerticalAlignment="Center"
HorizontalAlignment="Center"
Content="Button" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
Povinný element je
ControlTemplate, do kterého vložíme prvky, ze kterých se má naše kontrola nově skládat.
* Obsah uvedený mezi tagy odpovídá vždy některé z vlastností dané kontroly. Například u Labelu jsou tedy tyto zápisy ekvivalentní
<Label>můj text</Label>
a
<Label Content="můj text" />
Šablony ve stylech
Ve stylech pracujeme s vlastností
Template stejně jako s jinými vlastnostmi. Podívejme se na jednoduchý příklad, ve kterém pomocí stylu přepíšeme šablonu všech tlačítek.
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<!-- Common style -->
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Width="50" Height="30">
<Ellipse Fill="{TemplateBinding Button.Background}" />
<Label Content="{TemplateBinding Button.Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Content="New" />
<Button Content="Open" />
<Button Content="Save" Background="LightGreen" />
<Button Content="Close" Background="LightGreen" />
</StackPanel>
Vsimněte si zápisu hodnot vlastností
Fill a
Content. Pozadí elipsy a obsah tlačiítka tady není určen přímo v šabloně, ale pomocí
TemplateBinding se použije hodnota z kontroly.
Pomocí
TemplateBinding tedy pracujeme uvnitř šablony s hodnotami zadanými u kontroly. V tomto případě jsme použili barvu zadanou ve vlastnosti
Background tlačítka na vyplnění elipsy.
<Ellipse Fill="{TemplateBinding Button.Background}" />
^^
<Button Content="Save" Background="LightGreen" />
Obdobným způsobem je načten i text.
* Všiměte si, že u tlačítek, u kterých není vlastnost Background zadaná, se použila hodnota ze Bdního vzhledu.
Dependency properties
Dříve než přejdeme k trigrům, musíme si vysvětlit koncept tzv. 'Dependency properties'. Jedná se o speciální vlastnosti kontrol. Pokud nadefinujeme třídě novou vlastnost (
property), můžeme její hodnotu pouze číst nebo zapisovat. Dependency properties jsou ale rozšířeny o další funkce a můžeme pak použít na takovou vlastnost databinding, "animovat" její hodnotu (více o tomto až v dalších kapitolách) a nebo použít property trigger. Co je zajímavého na těchto vlastnostech pro nás nyní je, že hodnota takovéto vlastnosti může být také závislá na vlastnostech jiných kontrol. Jak to přesně funguje?
Příklad:
<Border TextElement.Foreground="Red" TextElement.FontSize="20">
<StackPanel>
<Label Foreground="Green">
<TextBlock>Green?!</TextBlock>
</Label>
<TextBlock>Red?!</TextBlock>
</StackPanel>
</Border>
Co je na této aplikaci nestandardní? Ačkoliv
TextBlocky nemají nastavenou barvu textu (
Foreground), není barva jejich textu standardní. Jedná se totiž o
Dependency properties a hodnoty těchto vlastností jsou převzaty z některého z nadřazených elementů.
TextBlock s červeným textem tedy přebral barvu z elementu
StackPanel, který tuto hodnotu přebral z elementu
Border.
Trigry
Trigry (anglicky
Triggers) nám v XAMLu umožní reagovat na údálosti kontrol, na hodnoty vlastností kontrol nebo na datové hodnoty. Podle toho taky rozlišujeme trigry na:
- Property triggers - reagují na hodnotu vlastnosti (property) kontroly
- Event triggers - reagují na události
- Data triggers - reagují na hodnotu proměné
Trigry se používají nejčastěji ve spojení se styly. Podívejme se na použití všech tří typů ve stylech.
Property trigger
Property trigry reagují pouze na hodnoty
dependency properties (vlastnosti) kontroly. V
property trigrech můžeme nastavit hodnoty vlastností pomocí elementu
Setter a nebo pracovat s animacemi. Jakmile podmínka trigru neplatí, vrátí se hodnoty zpět do původního stavu.
Příklad: Chceme, aby po najetí myší nad tlačítko byl text červený a zvýrazněný. Použijeme vlastnost
IsMouseOver tlačítka, která je
True pokud je myš nad tlačítkem, v opačném případě je
False.
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.FontWeight" Value="Bold" />
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Button>New</Button>
<Button>Open</Button>
<Button>Save</Button>
<Button>Close</Button>
</StackPanel>
Setter se tedy vykoná pouze pokud hodnota
IsMouseOver je
True. Pokud myší z tlačítka odjedeme, tlačítko se vrátí do původního stavu.
* Před názvy vlastností je lepší zadávat typ kontroly
MultiTrigger
MultiTrigry používáme pokud u
property trigru potřebujeme zadat více podmínek spuštění, například aby se text tlačítka změnil pouze pokud je kurzor myši nad tlačítkem a zároveň text tlačítka je "Close"
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<Style.Triggers>
<MultiTrigger>
<!-- Conditions -->
<MultiTrigger.Conditions>
<Condition Property="Button.IsMouseOver" Value="True" />
<Condition Property="Button.Content" Value="Close" />
</MultiTrigger.Conditions>
<!-- Setter -->
<Setter Property="Button.FontWeight" Value="Bold" />
<Setter Property="Button.Foreground" Value="Red" />
</MultiTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Button>New</Button>
<Button>Open</Button>
<Button>Save</Button>
<Button>Close</Button>
</StackPanel>
Nyní jsou elementy
Setter použity pouze na tlačítko s textem "Close" pokud je kurzor myši nad tlačítkem.
Event trigger
V event trigrech můžeme pracovat pouze s animacemi. Ty probereme až v následujícím díle, proto implementaci animací v následující ukázce můžete vynechat.
Příklad: Po najetí myší nad tlačítko se výška tlačítka změní z původní hodnoty 20px na 30px. Jakmile tlačítko myší opustíme, vrátí se zpět do původní velikosti. Změny velikostí trvají 0.5 sekundy.
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Button.Height" Value="20" />
<Style.Triggers>
<!-- Enlarge button -->
<EventTrigger RoutedEvent="Button.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="30"
Storyboard.TargetProperty="Height"
Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<!-- Back -->
<EventTrigger RoutedEvent="MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="20"
Storyboard.TargetProperty="Height"
Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Button>New</Button>
<Button>Open</Button>
<Button>Save</Button>
<Button>Close</Button>
</StackPanel>
Data trigry
Data trigry nám umožní reagovat na hodnotu proměných v seznamech jako je například ListBox.
Trigry bez stylů?
Proč zde uvádím používání trigrů pouze ve spojení se styly? Proč bychom nemohli používat trigry přímo v kontrolách?
IntelliSence nám při psaní kódu nabídne tento způsob zápisu:
<Button>
<Button.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.FontSize" Value="20" />
</Trigger>
</Button.Triggers>
</Button>
^^ tento kód ale vyvolá chybu při běhu aplikace
Bohužel WPF (mně z neznámých důvodů) ani ve verzi 3.5 nepodporuje
property trigry a
data trigry u kontrol (fungují takto pouze
event trigry) a tak jediná možnost jak tento nedostatek obejít je použít styly.
Ukázka použití property trigru u jednoho tlačítka pomocí stylů
<StackPanel>
<Button Content="New">
<Button.Style>
<Style>
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.FontSize" Value="20" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
Tabulka kdy a kde je možné jednotlivé trigry používat
|
použíti ve stylech |
použití v kontrole |
|
Setter |
Animace |
Setter |
Animace |
| Property trigger |
ano |
ano |
ne |
ne |
| Event trigger |
ne |
ano |
ne |
ano |
| Data trigger |
ano |
ano |
ne |
ne |
Demo
V následujícím demu spojíme dohromady styly, trigry a šablony a vytvoříme si vlastní vzhled tlačítek v aplikaci.

<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<!-- Template -->
<Setter Property="Button.Template">
<Setter.Value>
<ControlTemplate>
<Border x:Name="myBorder" BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Margin="2"
Background="{TemplateBinding Button.Background}">
<ContentControl Content="{TemplateBinding Button.Content}"
FontWeight="Bold"
HorizontalAlignment="Center" />
</Border>
<!-- Triggers -->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Border.Background" Value="Black"
TargetName="myBorder" />
<Setter Property="Button.Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Content="New" />
<Button Content="Open" />
<Button Content="Save" />
<Button Content="Close" Background="Red" />
</StackPanel>
- použili jsme obecný (nepojmenovaný) styl, takže tento vzhled je použit na každé tlačítko ve StackPanelu
- přepsali jsme šablonu tlačítka, tzn. museli jsme znovu nadefinovat prvky tlačítka a přidali efekty
- obsah tlačítka nemusí být pouze text, ale například i obrázek nebo jiný element, proto pro vypsání Contentu jsme použili element ContentControl; obsah načítáme (TemplateBinding) z vlastnosti Content tlačítka
- použili jsme Property trigger - po najetí na tlačítko se změní barva textu a pozadí tlačítka
- TargetName stanoví prvek v šablone, na který má být element Setter použit; u Button.Foreground nebylo potřeba TargetName použít, protože se jedná o Dependency property takže elementy uvnitř šablony převezmou hodnotu z nadřazených elementů
Závěr
Díky šablonám máme kontroly ve WPF plně pod kontrolou, pokud se nám nelíbí standardní vzhled, můžeme si navolit vlastní a přitom zároveň zachovat funkčnost kontroly. Trigry nám zase umožní vytvářet efekty a reagovat na události nebo vlastnosti kontrol.