## 20.4 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 relativ 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. Als weitaus flexibler ist das Konzept der Templates von WPF zu bewerten. Sogenannte ControlTemplates sind für die Gestaltung des Layouts der Steuerelemente verantwortlich. Es ist nicht sehr schwierig, das Standard-Template eines Steuerelements durch ein eigenes Template auszutauschen, um einem Steuerelement ein individuelles Layout zu verpassen, dem kaum noch Grenzen gesetzt sind. Die zugrunde liegende strikte Trennung von Logik und Darstellung in WPF wird daher in vielen Fällen dazu führen, dass Sie nicht ein neues Steuerelement von Grund auf neu entwickeln müssen, sondern auf ein bereits vorhandenes zurückgreifen können.
Anmerkung
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.
### 20.4.1 Grundlagen der Templates ControlTemplates werden durch die Klasse ControlTemplate beschrieben und können im XAML-Code relativ 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, sodass am Ende eine optisch ansprechende, elliptische Schaltfläche das Ergebnis sein wird. Das folgende Listing soll uns als Ausgangspunkt dienen.
    <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>
Innerhalb von ControlTemplate ist ein Grid-Element definiert. Das ist vorteilhaft, weil in ihm mehrere übereinanderliegende Elemente platziert werden können und das Grid die Größe aller enthaltenen Elemente automatisch anpasst. Natürlich ist es auch möglich, einen anderen, beliebigen Container zu verwenden. Innerhalb des Grid wird ein Ellipse-Element beschrieben, dessen Eigenschaften Height und Width derzeit noch statisch definiert sind. 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 weiter unten 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 aber 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 einschränken, müssen Sie zusätzlich noch das Attribut TargetType angeben, zum Beispiel:
    <ControlTemplate x:Key="EllipseButton" TargetType="Button">
      ...
    </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 definiert. Jeder Steuerelementtyp hat sein eigenes Standard-Template, das von WPF vorgegeben wird. Unser Ziel ist es, dieses durch ein eigenes Template zu ersetzen. Dazu stellen die Steuerelemente, die von Control abgeleitet sind, die Eigenschaft Template bereit, der Sie das neue Template zuweisen.
    <Button Template="{StaticResource ResourceKey=EllipseButton}">
      Button
    </Button>
Befindet sich das Template innerhalb der aktuellen Quellcodedatei, können Sie auf ResourceKey verzichten. Ist das Template jedoch in App.xaml definiert, kann ohne Voranstellung von ResourceKey der Template-Identifier nicht aufgelöst werden. Hier klicken, um das Bild zu vergrößern Abbildung 20.6 Button in elliptischer Darstellung ### 20.4.2 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 widmen und dabei weitergehende 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 ersetzen 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">
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.
    <ContentPresenter
      HorizontalAlignment="Center"
      VerticalAlignment="Center"
      Content="{TemplateBinding Content}" >
    </ContentPresenter>
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 wir 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 Stilen. Trigger werden der Eigenschaft Triggers der ControlTemplates zugewiesen. Das folgende Beispiel beschreibt die Änderung der Darstellung, wenn mit die Maus über die Ellipse, also den Button, gezogen wird. Ist der Wert von IsMouseOver true, wird der Trigger ausgelöst. Mittels 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>
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.Triggers>
      ...
      <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>
In Abbildung 20.7 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. Hier klicken, um das Bild zu vergrößern Abbildung 20.7 EllipseButton-ControlTemplate
Hinweis
Das komplette Beispiel des ControlTemplates finden Sie auf der Buch-DVD unter Beispiele\Kapitel 20\ButtonTemplate.
### 20.4.3 Definition innerhalb eines Styles Meistens wird innerhalb einer Anwendung ein Template definiert, das auf alle Steuerelemente gleichen Typs angewendet werden soll, sodass das Template anwendungsintern zum Standard-Template der Steuerelemente mutiert. Dafür sind Styles besonders gut geeignet. Damit erübrigt sich ein manuelles Zuweisen der Templates. Es wird zwischen der Verwendung von expliziten und impliziten ControlTemplates unterschieden. Das folgende Codefragment zeigt ein explizites Template. Hier wird in der Eigenschaft Template des Setter-Elements auf die statische Ressource verwiesen.
    <Style x:Key="MyEllipseButton"
           TargetType="{x:Type Button}" >
      <Setter Property="Template" Value="{StaticResource EllipseButton}" />
    </Style>
    <ControlTemplate x:Key="EllipseButton" TargetType="Button">
      ...
    </ControlTemplate>
Im Steuerelement wird anschließend der Style bekannt gegeben:
    <Button Style="{StaticResource MyEllipseButton}" ... />
Das ControlTemplate kann aber auch innerhalb eines Styles definiert werden. Damit verbessert sich nicht nur die Wartbarkeit, zudem entfällt beim Steuerelement 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>
### 20.4.4 Den Visual Tree ermitteln Es hört sich sehr verlockend an, aus einer rechteckigen Schaltfläche beispielsweise eine runde zu machen. Allerdings ist das Redesign eines Steuerelements mit sehr viel Codierungsaufwand verbunden, denn jedes Steuerelement baut auf einem mehr oder weniger umfangreichen Standard-Template auf. Um ein Steuerelement individuell zu designen, ist die Kenntnis des Visual Tree des betreffenden Steuerelements notwendig. Dazu wäre allerdings zu sagen, dass in der Dokumentation leider keine passende Beschreibung des Visual Tree zu finden ist. Sie sind also auf sich selbst gestellt, das herauszufinden. #### Das Tool »Expression Blend« Widmen wir uns daher nun den Möglichkeiten, den Visual Tree eines Steuerelements zu ermitteln. Leider bietet uns Visual Studio 2010 nichts in dieser Hinsicht an. So müssen wir entweder auf andere Tools zurückgreifen oder die Möglichkeiten nutzen, die das .NET Framework bietet. Als Tool bietet sich beispielsweise Microsoft Expression Blend an, das aber leider nicht kostenlos zur Verfügung gestellt wird. Expression Blend dient zur Gestaltung von Benutzeroberflächen, unter anderem auch der von WPF-Anwendungen. In Abbildung 20.8 wird gezeigt, wie Sie sich den Visual Tree eines Button-Steuerelements in Expression Blend 3 ausgeben lassen können. Im Kontextmenü für den Button wählen Sie Vorlage bearbeiten und darunter den Punkt Kopie bearbeiten. Sie müssen jetzt der Kopie noch einen neuen Namen geben oder akzeptieren den vorgeschlagenen. Expression Blend erzeugt damit einen neuen Style auf Basis des Standard-Styles und verknüpft diesen automatisch mit dem Button. Sie können sich das Grundgerüst des Styles ansehen, wenn Sie im Menü Ansicht • Aktive Dokumentansicht aus der Designeransicht in die XAML-Ansicht umschalten. Innerhalb des Styles sind auch diverse Trigger hinterlegt. Der folgende XAML-Code zeigt den kopierten Style mit dem Template und soll nur einen Eindruck davon vermitteln, wie umfangreich sich der Standard-Style einer Schaltfläche darstellt. Um nicht unnötig Platz zu verschwenden, ist er sogar noch deutlich gekürzt worden. Hier klicken, um das Bild zu vergrößern Abbildung 20.8 Expression Blend 3
    <Window ...>
      <Window.Resources>
        <Style x:Key="ButtonFocusVisual">
          <Setter Property="Control.Template">
            <Setter.Value>
              <ControlTemplate>
              <Rectangle Stroke="Black" StrokeDashArray="12"
                          StrokeThickness="1" Margin="2"
                          SnapsToDevicePixels="true"/>
              </ControlTemplate>
           </Setter.Value>
           </Setter>
        </Style>
        <LinearGradientBrush x:Key="ButtonNormalBackground"
                              EndPoint="0,1" StartPoint="0,0">
           <GradientStop Color="#F3F3F3" Offset="0"/>
            ...
        </LinearGradientBrush>
        <SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
          <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
            <Setter Property="FocusVisualStyle"
                    Value="{StaticResource ButtonFocusVisual}"/>
                    ...
            <Setter Property="Template">
              <Setter.Value>
               <ControlTemplate TargetType="{x:Type Button}">
                 <Microsoft_Windows_Themes:ButtonChrome
                    x:Name="Chrome" SnapsToDevicePixels="true"
                    Background="{TemplateBindingBackground}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    RenderDefaulted="{TemplateBinding  IsDefaulted}"
                    RenderMouseOver="{TemplateBinding IsMouseOver}"
                    RenderPressed="{TemplateBinding IsPressed}">
                    <ContentPresenter ... />
                 </Microsoft_Windows_Themes:ButtonChrome>
                 <ControlTemplate.Triggers>
                  <Trigger Property="IsKeyboardFocused" Value="true">
                     <Setter Property="RenderDefaulted"
                          TargetName="Chrome" Value="true"/>
                  </Trigger>
                      ...
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </Window.Resources>
      <Grid x:Name="LayoutRoot">
        <Button Style="{DynamicResource ButtonStyle1}"/>
       </Grid>
    </Window>
#### Standard-Template mit Code abfragen Steht Ihnen das Werkzeug Expression Blend nicht zur Verfügung, lässt sich das Standard-Template auch mit Code ermitteln. Im folgenden Beispielprogramm wird das demonstriert. Abbildung 20.9 zeigt die Oberfläche des Programms. In der TextBox tragen Sie den Typ des Steuerelements ein, dessen Standard-Template im unteren TextBlock angezeigt werden soll. Beachten Sie dabei unbedingt die Groß-/Kleinschreibung. Wie bereits weiter oben angedeutet wurde, ist eine Voraussetzung, dass das angegebene Steuerelement auch die Basis Control hat, da sonst eine Exception ausgelöst wird. Den XAML-Code entnehmen Sie bitte der dem Buch beigefügten DVD. Interessanter ist die genauere Betrachtung des Ereignishandlers der Schaltfläche. Die Aktionen, die innerhalb des Ereignishandlers ausgeführt werden müssen, lassen sich durch zwei Schwerpunkte beschreiben: Aus der Typangabe, die in der TextBox eingetragen ist, muss zuerst ein passendes Objekt erzeugt und zu einem Mitglied des Window werden. Im zweiten Schritt kann das Standard-Template des Objekts abgefragt und das TextBlock-Steuerelement eingetragen werden. Hier klicken, um das Bild zu vergrößern Abbildung 20.9 Ausgabe des Beispielprogramms »StandardTemplate« Der Programmcode setzt das Bekanntgeben der Namespaces System.Reflection, System.Xml und System.Windows.Markup voraus.
    // ---------------------------------------------------------------
    // Beispiel: ...\Kapitel 20\StandardTemplate
    // ---------------------------------------------------------------
    private void btnShowTemplate_Click(object sender, RoutedEventArgs e) {
      try {
        // Erzeugen eines Objekts von dem in der Textbox angegebenen Typ
        Assembly assembly = Assembly.GetAssembly(stackPanel.GetType());
        Type controlType = assembly.GetType("System.Windows.Controls." +
                           txtControl.Text.Trim());
        Control ctrl = (Control)controlType.GetConstructor
                                         (Type.EmptyTypes).Invoke(null);
        stackPanel.Children.Add(ctrl);
        // Standard-Template ermitteln und im TextBlock-Control eintragen
        StringBuilder builder = new StringBuilder();
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        XmlWriter writer = XmlWriter.Create(builder, settings);
        XamlWriter.Save(ctrl.Template, writer);
        writer.Close();
        txtOutput.Text = builder.ToString();
        stackPanel.Children.Remove(ctrl);
      }
      catch(Exception ex) {
        txtOutput.Text = "FEHLER: " + ex.Message;
      }
    }
Es sind ein paar Kniffe nötig, um aus der in der TextBox eingetragenen Zeichenfolge ein Steuerelement-Objekt zu erzeugen. Dazu nutzen wir die Möglichkeiten der Reflection. Mit
    Assembly assembly = Assembly.GetAssembly(stackPanel.GetType());
besorgen wir uns zunächst die Referenz auf die aktuelle Assembly. Diese benötigen wir, um anschließend mit
    Type controlType = assembly.GetType("System.Windows.Controls." +
                                         txtControl.Text.Trim());
die Type-Informationen der entsprechenden Klasse abzurufen. Nun brauchen wir nur noch einen Konstruktoraufruf, um ein Objekt des gewünschten Typs in den Speicher zu laden. Dazu dient die folgende Anweisung:
    Control ctrl =
       (Control)controlType.GetConstructor(Type.EmptyTypes).Invoke(null);
Die Methode GetConstructor sucht den öffentlichen Konstruktor des Steuerelements in den Typinformationen. Durch Übergabe von Type.EmptyTypes sprechen wir den parameterlosen Konstruktor an, der mit Invoke schließlich aufgerufen wird. Um die Standard-Template des Controls abrufen zu können, müssen wir das Objekt nun nur noch dem Elementbaum hinzufügen:
    stackPanel.Children.Add(ctrl);
Die Grundlage der nun endlich folgenden Ausgabe des Standard-Template ist die Klasse XamlWriter, mit der XAML ausgegeben werden kann. Der Methode Save dieser Klasse übergeben Sie das Template des Steuerelements, indem Sie die Eigenschaft Template des Steuerelements abrufen. Template ist in der Klasse Control definiert. Für den Aufruf der mehrfach überladenen Methode Save ist im Beispiel. die Variante gewählt, die in einem zweiten Parameter ein XmlWriter-Objekt erwartet, in das die Standardvorlage des Steuerelements geschrieben wird.