23.7 Ermitteln des visuellen Elementbaums
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 daher oftmals die Kenntnis des visuellen Elementbaums des betreffenden Steuerelements notwendig. In der Dokumentation ist dieser leider nicht zu finden. Sie sind also auf sich selbst gestellt, das herauszufinden.
23.7.1 Das Tool »Expression Blend«
Widmen wir uns daher nun der Aufgabe, den Visual Tree eines Steuerelements zu ermitteln. Visual Studio 2012 bietet uns dazu keine direkte Lösung an. So müssen wir entweder auf ein anderes Tool zurückgreifen oder die sich bietenden Möglichkeiten des .NET Frameworks nutzen.
Als Tool bietet sich beispielsweise Microsoft Expression Blend an, das mit Visual Studio 2012 ausgeliefert wird. Expression Blend dient der Gestaltung von Benutzeroberflächen, unter anderem auch der von WPF-Anwendungen. In der folgenden Abbildung wird gezeigt, wie Sie sich den Visual Tree eines Button-Steuerelements in Expression Blend 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.
Abbildung 23.9 Expression Blend
Expression Blend erzeugt damit einen 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 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.
<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>
Listing 23.37 Standard-Template eines Buttons
23.7.2 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 GetStandardTemplate wird das demonstriert. Die Oberfläche des Programms zeigt Abbildung 23.10. 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, ist eine Voraussetzung, dass das angegebene Steuerelement auch die Basis Control hat, da sonst eine Exception ausgelöst wird.
Abbildung 23.10 Ausgabe des Beispielprogramms »GetStandardTemplate«
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 werden und zu einem Mitglied des Window werden.
- Im zweiten Schritt kann das Standard-Template des Objekts abgefragt und das TextBlock-Steuerelement eingetragen werden.
Der Programmcode setzt das Bekanntgeben der Namespaces System.Reflection, System.Xml und System.Windows.Markup voraus.
// Beispiel: ..\Kapitel 23\GetStandardTemplate
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 bedarf einiger Kniffe, 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 bedarf es nur noch des Konstruktoraufrufs, 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 das Standard-Template des Controls abrufen zu können, müssen wir das Objekt nun nur noch dem Elementbaum hinzufügen:
stackPanel.Children.Add(ctrl);
Grundlage der nun endlich folgenden Ausgabe des Standard-Templates 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.
Die Wahl für diese Überladung der Methode Save hat einen Vorteil, denn die Ausgabe des XmlWriter-Objekts kann mit einem XmlWriterSettings-Objekt beeinflusst werden. Mit dessen Eigenschaft Indent wird festgelegt, ob die Elemente eingezogen ausgegeben werden sollen. Bekanntermaßen sind String-Objekte unveränderlich. Das hätte in unserem Beispiel zur Folge, dass die Speicherressourcen wesentlich belastet werden können. Um das zu vermeiden, wird stattdessen ein StringBuilder-Objekt verwendet.
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.