Was sind Stile und Vorlagen? (WPF .NET)
Stile und Vorlagen in Windows Presentation Foundation (WPF) beziehen sich auf eine Reihe von Funktionen, mit denen Entwickler und Designer visuell überzeugende Effekte und ein einheitliches Erscheinungsbild für ihr Produkt erstellen können. Wenn Sie die Darstellung einer App anpassen, benötigen Sie ein leistungsfähiges Stil- und Vorlagenmodell, das die Wartung und Freigabe des Erscheinungsbilds innerhalb der jeweiligen und zwischen Apps ermöglicht. WPF stellt dieses Modell bereit.
Eine weitere Funktion des WPF-Stilmodells ist die Trennung von Darstellung und Programmlogik. Designer können die Darstellung einer App nur mithilfe von XAML bearbeiten, während Entwickler mit C# oder Visual Basic an der Programmierlogik arbeiten.
Diese Übersicht konzentriert sich auf die Aspekte der Stile und Vorlagen der App und geht nicht auf Datenbindungskonzepte ein. Weitere Informationen zur Datenbindung finden Sie unter Übersicht über die Datenbindung.
Es ist wichtig, die Ressourcen zu kennen, die die Wiederverwendung von Stilen und Vorlagen ermöglichen. Weitere Informationen zu Ressourcen finden Sie unter Übersicht der XAML-Ressourcen.
Beispiel
Der Beispielcode in dieser Übersicht basiert auf einer einfachen Anwendung zum Suchen von Fotos, die in der folgenden Abbildung gezeigt wird.
In diesem einfachen Foto-Beispiel werden Stile und Vorlagen verwendet, um eine visuell ansprechende Benutzeroberfläche zu erstellen. Das Beispiel verfügt über zwei TextBlock-Elemente und ein ListBox-Steuerelement, das an eine Liste von Bildern gebunden ist.
Das vollständige Beispiel finden Sie unter Einführung zum Beispiel zu Stilen und Vorlagen.
Stile
Style ist eine einfache Möglichkeit, mehrere Eigenschaftswerte auf mehrere Elemente anzuwenden. Stile lassen sich auf alle Elemente anwenden, die von FrameworkElement oder FrameworkContentElement abgeleitet sind, z. B. Window oder Button.
Stile werden üblicherweise deklariert, indem sie im Abschnitt Resources
einer XML-Datei als Ressource deklariert werden. Da es sich bei Stilen um Ressourcen handelt, unterliegen sie denselben Regeln für Gültigkeitsbereiche, die für alle Ressourcen gelten. Einfach ausgedrückt: Wo Sie einen Stil deklarieren, bestimmt darüber, wo der Stil angewendet werden kann. Wenn Sie den Stil z. B. im Stammelement Ihrer XAML-Datei mit der App-Definition deklarieren, kann der Stil überall in der App verwendet werden.
Der folgende XAML-Code deklariert beispielsweise zwei Stile für einen TextBlock
: einen Stil, der automatisch auf alle TextBlock
-Elemente angewendet wird, und einen anderen Stil, auf den explizit verwiesen werden muss.
<Window.Resources>
<!-- .... other resources .... -->
<!--A Style that affects all TextBlocks-->
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock"
x:Key="TitleText">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Foreground">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#90DDDD" />
<GradientStop Offset="1.0" Color="#5BFFFF" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Im Folgenden finden Sie ein Beispiel der oben deklarierten Stile, die verwendet werden.
<StackPanel>
<TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>
Weitere Informationen finden Sie unter Erstellen eines Stils für ein Steuerelement.
ControlTemplates
In WPF definiert die ControlTemplate eines Steuerelements die Darstellung des Steuerelements. Sie können die Struktur und die Darstellung eines Steuerelements ändern, indem Sie eine neue ControlTemplate definieren und einem Steuerelement zuweisen. In vielen Fällen erhalten Sie durch Vorlagen ausreichende Flexibilität, um keine eigenen benutzerdefinierten Steuerelemente schreiben zu müssen.
Jedes Steuerelement verfügt über eine Standardvorlage, die der Control.Template-Eigenschaft zugewiesen ist. Die Vorlage verbindet die visuelle Darstellung des Steuerelements mit den Funktionen des Steuerelements. Da Sie eine Vorlage in XAML definieren, können Sie die Darstellung des Steuerelements ändern, ohne Code schreiben zu müssen. Jede Vorlage ist für ein bestimmtes Steuerelement konzipiert (z. B. ein Button-Element).
Im Allgemeinen deklarieren Sie eine Vorlage im Abschnitt Resources
einer XAML-Datei als Ressource. Wie bei allen Ressourcen gelten Gültigkeitsbreichsregeln.
Steuerelementvorlagen sind viel komplexer als ein Stil. Dies liegt daran, dass die Steuerelementvorlage die visuelle Darstellung des gesamten Steuerelements erneut schreibt, während ein Stil einfach Eigenschaftsänderungen auf das vorhandene Steuerelement anwendet. Da jedoch die Vorlage eines Steuerelements durch Festlegen der Control.Template-Eigenschaft angewendet wird, können Sie einen Stil verwenden, um eine Vorlage zu definieren oder festzulegen.
Designer ermöglichen es Ihnen im Allgemeinen, eine Kopie einer vorhandenen Vorlage zu erstellen und zu ändern. Wählen Sie im WPF-Designer von Visual Studio beispielsweise ein CheckBox
-Steuerelement aus, klicken Sie mit der rechten Maustaste, und wählen Sie dann Vorlage bearbeiten>Kopie erstellen aus. Mit diesem Befehl wird ein Stil generiert, der eine Vorlagen definiert.
<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
<Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
<Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid x:Name="markGrid">
<Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
<Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
</Grid>
</Border>
<ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="true">
<Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
<Setter Property="Padding" Value="4,-1,0,0"/>
... content removed to save space ...
Das Bearbeiten einer Kopie einer Vorlage ist eine gute Möglichkeit, um zu erfahren, wie Vorlagen funktionieren. Anstatt eine neue leere Vorlage zu erstellen, ist es einfacher, eine Kopie zu bearbeiten und einige Aspekte der visuellen Darstellung zu ändern.
Ein Beispiel dazu finden Sie unter Erstellen einer Vorlage für ein Steuerelement.
TemplateBinding
Möglicherweise haben Sie bemerkt, dass die im vorherigen Abschnitt definierte Vorlagenressource die TemplateBinding-Markuperweiterung verwendet. Eine TemplateBinding
ist die optimierte Form einer Bindung für Vorlagenszenarien, die einer mit {Binding RelativeSource={RelativeSource TemplatedParent}}
erstellten Bindung entspricht. TemplateBinding
ist nützlich, um Teile der Vorlage an die Eigenschaften des Steuerelements zu binden. Jedes Steuerelement verfügt beispielsweise über eine BorderThickness-Eigenschaft. Verwenden Sie eine TemplateBinding
, um zu verwalten, welches Element in der Vorlage von dieser Steuerelementeinstellung betroffen ist.
ContentControl und ItemsControl
Wenn ein ContentPresenter in der ControlTemplate eines ContentControl-Elements deklariert wird, wird der ContentPresenter automatisch an die ContentTemplate- und Content-Eigenschaften gebunden. Ebenso wird ein ItemsPresenter, der sich in der ControlTemplate eines ItemsControl-Elements befindet, automatisch an die Eigenschaften ItemTemplate und Items gebunden.
DataTemplates
In dieser Beispiel-App gibt es ein ListBox-Steuerelement, das an eine Liste von Fotos gebunden ist.
<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>
Diese ListBox sieht zurzeit wie folgt aus.
Die meisten Steuerelemente verfügen über Inhalte, und dieser Inhalt stammt häufig aus Daten, an die Sie binden. In diesem Beispiel sind die Daten die Liste der Fotos. In WPF verwenden Sie eine DataTemplate, um die visuelle Darstellung von Daten zu definieren. Ihre Eingabe in DataTemplate bestimmt im Grunde genommen, wie die Daten in der gerenderten App aussehen.
In unserer Beispiel-App verfügt jedes benutzerdefinierte Photo
-Objekt über eine Source
-Eigenschaft vom Typ „string“ (Zeichenfolge), die den Pfad des Bilds angibt. Derzeit werden die Fotoobjekte als Dateipfade angezeigt.
public class Photo
{
public Photo(string path)
{
Source = path;
}
public string Source { get; }
public override string ToString() => Source;
}
Public Class Photo
Sub New(ByVal path As String)
Source = path
End Sub
Public ReadOnly Property Source As String
Public Overrides Function ToString() As String
Return Source
End Function
End Class
Damit die Fotos als Bilder angezeigt werden, erstellen Sie eine DataTemplate-Klasse als Ressource.
<Window.Resources>
<!-- .... other resources .... -->
<!--DataTemplate to display Photos as images
instead of text strings of Paths-->
<DataTemplate DataType="{x:Type local:Photo}">
<Border Margin="3">
<Image Source="{Binding Source}"/>
</Border>
</DataTemplate>
</Window.Resources>
Beachten Sie, dass die DataType-Eigenschaft mit der TargetType-Eigenschaft von Style vergleichbar ist. Wenn sich Ihre DataTemplate im Abschnitt „Resources“ befindet und Sie die DataType-Eigenschaft für einen Typ angeben und einen x:Key
auslassen, wird die DataTemplate immer dann angewendet, wenn dieser Typ angezeigt wird. Sie haben immer die Möglichkeit, die DataTemplate einem x:Key
zuzuweisen und dann als StaticResource
für Eigenschaften festzulegen, die DataTemplate-Typen annehmen, z. B. die ItemTemplate-Eigenschaft oder die ContentTemplate-Eigenschaft.
Im Wesentlichen definiert das DataTemplate im Beispiel oben, dass jedes Mal, wenn ein Photo
-Objekt vorhanden ist, dieses als Image innerhalb eines Border-Elements angezeigt werden soll. Mit dieser DataTemplate sieht unsere App nun wie folgt aus.
Das Datenvorlagenmodell stellt weitere Funktionen bereit. Wenn Sie z. B. Sammlungsdaten anzeigen, die andere Sammlungen enthalten, die einen HeaderedItemsControl-Typ wie Menu oder TreeView verwenden, ist die HierarchicalDataTemplate vorhanden. Eine weitere Datenvorlagenfunktion ist das DataTemplateSelector-Element, das es Ihnen ermöglicht, basierend auf benutzerdefinierter Progammlogik eine zu verwendende DataTemplate auszuwählen. Weitere Informationen finden Sie unter im Artikel Übersicht über Datenvorlagen, der eine ausführlichere Erläuterung der verschiedenen Vorlagenfunktionen bereitstellt.
Trigger
Ein Trigger legt Eigenschaften fest oder startet Aktionen, z.B. eine Animation, wenn sich ein Eigenschaftswert ändert oder ein Ereignis ausgelöst wird. Style, ControlTemplateund DataTemplate verfügen alle über eine Triggers
-Eigenschaft, die eine Reihe von Triggern enthalten kann. Es gibt mehrere Typen von Triggern.
PropertyTriggers
Ein Trigger, der Eigenschaftswerte festlegt oder Aktionen basierend auf einem Eigenschaftswert auslöst, wird als Eigenschaftstrigger bezeichnet.
Um die Verwendung von Eigenschaftstriggern zu veranschaulichen, können Sie jede ListBoxItem-Klasse teilweise transparent anzeigen, es sei denn, sie ist aktiviert. Im folgenden Stil wird der Opacity-Wert eines ListBoxItem auf 0.5
festgelegt. Wenn die IsSelected-Eigenschaft true
ist, wird Opacity jedoch auf 1.0
festgelegt.
<Window.Resources>
<!-- .... other resources .... -->
<Style TargetType="ListBoxItem">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="MaxHeight" Value="75" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.Setters>
<Setter Property="Opacity" Value="1.0" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
In diesem Beispiel wird ein Trigger verwendet, um einen Eigenschaftswert festzulegen, aber beachten Sie, dass die Trigger-Klasse auch über die Eigenschaften EnterActions und ExitActions verfügt, die es dem Trigger ermöglichen, Aktionen auszuführen.
Beachten Sie, dass die MaxHeight-Eigenschaft von ListBoxItem auf 75
festgelegt ist. In der folgenden Abbildung ist das dritte Element das ausgewählte Element.
EventTrigger und Storyboards
Ein weiterer Trigger ist EventTrigger, der eine Reihe von Aktionen startet, die auf dem Vorkommen eines Ereignisses basieren. Die folgenden EventTrigger-Objekte geben z. B. an, dass die ListBoxItem-Eigenschaft, wenn das MaxHeight-Element durch den Mauszeiger aktiviert wird, auf einen Wert von 90
über einen zweiten Zeitraum von 0.2
animiert wird. Wenn sich die Maus vom Element weg bewegt, gibt die Eigenschaft auf den ursprünglichen Wert über einen Zeitraum von 1
Sekunden an. Beachten Sie, dass es nicht notwendig ist, einen To-Wert für die MouseLeave-Animation anzugeben. Dies liegt daran, dass die Animation den ursprünglichen Wert nachverfolgen kann.
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.Setters>
<Setter Property="Opacity" Value="1.0" />
</Trigger.Setters>
</Trigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetProperty="MaxHeight"
To="90" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:1"
Storyboard.TargetProperty="MaxHeight" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
Weitere Informationen finden Sie in der Übersicht über Storyboards.
In der folgenden Abbildung zeigt die Maus auf das dritte Element.
MultiTrigger, DataTrigger und MultiDataTrigger
Zusätzlich zu Trigger und EventTriggergibt es auch andere Typen von Triggern. Mit MultiTrigger können Sie Eigenschaftswerte auf Grundlage mehrerer Kriterien festlegen. Sie verwenden DataTrigger und MultiDataTrigger, wenn die-Eigenschaft Ihrer Bedingung datengebunden ist.
Visuelle Zustände
Steuerelemente befinden sich immer in einem bestimmten Zustand. Wenn die Maus z. B. über die Oberfläche eines Steuerelements bewegt wird, wird das Steuerelement als im allgemeinen Zustand MouseOver
betrachtet. Ein Steuerelement ohne einen bestimmten Zustand wird als im allgemeinen Zustand Normal
betrachtet. Zustände werden in Gruppen unterteilt, und die zuvor erwähnten Zustände sind Teil der Statusgruppe CommonStates
. Die meisten Steuerelemente verfügen über zwei Statusgruppen: CommonStates
und FocusStates
. Für jede Zustandsgruppe, die auf ein Steuerelement angewendet wird, befindet sich ein Steuerelement immer in einem Zustand jeder Gruppe, z. B. CommonStates.MouseOver
und FocusStates.Unfocused
. Ein Steuerelement kann sich jedoch nicht in zwei verschiedenen Zuständen innerhalb derselben Gruppe befinden, etwa in CommonStates.Normal
und CommonStates.Disabled
. Im Folgenden finden Sie eine Tabelle mit Zuständen, die die meisten Steuerelemente erkennen und verwenden.
VisualState-Name | VisualStateGroup-Name | Beschreibung |
---|---|---|
Normal |
CommonStates |
Der Standardzustand |
MouseOver |
CommonStates |
Der Mauszeiger ist über dem Steuerelement positioniert. |
Pressed |
CommonStates |
Das Steuerelement wird gedrückt. |
Disabled |
CommonStates |
Das Steuerelement ist deaktiviert. |
Focused |
FocusStates |
Der Fokus liegt auf dem Steuerelement. |
Unfocused |
FocusStates |
Der Fokus liegt nicht auf dem Steuerelement. |
Wenn Sie ein System.Windows.VisualStateManager-Element für das Stammelement einer Steuerelementvorlage definieren, können Sie Animationen auslösen, wenn ein Steuerelement in einen bestimmten Zustand wechselt. Der VisualStateManager
deklariert, welche Kombinationen von VisualStateGroup und VisualState überwacht werden. Wenn das Steuerelement einen überwachten Zustand eintritt, wird die von VisualStateManager
definierte Animation gestartet.
Der folgende XAML-Code überwacht z. B. den CommonStates.MouseOver
-Zustand, um die Füllfarbe des Elements mit dem Namen backgroundElement
zu animieren. Wenn das Steuerelement in den CommonStates.Normal
-Zustand zurückkehrt, wird die Füllfarbe des Elements mit dem Namen backgroundElement
wiederhergestellt.
<ControlTemplate x:Key="roundbutton" TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<ColorAnimation Storyboard.TargetName="backgroundElement"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="{TemplateBinding Background}"
Duration="0:0:0.3"/>
</VisualState>
<VisualState Name="MouseOver">
<ColorAnimation Storyboard.TargetName="backgroundElement"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="Yellow"
Duration="0:0:0.3"/>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
Weitere Informationen zu Storyboards finden Sie unter Übersicht über Storyboards.
Gemeinsam genutzte Ressourcen und Designs
Eine typische WPF-Anwendung kann über mehrere Benutzeroberflächenressourcen verfügen, die in der gesamten App angewendet werden. Dieser Satz von Ressourcen wird im Ganzen als das Design der App bezeichnet. WPF bietet Unterstützung beim Packen der Benutzeroberflächenressourcen als Design mithilfe eines Ressourcenverzeichnisse, die als ResourceDictionary-Klasse gekapselt ist.
WPF-Designs werden mithilfe des Mechanismus für Stile und Vorlagen definiert, der von WPF zum Anpassen der visuellen Objekte eines Elements bereitgestellt wird.
WPF-Designressourcen werden in eingebetteten Ressourcenverzeichnissen gespeichert. Diese Ressourcenverzeichnisse müssen in einer signierten Assembly eingebettet werden und werden entweder in der gleichen Assembly wie der Code selbst oder in einer parallele Assembly eingebettet. Im Fall von „PresentationFramework.dll“ (der Assembly, die WPF-Steuerelemente enthält) befinden sich Designressourcen in einer Reihe von parallelen Assemblys.
Das Design wird zu dem letzten Ort, an dem bei der Suche nach dem Stil eines Elements gesucht wird. In der Regel beginnt die Suche, indem sie die Elementstruktur aufwärts durchläuft und nach einer geeigneten Ressource sucht. Dann wird die Suche in der Ressourcensammlung der App fortgesetzt, und schließlich wird das System abgefragt. Auf diese Weise können App-Entwickler der Stil für alle Objekte auf Struktur- oder Anwendungsebene neu definieren, bevor das Design erreicht wird.
Sie können Ressourcenverzeichnisse als einzelne Dateien definieren, die es Ihnen ermöglichen, ein Design in mehreren Apps wiederzuverwenden. Sie können auch austauschbare Designs erstellen, indem Sie mehrere Ressourcenverzeichnisse definieren, welche dieselben Ressourcentypen bereitstellen, jedoch mit unterschiedlichen Werten. Das Neudefinieren dieser Stile oder anderer Ressourcen auf Anwendungsebene ist die empfohlene Vorgehensweise für das Skinning von Apps.
Um eine Reihe von Ressourcen wie Stile und Vorlagen anwendungsübergreifend freizugeben, können Sie eine XAML-Datei erstellen und ein ResourceDictionary-Element definieren, das Verweise auf eine shared.xaml
-Datei enthält.
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
Es ist die gemeinsame Nutzung von shared.xaml
, die selbst ein ResourceDictionary-Element definiert, die eine Reihe von Stil- und Pinselressourcen enthält, die es den Steuerelementen in einer App ermöglichen, ein einheitliches Aussehen aufzuweisen.
Weitere Informationen finden Sie unter Gemergte Ressourcenverzeichnisse.
Wenn Sie ein Design für Ihr benutzerdefiniertes Steuerelement erstellen, finden Sie im Abschnitt Definieren von Ressourcen auf Designebene der Übersicht über das Erstellen von Steuerelementen weitere Informationen.
Siehe auch
.NET Desktop feedback