23.6 Templates
Um mehreren Steuerelementen ein identisches Layout zu verleihen und dabei redundanten Code zu vermeiden, eignen sich Styles ganz hervorragend. Die grundlegende optische Darstellung der Steuerelemente wird dabei aber nicht verändert, ein Button-Steuerelement wird beispielsweise immer als Rechteck dargestellt. Die Möglichkeiten eines Styles sind daher vergleichsweise beschränkt.
Templates gehen in dieser Hinsicht einen Schritt weiter, denn sie gestatten die individuelle Gestaltung des Layouts der Steuerelemente durch eigenen XAML-Code, ohne die elementare Funktionsweise dabei zu beeinflussen. Möglich wird das durch die konsequente Aufteilung von Logik und Darstellung in WPF. Unter Windows Forms waren Logik und Darstellung so miteinander verkoppelt, dass man eigene Steuerelemente entwickeln musste, um eine individuelle Darstellung zu erzielen. Das war mit nicht unerheblichem Programmieraufwand verbunden, weil die Logik in das neue Steuerelement eingepflegt werden musste.
Doch nicht nur das Layout eines Steuerelements kann mit einem Template verändert werden. Auch die Darstellung von einzelnen Daten oder Datengruppen lässt sich mit Templates nach eigenen Vorstellungen anpassen. Deshalb werden in der WPF auch drei Arten von Templates unterschieden:
- ControlTemplates
- ItemsPanelTemplates
- DataTemplates
Mit einem ControlTemplate wird das Layout eines Steuerelements beschrieben, und ein DataTemplate übernimmt die Darstellung der Daten eines bestimmten Typs in einem ContentControl. Mit ItemsPanelTemplate passen wir das Layout eines Steuerelements an, das mehrere Elemente aufnehmen kann. Dabei handelt es sich um die Steuerelemente, die von der Klasse ItemsControl abgeleitet sind (ListBox, ComboBox, Menu usw.).
In diesem Kapitel werden wir uns ausschließlich mit dem ControlTemplate beschäftigen, da DataTemplates und ItemsPanelTemplates meistens im Zusammenhang mit der Darstellung eingesetzt werden. Dieses Thema wird uns aber erst in Kapitel 24 und 25 beschäftigen.
23.6.1 Allgemeines zu »ControlTemplates«
ControlTemplates sind für das Layout der Steuerelemente verantwortlich. Es ist nicht sehr schwierig, das Standard-Template eines Steuerelements durch ein eigenes auszutauschen, um einem Steuerelement ein individuelles Layout zu verpassen.
Nicht alle Steuerelemente lassen sich mit Templates umgestalten. Templates sind nur bei Steuerelementen möglich, die von der Klasse Control abgeleitet sind. Bei allen anderen erfolgt die Darstellung auch weiterhin mit Programmcode. Einige Steuerelemente bieten zudem die Möglichkeit, Teilbereiche zu ändern.
Grundlagen der Templates
ControlTemplates werden durch die gleichnamige Klasse beschrieben und können im XAML-Code sehr einfach definiert werden. Die Inhaltseigenschaft VisualTree von ControlTemplate beschreibt den visuellen Elementbaum, der für die Darstellung des Steuerelements verantwortlich ist. Am Beispiel einer Schaltfläche soll das erläutert werden. Dabei werden wir schrittweise die Darstellung eines Button-Objekts ändern, so dass am Ende eine optisch ansprechende, elliptische Schaltfläche das Ergebnis sein wird. Das folgende Listing soll uns als Ausgangspunkt dienen. Hier wird zunächst nur das Layout der neuen Schaltfläche beschrieben.
[...]
<Window.Resources>
<ControlTemplate x:Key="ellipseButton">
<Grid>
<Ellipse Name="ellipse" Width="100" Height="60">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Offset="0" Color="Wheat" />
<GradientStop Offset="1" Color="DarkGray" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</ControlTemplate>
<Window.Resources>
[...]
Listing 23.28 Das Layout einer benutzerdefinierten Schaltfläche
Innerhalb von ControlTemplate ist ein Grid-Element mit nur einer Zelle definiert. Das ist vorteilhaft, da das Grid die Größe aller enthaltenen Elemente automatisch anpasst. Natürlich ist auch möglich, einen anderen, beliebigen Container zu verwenden.
Innerhalb des Grid wird ein Ellipse-Objekt beschrieben. Dieses soll unseren elliptischen Button darstellen. Die Eigenschaften Height und Width sind derzeit noch statisch definiert. Das hat zur Folge, dass die Ellipse immer gleich groß dargestellt wird, unabhängig davon, welche Abmessungen die Schaltfläche tatsächlich aufweist. Wir werden später noch eine entsprechende Anpassung vornehmen müssen, damit sich die Ellipse den Außenabmessungen der Schaltfläche anpasst. Das Füllmuster der Ellipse weist einen Farbverlauf auf, auf den an dieser Stelle aber nicht weiter eingegangen wird.
Jedes Template muss mit einem eindeutigen Identifier signiert werden, der dem Attribut x:Key bekannt gegeben wird. Im Gegensatz zu einem Style ist dieses Attribut keine Option, sondern Pflicht. Das Template kann zudem von jedem Steuerelementtyp verwendet werden, vorausgesetzt, er ist von Control abgeleitet. Möchten Sie das Template aber auf einen bestimmten Typ beschränken, müssen Sie zusätzlich noch das Attribut TargetType angeben, z. B.:
<ControlTemplate x:Key="ellipseButton" TargetType="Button">
[...]
</ControlTemplate>
Listing 23.29 Struktur des Elements »ControlTemplate«
Templates lassen sich innerhalb eines beliebigen Resources-Abschnitts definieren. Man wird sich aber kaum die Mühe machen, ein Template nur für ein oder mehrere Steuerelemente in einem Fenster zu entwickeln. Vielmehr soll die ganze Anwendung davon profitieren. Deshalb sind Templates meistens global innerhalb der Datei App.xaml oder gar in einem Ressourcenwörterbuch definiert.
Jeder Steuerelementtyp hat sein eigenes, spezifisches Standard-Template, das von der WPF vorgegeben wird. Unser Ziel ist es, dieses durch unser eigenes Template zu ersetzen. Dazu stellen die Steuerelemente, die von Control abgeleitet sind, die Eigenschaft Template bereit. Dieser geben wir mit StaticResource den Verweis auf unser Template bekannt:
<Button Template="{StaticResource ellipseButton}">
Button
</Button>
Listing 23.30 Eine Schaltfläche mit einem »ControlTemplate« verbinden
Abbildung 23.7 Button in elliptischer Darstellung
Verfeinerung des Entwurfs
Obwohl das Ergebnis zum jetzigen Zeitpunkt durchaus schon optisch respektabel ist, müssen wir selbstkritisch noch die folgenden Mängel feststellen:
- Die Größe der angezeigten Ellipse ist unveränderlich. Auch eine Änderung der Eigenschaften Height und Width beispielsweise im Eigenschaftsfenster der Schaltfläche oder im XAML-Code wird daran nichts ändern, die Ellipse erscheint weiterhin in derselben Größe.
- Unsere neue Schaltfläche weist keine Beschriftung auf. Hier sind anscheinend noch Stellschrauben zu bedienen, um die Inhaltseigenschaft der zugrunde liegenden Schaltfläche auf unsere eigene Schaltfläche zu übertragen.
- Unsere Benutzerschaltfläche zeigt keine optischen Änderungen, wenn die Maus darüber gezogen wird oder sie gar angeklickt wird, obwohl sie durchaus auf das Click-Ereignis reagiert.
Diesen drei Punkten wollen wir uns nun der Reihe nach widmen und dabei weiter gehende Erkenntnisse hinsichtlich der Templates sammeln.
Wertübernahme mit »TemplateBinding«
Damit das ControlTemplate auch in verschiedenen Größen dargestellt werden kann, muss es die entsprechenden Daten aus dem übergeordneten Element beziehen. Bei dem übergeordneten Element handelt es sich um dasjenige, dem das ControlTemplate als Vorlage zugewiesen wird. In unserem Beispiel handelt es sich also um einen Button. Die Datenübernahmen geschehen mittels Datenbindung, die mit der Markup Extension TemplateBinding umgesetzt wird. TemplateBinding muss immer dort angegeben werden, wo Werte aus den Eigenschaften benötigt werden.
In unserem Beispiel ist die elliptische Form noch wie folgt definiert:
<Ellipse Name="ellipse" Width="100" Height="60">
Wir ergänzen das Ellipse-Element nun wie folgt und zeichnen auch gleichzeitig einen Rahmen um die äußere Kontur.
<Ellipse Name="ellipse"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Stroke="Black"
StrokeThickness="1">
Listing 23.31 Datenübernahme mit »TemplateBinding«
In ähnlicher Weise widmen wir uns auch noch der Eigenschaft Content des übergeordneten Steuerelements. Denn bisher können wir von einer der herausragenden Eigenschaften der WPF, dem Verschachteln mehrerer Steuerelemente, noch nicht profitieren. Zudem bleibt uns noch immer die Möglichkeit verwehrt, den elliptischen Button zu beschriften. Hier bietet die WPF das Element ContentPresenter an, das speziell für den Entwurf von Templates bereitgestellt wird.
<ControlTemplate x:Key="ellipseButton" TargetType="Button">
<Grid>
<Ellipse ...>
[...]
</Ellipse>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" >
</ContentPresenter>
</Grid>
</ControlTemplate>
Listing 23.32 Die »Content«-Eigenschaft übernehmen
Da wir den Inhalt nicht am Bezugspunkt links oben ausgerichtet haben wollen, sondern zentral innerhalb der Ellipse, wird der Inhalt mit HorizontalAlignment und VerticalAlignment mittig dargestellt. Mit der Markup Extension TemplateBinding binden wird die Content-Eigenschaft des ContentPresenter-Elements an die Content-Eigenschaft der übergeordneten Schaltfläche.
Interaktivität mit Trigger
Obwohl unser Template schon recht weit gediehen ist, reagiert es immer noch nicht auf den Anwender. Zieht man zum Beispiel mit der Maus über die Komponente, signalisiert keine farbliche Änderung, dass das Steuerelement nun aktiv ist und angeklickt werden kann.
Zur Lösung dieser Problematik bieten sich wieder Trigger an, ähnlich wie bei den Styles. Trigger werden der Eigenschaft Triggers des ControlTemplate zugewiesen. Das folgende Beispiel beschreibt die Änderung der Darstellung, wenn mit der Maus über die Ellipse, also den Button, gezogen wird. Ist der Wert von IsMouseOver true, wird der Trigger ausgelöst. Mit Hilfe von Setter-Elementen werden dann die Eigenschaften des Steuerelements verändert. Im Code des Beispiels wird nur der Rand der Ellipse in der Farbe Rot angezeigt.
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter TargetName="ellipse" Property="StrokeThickness" Value="3" />
<Setter TargetName="ellipse" Property="Stroke" Value="Red" />
</Trigger>
[...]
</ControlTemplate.Triggers>
Listing 23.33 Triggern der Maus
Im Allgemeinen wird diese Verhaltensänderung für sich alleine nicht ausreichen. Klickt der Anwender auf die Schaltfläche, soll vermutlich auch die Füllfarbe die Aktion visualisieren. Hierzu ist ein zweiter Trigger erforderlich, der ausgelöst wird, wenn die Eigenschaft IsPressed den Wert true hat. Der entsprechende XAML-Code wäre dann zum Beispiel wie folgt:
<ControlTemplate x:Key="ellipseButton" TargetType="Button">
<Grid>
[...]
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter TargetName="ellipse"
Property="StrokeThickness" Value="3" />
<Setter TargetName="ellipse"
Property="Stroke" Value="Red" />
</Trigger>
<Trigger Property="Button.IsPressed" Value="True">
<Setter TargetName="ellipse" Property="StrokeThickness" Value="2" />
<Setter TargetName="ellipse" Property="Stroke" Value="Red" />
<Setter TargetName="ellipse" Property="Fill">
<Setter.Value>
<RadialGradientBrush>
<GradientStop Offset="0" Color="Blue" />
<GradientStop Offset="1" Color="WhiteSmoke" />
</RadialGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Listing 23.34 Komplette Trigger des Beispiels
In Abbildung 23.8 sehen Sie das Ergebnis der Bemühungen. Links ist die »normale« Darstellung des Buttons zu sehen, rechts ein Button, während auf ihn geklickt wird.
Abbildung 23.8 »EllipseButton«-ControlTemplate
Das komplette Beispiel des ControlTemplates finden Sie auf der Buch-DVD unter
Beispiele\Kapitel 23\ControlTemplateSample.
23.6.2 Definition innerhalb eines Styles
Häufig wird innerhalb einer Anwendung ein Template definiert, das auf alle Steuerelemente des gleichen Typs angewendet werden soll, so dass das Template anwendungsintern zum Standard-Template der Steuerelemente mutiert. Dafür sind Styles besonders gut geeignet. Damit erübrigt sich ein manuelles Zuweisen des Templates.
Je nachdem, wo das Template definiert ist, wird zwischen einer expliziten und impliziten Definition von ControlTemplates unterschieden. Das folgende Codefragment zeigt ein explizites Template. Hier wird in der Eigenschaft Template des Setter-Elements auf die statische Ressource verwiesen.
<ControlTemplate x:Key="EllipseButton" TargetType="Button">
[...]
</ControlTemplate>
<Style x:Key="MyEllipseButton"
TargetType="{x:Type Button}" >
<Setter Property="Template" Value="{StaticResource EllipseButton}" />
</Style>
Listing 23.35 Explizites ControlTemplate
Im Steuerelement wird anschließend der Style bekannt gegeben.
<Button Style="{StaticResource MyEllipseButton}" ... />
Das ControlTemplate kann auch innerhalb eines Styles definiert werden. Das erleichtert nicht nur die Wartbarkeit, sondern es entfällt beim Steuerelement auch die Angabe der Ressource mit Template oder Style.
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Listing 23.36 Implizites ControlTemplate
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.