GUI-Programmierung 2 - Windows Presentation Foundation
Transcription
GUI-Programmierung 2 - Windows Presentation Foundation
GUI-Programmierung 2 - Windows Presentation Foundation (WPF) Alexander Aumann [email protected] Abstract: Seit .NET 3.0 ist WPF als Nachfolger von Windows Forms das Mittel der Wahl zum Erstellen grafischer Oberflächen mit .NET. Diese Ausarbeitung stellt die Unterschiede und ein paar der interessanteren Features von WPF vor. WPF wurde mit Version 3.0 des .NET-Frameworks von Microsoft als die neue Programierschnittstelle zur Erstellung grafischer Benutzeroberflächen (engl.: graphical user interfaces = GUIs) eingeführt. Es ist somit der Nachfolger der seit .NET 1.0 verwendeten Windows Forms, und hat es mittlerweile - was Microsofts eigene Projekte angeht - auch weitgehend abgelöst: so ist das neue Visual Studio 2010 mit WPF entwickelt worden [Sch09]. Im Rahmen dieses Dokuments werden daher die Vorzüge des neuen Systems im Vergleich zu Windows Forms aufgezeigt, sowie einige der interessanteren Neuerungen vorgestellt. 1 Windows Forms oder WPF? Ein Vergleich Ein erster großer Unterschied zwischen WPF und Windows Forms (kurz: WinForms) ist die Art und Weise wie das einzelne Standardsteuerelement (zum Beispiel ein Textfeld oder Button) auf den Bildschirm gezeichnet wird. WinForms greift zur Grafikausgabe auf GDI/GDI+ zurück und bietet somit praktisch nur eine Einbettung der schon seit Windows 95 grundsätzliche vorhandenen Zeichenfunktionen in Managed Code an. WPF dagegen zeichnet alle Steuerelemente unter Zuhilfenahme von DirectX selbst. Das ermöglicht auf der einen Seite eine sehr flexible Anpassung der Steuerelemente (siehe Abschnitt 4: Styles und Control Templates) und dank DirectX außerdem hardwarebeschleunigtes Zeichnen. Das zweite große Plus von WPF ist die Art und Weise, wie man als Designer den Aufbau der grafischen Oberfläche definiert. Betrachten wir dazu kurz die Vorgehensweise unter WinForms: Selbst wenn man zur Oberflächenerstellung ein grafisches Designprogramm, wie zum Beispiel den im Visual Studio enthaltenen Designer verwendet, wird der Entwurf im Hintergrund in C#-Programmcode umgesetzt und die Oberflächendefinition auf diese Art und Weise festgehalten. Als WPF-Designer dagegen definiert man Oberflächen in XAML (vergleiche Abschnitt 2) komplett getrennt von der für die Programmlogik verwendeten Programmiersprache. Dadurch ist es beispielsweise einem unabhängigen Designteam möglich, eine ausgefallene GUI zu entwerfen, ohne sich Gedanken über die verwendete Programmiersprache machen zu müssen. Mit WPF wurden auch viele der im Hintergrund wirksamen Mechanismen überarbeitet, so wurde das Eventsystem durch Routed Events deutlich verbessert, Commands erlauben es häufig vorkommende Befehle wie Drucken oder Copy & Paste zentral zu verwalten. Viele im Verlauf der kommenden Seiten vorgestellte Features funktionieren nur Dank sogenannter Dependency Properties. All diese Weiterentwicklungen und Neuerungen in WPF machen es erst so mächtig, wie es letztlich ist. Sie seien hier jedoch nur kurz der Vollständigkeit halber erwähnt, da eine ausführliche Behandlung den Rahmen dieser Arbeit sprengen würde. Bevor im nächsten Kapitel XAML näher vorgestellt wird, sollen auch einige Nachteile von WPF im Vergleich zu Windows Forms nicht unerwähnt bleiben. Zum Ersten erfordert gerade die Tatsache, dass WPF so umfangreiche Möglichkeiten bietet, eine gewisse Einarbeitungszeit, alleine schon, um sich einen Überblick zu verschaffen. Da WPF im Vergleich zu WinForms recht jung ist, fehlen auch (noch) ein paar der mächtigeren vorgefertigten Steuerelemente, wie zum Beispiel die DataGridView. 2 XAML XAML, beziehungsweise ausgeschrieben die Extensible Application Markup Language, wurde von Microsoft speziell für WPF entwickelt und ist im wesentlichen ein XMLDialekt, der zur Oberflächenbeschreibung (und Ressourcenverwaltung) dient. Zu Oberflächenbeschreibung zählen bei WPF nicht nur das einfache Layout beziehungsweise die Anordnung der Steuerelemente, sondern auch Styles, verschiedene Templates zum noch feineren Anpassen der einzelnen Steuerelemente und sogar Animationen. Auf diese Features wird in den späteren Abschnitten noch ausführlicher eingegangen, in diesem Kapitel werden zunächst einige Grundlagen vorgestellt. 2.1 XAML-Dateien in einer frischen WPF-Anwendung Wenn man beispielsweise mit dem Visual Studio 2010 ein neues WPF-Projekt erstellt, enthält es folgende Dateien, die zur Bearbeitung durch den Entwickler primär vorgesehen sind: • App.xaml • App.xaml.cs • MainWindow.xaml • MainWindow.xaml.cs Die App.* Dateien sind quasi leer (bis auf die Festlegung des Anwendungshauptfensters in App.xaml) und dienen später beispielsweise der Definition globaler Ressourcen. Die Datei MainWindow.xaml sieht zum Beispiel wie folgt aus: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBox Name="text1" Grid.Row="0"/> <Button Name="button1" Content="OK" Grid.Row="1"/> </Grid> </Window> Die erste Zeile legt dabei fest, dass eine Klasse MainWindow im Namespace WpfApplication1 erstellt werden soll, die von Window erbt. Die beiden folgenden Zeilen dienen zur Einbindung der für XAML nötigen XML-Namensräume, wobei in .../xaml/presentation zum Beispiel alle WPF-Klassen und somit alle Steuerelemente liegen. Dadurch, dass dieser Namensraum direkt dem des aktuellen Dokuments zugewiesen wird, können Elemente daraus ohne qualifizierenden Namen direkt verwendet werden (zum Beispiel Window oder Grid). Im Hintergrund sucht sich der XAML-Compiler automatisch die korrespondierende .NET-Klasse, im Fall von Window zum Beispiel System.Windows.Window. In ../xaml liegen speziellere Elemente, die dazu dienen genauer festzulegen, wie das XAML-File interpretiert werden soll. Auf diese Elemente kann mit "x:Elementname" zugreifen. Was in den verbliebenen Zeilen passiert, wird im nächsten Abschnitt genauer erläutert, zunächst wollen wir uns kurz der zugehörigen C#-Quelldatei MainWindow.xaml.cs widmen. Sie enthält den Teil der Klasse MainWindow, der zur Bearbeitung durch den Programmierer vorgesehen ist und ist bis auf einen Aufruf der InitializeComponent()Methode im Konstruktor anfangs leer. Diese Datei ist später der ideale Ort um beispielsweise Code zum Event-Handling et cetera unterzubringen. Ein großer Unterschied zu WindowsForms ist dabei, dass der automatisch generierte zweite Teil der Klasse MainWindow, der in der Datei MainWindow.d.cs liegt, nur Membervariablen zuweist, um dem Programmierer die Möglichkeit zu geben auf einzelne Steuerelemente aus dem Programmcode zuzugreifen. Das Layout hingegen wird darin nicht festgelegt, sondern zur Laufzeit aus der zugehörigen XAML-Datei konstruiert (genauer gesagt aus einer zur Kompilierzeit erzeugten binären Repräsentation des XAML-Codes, sogenanntem Binary-Application-Markup-Language-Code (BAML)). 2.2 Steuerlemente und ihre Eigenschaften Als Steuerelement (engl.: control) bezeichnet man die einzelnen Bausteine einer GUI, zum Beispiel Buttons, Labels oder Textfelder. Jedes Steuerelement hat gewisse Eigen- schaften (engl.: properties), die in WPF in Form von Dependency Properties umgesetzt werden (eine Erweiterung des Property-Systems von WinForms, mehr sei dazu an dieser Stelle nicht gesagt). Durch das Setzen dieser Eigenschaften kann man das Verhalten beziehungsweise Aussehen einzelner Steuerelemente beeinflussen, zum Beispiel indem man die Text-Eigenschaft einer TextBox festlegt. In XAML werden Eigenschaften festgelegt, indem man im Start-Tag des Elements nach dem Typ des Elements alle Eigenschaften, die man setzen möchte, aufzählt und ihnen den entsprechenden Wert zuweist. So erhält die Titel -Eigenschaft des Hauptfensters im obigen Beispiel den Wert "MainWindow" und zeigt diesen String somit in der Titelleiste an, außerdem werden noch Höhe und Breite des Fensters gesetzt (dazu sei kurz erwähnt, dass WPF aus Flexibilitätsgründen nicht in Pixeln rechnet, sondern in sogenann1 inch ). ten geräteunabhängigen Einheiten der Größe 96 Einem Fenster fügt man Steuerelemente in XAML einfach dadurch hinzu, dass man sie an der gewünschten Stelle im XML-Baum auflistet, im Regelfall eingebettet in ein Layout (mehr dazu im nächsten Kapitel). Im Beispiel enthält das Hauptfenster ein Grid-Layout, welches wiederum einen Button und eine TextBox enthält. Erwähneswert sind noch sogenannte Attached Properties: Hierbei handelt es sich um Eigenschaften, die eigentlich nicht zum Steuerelement selbst gehören, sondern zum Container, der es beeinhaltet. Alle Elemente, die in ein Grid eingebettet werden, erhalten so zum Beispiel eine zusätzliche Row-Eigenschaft. Um diese Attached Properties zu ändern, muss man vor die Eigenschaft noch den Namen des Elements stellen, zu der sie gehört: Grid.Row="0" im Beispiel setzt diese Eigenschaft für das Textfeld auf 0. Nachdem nun ein grober Überblick über XAML erfolgt ist, beschäftigt sich der nächste Abschnitt näher mit den verschiedenen Möglichkeiten eine Oberfläche zu strukturieren. 3 Layout Als Layout bezeichnet man allgemein die Anordnung von Steuerelementen auf der Benutzeroberfläche. Interessant wird die Angelegenheit vor allem, wenn beispielsweise der Benutzer die Fenstergröße ändert oder eine Labelbeschriftung zu groß wird und die Oberfläche zur Laufzeit angepasst werden muss, um trotzdem alle Informationen in angemessener Weise zu repräsentieren. An dieser Stelle kommen die WPF-Layout-Panels ins Spiel. Sie ermöglichen es Elemente im Fenster relativ zueinander anzuordnen und kümmern sich zum Beispiel darum, dass wenn sich die Aufschrift eines Labels ändert alle nötigen Größenverhältnisse der Oberfläche angepasst werden. Daraus resultierend ist es in WPF nicht üblich einzelne Elemente, wie noch bei WinForms ursprünglich vorgesehen, nach absoluten Koordinaten anzuordnen. Stattdessen ist der Standardansatz immer die Verwendung eines passenden Layoutpanels. Die folgenden Abschnitte stellen die verfügbaren Panels jeweils kurz vor. 3.1 Das StackPanel Das StackPanel ordnet Elemente entweder in einem horizontalen oder vertikalen Stapel an. Mit der Orientation-Eigenschaft des Stackpanels lässt sich festlegen, ob die Elemente vertikal oder horizontal ausgerichtet werden. Zur Beeinflussung der Anordnung eines Steuerelements im Beispiel des vertikalen Stapels dient unter anderem die sogenannte HorizontalAlignment-Eigenschaft des jeweiligen Controls dazu, es links, rechts, zentriert oder gestreckt auszurichten. 3.2 Das WrapPanel Ein weiterer Layout-Container ist das sogenannte WrapPanel, dass es ermöglicht Elemente in einer einzigen Reihe anzuordnen, wobei Elemente, die nicht mehr Platz haben, automatisch in die nächste Reihe umgebrochen werden. Jede Zeile wird dabei groß genug gemacht, um das höchste Steuerelement aufnehmen zu können, niedrigere Elemente werden entsprechend ihres Alignments darin ausgerichtet. 3.3 Das DockPanel Das DockPanel ermöglicht es, Elemente an eine der vier Seiten des Containers anzuheften. Ein Standardbeispiel in einer echten Anwendung wären ToolBox-Leisten, die sich häufig am oberen oder linken Rand eines Fensters befinden. Mit Hilfe des Attached Properties Dock des DockPanels, können die einzelnen Steuerelemente entsprechend angeordnet werden. Hierbei spielt die Reihenfolge der Steuerelemente eine Rolle: Fügt man zuerst ein Steuerelement ein, das oben andockt, nimmt es die gesamte Breite des Fensters ein. Danach eingefügte Steuerelemente, die zum Beispiel links angedockt werden, bekommen den restlichen Platz auf dieser Seite und so weiter. 3.4 Das Grid Das Grid-Layout ist der mächtigste aller Layout-Container. Er teilt den zu verwaltenden Platz in Zeilen und Spalten auf und jedem Kindelement können schließlich ein oder mehrere Zellen in diesem Gitter zugeordnet werden. Auf diese Art und Weise sind ausgefallene Layouts möglich, vor allem durch Schachteln von Grids und den einfacheren LayoutContainern können so nahezu beliebige Anordnungen erreicht werden. Abbildung 1 zeigt das Layout einer einfachen Beispielanwendung ”Filmsammlung”. Wie durch die roten Linien angedeutet, besteht das Layout aus einem äußeren 3x3-Grid. Die gesamte mittlere Spalte wird von einem GridSplitter beansprucht, der es dem Benutzer ermöglicht das Verhältnis von linker zu rechter Seite zu ändern. Links und rechts des Abbildung 1: Das Grid-Layout Splitters sind in der obersten Zeile Labels eingefügt, in der linken Spalte füllt eine ListBox die verbliebenen zwei Zeilen. Rechts ist unter dem Label ein weiteres Grid eingefügt, welches die nötigen Textfelder und Labels enthält, darunter befindet sich ein FlowPanel zur Ausrichtung der beiden Buttons. So sieht der Anfang einer Grid-Layout-Definition für gewöhnlich in XAML aus: <Grid Name="gridMain" Margin="3"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="3*"/> </Grid.ColumnDefinitions> ...Steuerelemente... </Grid> Im Grid.Row(/Column)Definitions-Abschnitt wird die benötigte Zeilen- und Spaltenzahl festgelegt, die Breite der Spalten (beziehungsweise analog Höhe der Zeilen) kann auf drei unterschiedliche Arten festgelegt werden: Auto legt fest, dass die Spalte genau so breit ist, dass alle Elemente Platz haben. Das Zusammenspiel der Breitenangaben 1* und 3* sorgt dafür, dass die dritte Spalte, wenn mehr Platz entsteht (zum Beispiel durch eine Vergrößerung des Fensters) dreimal so stark wächst wie die erste. Zuletzt gäbe es die Möglichkeit eine feste Zahl an Einheiten für die Breite anzugeben. Die einzelnen Kindelemente können mit Hilfe der Attached Properties Row, Column, RowSpan und ColumnSpan angeordnet werden. Die ersten legen dabei fest welcher Zelle das Element zugeordnet ist, die anderen beiden, über wie viele Zeilen und Spalten es sich ausbreitet. 3.5 UniformGrid und Canvas UniformGrid und Canvas sind spezialisierte Layout-Container, die im Vergleich zu den bereits erwähnten seltener Verwendung finden. Das UniformGrid teilt ein Gebiet in ein Raster aus lauter gleich großen Zeilen und Spalten auf, das Canvas dient zur Anordnung von Elementen nach absoluten Koordinaten, was nur in Spezialfällen (zum Beispiel im Zusammenhang mit komplexen Animationen) Sinn macht. 4 Styles und Control Templates Nach den grundsätzlichen Möglichkeiten zur Anordnung von Steuerelementen kommen wir nun zu den Möglichkeiten, die WPF anbietet um die einzelnen Elemente nahezu beliebig anzupassen: Styles und Control Templates. Damit sind umfangreiche Änderungen möglich, ohne dazu die zugehörige Programmlogik neu schreiben zu müssen. 4.1 Styles Styles ermöglichen es dem Designer in XAML einzelne Eigenschaften eines Steuerelements zu ändern und dem Element so ein neues Aussehen zu geben. Als Beispiel betrachten wir zunächst einen einfachen Style, der die Schrift eines Labels ändert: <Style x:Key="LabelHeaderStyle"> <Style.Setters> <Setter Property="Label.FontSize" Value="14"/> <Setter Property="Label.FontWeight" Value="Bold"/> <Setter Property="Label.FontFamily" Value="TimesNewRoman"/> <Setter Property="Label.Foreground"> <Setter.Value> <LinearGradientBrush > <GradientStop Color="Black" Offset="0"/> <GradientStop Color="Black" Offset="0.66"/> <GradientStop Color="Black" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> </Style.Setters> </Style> In der ersten Zeile wird dem Style mit x:Key="LabelHeaderStyle" ein Name zugewiesen. Ansonsten enthält die Styledefinition eine Reihe von sogenannten Settern, die der Style.Setters-Sammlung hinzugefügt werden. Jeder der Setter enthält ein Property-Attribut, das festlegt, welche Eigenschaft geändert werden soll und ein ValueAttribut, das den neuen Wert bestimmt. Dieses kann auch komplex sein, und wird dazu in ein eigenes Setter.Value-Tag eingebaut. Im Beispiel ist das ein LinearGradientBrush für die Foreground -Eigenschaft, der einen gleichmäßigen Farbverlauf zwischen den Farben an bestimmten Offsets ermöglicht, in diesem Fall allerdings nur von schwarz über schwarz nach schwarz. Warum das so Sinn machen kann, dazu gleich mehr. Zunächst aber zur Frage, wo denn nun der Nutzen von Styles liegt, man könnte die Eigenschaften wie Schriftgröße und -farbe schließlich auch direkt in der Oberflächendefinition des Layouts ändern, was sogar weniger Schreibaufwand verursacht. Der große Vorteil von Styles ist deren Wiederverwendbarkeit: den einmal definierten Style kann man beliebig vielen Steuerelementen zuweisen, zum Beispiel allen, die eine Überschrift darstellen. Und wenn man sich späer zu einer Umgestaltung entschließt, muss man die Änderungen nur an einer zentralen Stelle - dem Style - vornehmen. Styles werden als Ressourcen angelegt. Auf das Ressourcenmanagement wird an dieser Stelle nicht näher eingegangen, näheres dazu findet sich zum Beispiel unter [WEB10a]. Wie weist man nun aber einem Steuerelement einen speziellen Style zu? Dazu gibt es zwei grundsätzliche Ansätze: die erste Möglichkeit ist, die Style-Eigenschaft des Steuerelements zu setzen: <Label Content="Filmdaten" (...) Style="{StaticResource LabelHeaderStyle}"/> Auf diese Art und Weise wird der LabelHeaderStyle als Ressource geladen und dem Label zugewiesen. Die zweite Möglichkeit ist, Styles automatisch allen Elementen eines Typs zuzuweisen. Dazu muss die TargetType-Eigenschaft gesetzt werden, und der Style darf keinen Namen erhalten: <Style TargetType="Button"> (...) </Style > Styles können allerdings mehr als nur einzelne Eigenschaften statisch verändern, sie können nämlich durch sogenannte Style-Trigger auf verschiedene Events reagieren und auf diese Art und Weise zum Beispiel einen MouseOver-Effekt erzeugen: <Style> ...Setter... <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty= "Foreground.GradientStops[1].Color" To="BurlyWood" Duration="0:0:0.5"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> ...rückgängig machen... </EventTrigger> </Style.Triggers> </Style>. Dieser Style enthält einen EventTrigger der ausgelöst wird, sobald der Mauszeiger über dem Steuerelement ist. Als ausgeführte Aktion wird eine Animation festgelegt (in XAML sind Animationen immer in ein Storyboard eingebettet), die die Farbe des mittleren Offset (vom ursprünglichen Schwarz) in 0.5 Sekunden nach BurlyWood ändert. Dadurch lässt sich eine Art Aufglüheffekt erzeugen. Um diesen (dauerhaften) Effekt wieder rückgängig zu machen, ist es sinnvoll auch den MouseLeave-Event zu behandeln. Styles bieten bereits vielfältige Möglichkeiten zur Anpassung einzelner Controls. Will man ein Steuerelement jedoch komplett umgestalten und nicht nur einzelne Eigenschaften ändern, greift man auf Control Templates zurück, die im nächsten Kapitel vorgestellt werden. 4.2 Control Templates In WPF sind alle Steuerelemente intern aus kleineren Teilelementen zusammengesetzt, so besteht ein Button aus einem Rahmen, in den ein ContentPresenter eingebettet ist, der die Aufschrift darstellt. Dieser interne Zusammenbau wird im Gegensatz zum logischen Aufbau einer Oberfläche (ein Fenster enthält ein Layout, welches Steuerelemente enthält) als Visual Tree bezeichnet (näheres dazu zum Beispiel hier: [Sam10]). Möchte man ein Steuerelement also grundlegend umbauen muss es eine Möglichkeit geben diesen Visual Tree zu verändern. Control Templates bieten diese und wir werden sie im Folgenden benutzen um den Standardbutton anzupassen. Konkret soll der Button deutlich abgerundete Ecken erhalten sowie eine andere Rahmen- und Hintergrundfarbe, die sich dynamisch anpasst: So soll zum Beispiel ein Button über dem der Mauszeiger ist dunkler werden und auch auf Klicks optisch reagieren. Das nötige Control Template sieht wie folgt aus: <Style TargetType="Button"> ...beliebige (Style)Setter möglich... <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border TextBlock.Foreground= "{TemplateBinding Foreground}" x:Name="Border" CornerRadius="8" BorderThickness="1"> <Border.BorderBrush> ... </Border.BorderBrush> <Border.Background> ... </Border.Background> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration= "0:0:0.5"/> <VisualTransition GeneratedDuration="0" To="Pressed" /> </VisualStateGroup.Transitions> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver"> ...MouseOver-Aussehen festlegen... </VisualState> ... "Disabled" und "Pressed"... </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True" /> </Border> <ControlTemplate.Triggers> ...auch Trigger möglich... </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> Das erste, was hierbei vielleicht auffällt ist, dass das Control Template in einen Style eingebettet ist. Das ist gängige Praxis und ermöglicht vor allem eine automatische TemplateZuweisung an alle Buttons (TargetType-Eigenschaft des Styles). Das neue Control Template wird der Template-Eigenschaft des Steuerelements zugewiesen. Im tatsächlichen Control Template wird nun der Aufbau des Buttons festgelegt, in diesem Fall genügt uns ein Rahmen mit abgerundeter Ecke und den entsprechenden Farbeigenschaften. Der Rah- men enthält wiederum einen ContentPresenter und außerdem einen VisualStateManager, dazu gleich mehr. Sogenannte Template Bindings erlauben es eine interne Eigenschaft, wie zum Beispiel die TextBlock.Foreground -Eigenschaft an die externe Eigenschaft Foreground des Buttons zu binden: das bedeutet, wenn der Designer in XAML dem Button einen anderen Vordergrund zuweist, wird diese Zuweisung an den TextBlock weitergeleitet. Der VisualStateManager schließlich bietet eine einfache Möglichkeit für einzelne Zustände wie MouseOver oder Clicked entsprechende Eigenschaften zu setzen, über Transitions können die Übergänge zwischen diesen Zuständen feiner geregelt werden. Im Beispiel sollen die Übergänge zwischen den einzelnen Zuständen jeweils eine halbe Sekunde dauern, außer bei einem Klick (To:Pressed ), der sofort angezeigt wird. Abbildung 2 zeigt die angepasste Benutzeroberfläche mit Überschriftlabels und neuen Buttons, sowie einem angepassten Hintergrund mit Farbverlauf und leicht durchsichtigen Textfeldern. Abbildung 2: Filmsammlung mit benutzerdefiniertem Style Zum Abschluss bleibt noch zu sagen, dass das Buttonbeispiel noch ein recht einfaches Control Template ist, da ein Button prinzipiell nur eine logische Funktion hat. Möchte man dagegen zum Beispiel eine ScrollBar umgestalten muss man deutlich mehr beachten, da sie aus mehreren Komponenten, wie einem Balken, einem Slider und den Pfeiltasten zusammengesetzt ist und somit von der Anwendungslogik gewisse Vorgaben an ein eigenes Control Template gestellt werden. Ein sehr guter Einstiegspunkt für eigene Templates ist das Microsoft SimpleStyles-Beispielprojekt, das für jedes Standardcontrol ein übersichtliches, angepasstes Control Template enthält. Das Beispiel steht hier zum Download bereit: [WEB10b]. 5 Data Binding und Data Templates Nachdem wir nun schon gesehen haben was WPF im Gegensatz zu Windows Forms in Sachen Oberflächenanpassung alles kann, soll nun auch noch ein Blick auf Datenbindung in WPF geworfen werden. Datenbindung ist ein recht weitläufiger Begriff, im aktuellen Zusammenhang bedeutet es so viel wie irgendeine Art von Daten an ein Steuerelement auf der Benutzeroberfläche zu binden. 5.1 Data Binding Die einfachste Art von Data Binding in WPF wäre es beispielsweise eine bestimmte (Dependency) Property eines Controls an eine andere Eigenschaft eines zweiten Controls zu binden. So ist es zum Beispiel möglich ein Slider-Control mit der Schriftgröße eines Labels so zu verknüpfen, dass sich die Schriftgröße des Labels automatisch ändert, sobald der Benutzer den Slider bewegt. Die interessantere Art der Datenbindung ist es, im Hintergrund von der Programmlogik gespeicherte Daten mit einem oder mehreren Steuerelementen zu verknüpfen. Hierzu wollen wir uns die Filmsammlung noch einmal ansehen und konkret die Textfelder auf der rechten Seite mit den richtigen Werten füllen, je nachdem, welcher Film in der ListBox ausgewählt wurde. Das funktioniert in WPF überraschend einfach, wie im Folgenden dargestellt wird. Die Anwendung soll im Hintergrund Film-Objekte bereitstellen, die alle für einen einzelnen Film nötigen Daten kapseln. In diesem einfachen Beispiel seien das fullName, shortName, producers, director, writers, year und number. Um eine Datenbindung zu einem Film-Objekt nutzen zu können, müssen all diese Eigenschaften als public property zur Verfügung gestellt werden, der nötige C#-Code sieht wie folgt aus: namespace VideoLibrary { class Film { private string fullName; public string FullName { get { return fullName; } set { fullName = value;} } //... restliche Properties (shortName, director...) } } Diese Daten sind zum Beispiel in einer Datenbank abgelegt, die Zugriffsfunkionalität darauf sei in einer Klasse DataStore abgelegt. Um sie nun mit der Oberfläche zu verknüpfen muss in der MainWindow-Klasse noch ein wenig Code geschrieben werden: public partial class MainWindow : Window { DataStore ds; List<Film> films; public MainWindow() { InitializeComponent(); ds = new DataStore(); films = ds.GetFilms(); listBoxFilms.ItemsSource = films; } } Das DataStore-Objekt liefert eine Liste von Filmen zurück, die der ListBox als Datenquelle zugewiesen wird. Als nächstes wird in XAML eine DataBinding-Verknüpfung festgelegt, um die Textfelder mit der jeweiligen Filmeigenschaft zu verknüpfen: <TextBox Name="textBoxTitleComplete" (...) Text="{Binding Path=FullName}" /> Anstatt also der Texteigenschaft einen statischen String zuzuweisen wird festgelegt, dass sie mit dem FullName-Property der aktuellen Datenquelle verbunden werden soll. Bleibt zu guter Letzt noch das Setzen der Datenquelle. Das ginge entweder über die Angabe einer Quelle im Binding-Ausdruck, oder aber über einen DataContext. Der große Vorteil des DataContext ist, dass Kindelemente (zum Beispiel in einem Grid), die selber keinen DataContext festlegen, automatisch im Kontext des Containers stehen. Kurz: durch setzen eines DataContext für das Grid, haben alle Textfelder die richtige Datenquelle: <Grid Name="gridMain" DataContext= "{Binding ElementName=listBoxFilms, Path=SelectedItem}" > (...) </Grid > Das war auch schon alles: wenn nun der Benutzer in der Liste einen Film auswählt, werden die Textfelder mit den richtigen Werten gefüllt. Und es geht noch mehr: alle Änderungen, die an den Texten vorgenommen werden, werden automatisch in die Filmliste im Hintergrund übernommen. Das einzige Problem ist noch, dass die Liste selbst nicht weiß, wie sie ein Film-Objekt darstellen soll, sie ruft deshalb einfach auf jedes einzelne Objekt die .ToString()-Methode auf, und zeigt jeweils nur VideoLibrary.Film an. Eine erste Abhilfe wäre es diese Methode für die Film-Klasse zu überschreiben. WPF bietet jedoch mit sogennanten Data Templates ein deutlich mächtigeres Werkzeug für diese Aufgabe. 5.2 Data Templates Mit Data Templates lässt sich für das einzelne Item einer Liste (oder auch eines TreeViews) ganz detailliert der Aufbau festlegen. Im Rahmen eines einfachen Beispiels soll dazu in der Liste von jedem Film der Kurzname und leicht eingerückt der Regisseur dargestellt werden, eingebettet in einen hübschen Rahmen. Das Template in XAML sieht wie folgt aus: <DataTemplate x:Key="FilmDataTemplate"> <Border CornerRadius="4" BorderThickness="1" Margin="3"> <Grid Margin="2"> (...Grid.Row Definitionen...) <TextBlock Grid.Row="0" FontWeight="Bold" Text="{Binding Path=ShortName}"/> <TextBlock Grid.Row="1" Text="{Binding Path=Directors}" Margin="4, 0, 0, 0" HorizontalAlignment="Left"/> </Grid> </Border> </DataTemplate> Es werden die zwei nötigen Textfelder mit Hilfe eines Grids in den Rahmen eingebettet. Die Text-Eigenschaft wird jeweils an die entsprechende Filmeigenschaft gebunden, nur so kann jedes Listenelement die richtigen Daten anzeigen. Abschließend muss der ListBox nun noch dieses Data Template zugewiesen werden: <ListBox Name="listBoxFilms" ItemTemplate="{StaticResource FilmDataTemplate}" HorizontalContentAlignment="Stretch" (...) /> Als Ergebnis erhalten wir nun also eine vollständige, editierbare Filmauflistung, wie in Abbildung 3 dargestellt. 6 Zusammenfassung Die im Rahmen dieser Arbeit vorgestellten Features sollen einen ersten Überblick darüber geben, welche Möglichkeiten WPF im Vergleich zu WinForms bietet und als Anregungung Abbildung 3: Filmsammlung mit überarbeiteter Liste zu eigenen Experimenten dienen. Eine halbwegs vollständige und tiefgehende Abhandlung von WPF ist an dieser Stelle nicht möglich und würde Bücher füllen. Hier verweise ich deshalb auf die angegebene Literatur (hauptsächlich [Mac97]) und Recherche im Internet und hoffe, dass ich zumindest ein wenig das Interesse an der Windows Presentation Foundation wecken konnte. 7 Literaturverzeichnis Literatur [Mac97] Matthew MacDonald. Pro WPF in C#2008. Apress, 1997. [Sam10] Jitendra Sampathirao. Logical Tree and Visual Tree in WPF. http://www.c-sharpcorner.com/UploadFile/jitendra1987/4046/ Default.aspx, Stand: 15.12. 2010. [Sch09] Holger Schwichtenberg. Microsofts Visual Studio 2010 Beta 2 - eine Querschau. http://www.heise.de/developer/artikel/ Microsofts-Visual-Studio-2010-Beta-2-eine-Querschau-868144. html, 12 2009. [WEB10a] WEBSITE. Resources Overview. http://msdn.microsoft.com/en-us/library/ms750613.aspx, Stand: 15.12. 2010. [WEB10b] WEBSITE. WPF Documentation Samples. http://code.msdn.microsoft. com/wpfsamples\#controlcustomization, Stand: 15.12. 2010.