WPF: Grundlagen
Windows Presentation Foundation (kurz WPF) ist eine Erweiterung des .NET-Frameworks, welches uns eine neue grafische Oberfläche bietet. WPF ist erst ab der Framework-Version 3.0 verfügbar. Bei der Erstellung einer Anwendung mit der WPF-Oberfläche müssen wir den Typ „WPF-Anwendung“ in Visual Studio auswählen.
Beim Starten einer leeren WPF-Applikation fällt uns kein großer Unterschied im Vergleich zu Windows Forms auf. Auch hier gibt es einen Fensterrahmen mit Titel und den Kontroll-Boxen. Dies liegt daran, da es sich hierbei um den Standardaufbau von Windows-Fenstern handelt. Doch wenn wir uns das Programm bzw. den Designer genauer anschauen fällt uns auf, dass sich vor allem das Designen unserer Applikation komplett geändert hat. Hier gibt es nicht wie bei Windows Forms Objekte, die im „Designer“ in Form von Steuerelementen auf das Formular gezogen und über das Eigenschaftsfenster modifiziert werden. Vielmehr wird bei WPF ein Texteditor für den sogenannten XAML-Code (Extensible Application Markup Language) angezeigt. Natürlich gibt es auch bei WPF einen Designer, ein Eigenschaftsfenster und den Werkzeugkasten.
Was unterscheidet sich noch von Windows Forms zu WPF? In WPF ist es bei einigen Steuerelementen möglich, diese zu verschachteln. Dafür wird das einteilige XML-Element in ein zweiteiliges abgeändert und weitere Elemente verschachtelt. Meist ist eine direkte Verschachtelung nicht möglich, hier muss ein Layout-Panel (dazu später mehr) „zwischengeschachtelt“ werden. Als Beispiel könnte z. B. das integrieren eines Bildes in einen Button genannt werden. Ein großes und starkes Leistungsmerkmal von Windows Presentation Foundation ist die grafische Darstellung. Im Vergleich zu WinForm werden WPF-Fenster und deren Steuerelemente mit Direct3D gerendert und somit von dem Grafikprozessor (GPU) bearbeitet.
Bei Windows Forms hatte zudem jedes Steuerelement, welches im Designer angelegt wurde, zwingend einen Namen. Über diesen (Variablen-)Namen konnte auf das Steuerelement über den Programmcode zugegriffen werden. Die Variablendeklaration wurde hier in der Form1.Designer.cs-Datei vorgenommen. In WPF geschieht dies in der .g.cs-Datei (dazu gleich mehr), welche durch Visual Studio generiert wird. Hierbei muss nicht jedes Steuerelement zwingend einen Namen besitzen. Natürlich ist zu beachten: Besitzt ein Steuerelement keinen Namen, so können wir über den Programmcode auch nicht auf das Steuerelement mit Hilfe des Variablennamens zugreifen.
Schauen wir uns als nächstes einmal die von Visual Studio angelegten Dateien an: App.xaml, App.xaml.cs, MainWindow.xaml und MainWindow.xaml.cs. Dabei stellt die .xaml-Datei immer die XML-Datei mit dem XAML-Code zur Verfügung. Die dazugehörige .xaml.cs-Datei ist dabei die sogenannte Code-Behind-Datei, welche den C#-Code der Klasse enthält. Hierbei ersetzt die App.xaml- und App.xaml.cs-Datei die von WinForm bekannte Programm.cs-Datei. In der App.xaml-Datei wird das Haupt-Formular durch das Attribut StartupUri des Elements Application festgelegt. Über das Attribut x:Class legen wir den Namensraum und die Klasse innerhalb einer XAML-Datei fest. x ist hierbei eine Referenz für den Namensraum für den dazugehörigen XML-Standard (siehe Attribut xmlns:x). In der MainWindow.xaml.cs-Datei können wir sehen, dass das Root-Element Window ist. Dies ist die Klasse des standardisierten WPF-Fensters. In C# ist es mit Hilfe von WPF möglich, sogenannte User-Controls zu erstellen. Dabei handelt es sich um eigene Steuerelemente. Bei einem solchen eigenen Steuerelement ist das Root-Element dann nicht Window, sondern UserControl (dazu später mehr). Wenn wir ein neues Fenster oder User-Control erstellen, erhalten wir dabei immer eine .xaml- und eine .xaml.cs-Datei. Innerhalb der MainWindow.xaml.cs-Datei können wir sehen, dass sich untergeordnet vom Root-Element das Element Grid befindet. Dabei handelt es sich um ein sogenanntes Layout-Panel, welches wir auch durch ein anderes Layout-Panel (z. B. StackPanel) ersetzen können. Dem Root-Element werden keine Steuerelemente direkt untergeordnet, sondern immer nur einem Layout-Panel. Der Inhalt der MainWindow.xaml.cs unterscheidet sich nicht viel von der aus Windows Form bekannten Form1.cs. Wenn Sie wissen wollen was hinter der InitializeComponent()-Funktion steckt, dann schauen Sie mal in den Ordner obj/Debug. Dort gibt es die Dateien App.g.cs und MainWindow.g.cs. Diese stellen den Zusammenhang zwischen der .xaml- und .xaml.cs-Datei her. Unter anderem befindet sich in der .g.cs-Datei auch die Deklaration von Variablen von Steuerelementen. So wie die von Windows Forms bekannten .Designer.cs-Dateien sollten die .g.cs-Dateien ebenfalls nicht geändert werden, da diese von Visual Studio generiert werden.
Die Klassen für Windows Presentation Foundation Anwendungen befinden sich in mehreren Namensräumen. Bei Windows Form Anwendungen wurde lediglich der Namensraum System.Windows.Forms benötigt. In WPF wurde eine Aufteilung in mehrere Namensräume vorgenommen: System.Windows (enthält grundlegende Klasse), System.Windows.Controls (enthält Steuerelemente), System.Windows.Data (enthält Klassen zur Daten-Bindung), System.Windows.Input (enthält Klassen für das Eingabesystem), System.Windows.Navigation (enthält Klassen für die Navigierungs-Funktionen), System.Windows.Media (enthält Grafik- und Medien-Klassen) und System.Windows.Shapes (enthält grafische Steuerelemente wie Rechtecke, Linien etc.). Daneben bindet Visual Studio standardmäßig noch den Namensraum System.Windows.Documents ein, welcher für uns jedoch nicht von größerer Bedeutung ist.
Natürlich sind die meisten Eigenschaften, Funktionen und Ereignisse in WPF ähnlich oder sogar identisch mit denen aus WinForm. Doch bevor wir uns auf die verschiedenen Steuerelemente stürzen, wollen wir uns noch ein paar Unterschiede anschauen. Die Basisklasse von so gut wie allen WPF-Steuerelementen ist Control. Alle Ereignisse, welche keine besonderen Event-Argumente besitzen, werden nicht mehr Event-Argumente der Klasse EventArgs, sondern der Klasse RoutedEventArgs übergeben. Die Eigenschaften, welche als Datentyp bool besitzen, wurden in WPF umbenannt (z. B. aus Enabled wird IsEnabled, aus Visible wird IsVisible). Die Eigenschaft Text, welche wir aus WinForm-Anwendungen kennen, ist oftmals unter den Namen Title oder Content zu finden. Die Content-Eigenschaft entspricht immer dem Inhalt des Steuerelements. An Stelle zur Verwendung der Eigenschaft kann auch das Element in ein mehrteiliges Element ersetzt werden, bei welchem der Text innerhalb der Tags notiert wird.
Ein weiterer sehr wichtiger Vorteil von WPF ist die Bindung von Daten und das Erstellen von Darstellungs-Templates (Vorlagen). Hiermit ist es z. B. möglich einer Auswahlliste nicht nur Text, sondern zusätzlich auch ein Bild anzuzeigen. Die Daten-Bindung mit WPF ist sehr komplex und umfangreich, weshalb wir es in ein eigenes Kapitel ausgelagert haben. Dort werden wir genauer auf dieses Thema eingehen.
Für welche grafische Oberfläche sollte ich mich als Programmierer also entscheiden? Sollte ich WPF gar nicht lernen, wenn ich von Windows Forms überzeugt bin? Sollte ich nur noch WPF einsetzen und auf Windows Forms komplett verzichten? Beide Konzepte haben ihre Stärken und man sollte stets beide grafischen Oberflächen kennen und anwenden können. Es muss vielmehr für jeden spezifischen Fall entschieden werden, welche Oberfläche verwendet werden soll. Ein großer Vorteil von WPF ist die Trennung zwischen Design und Programm. Des Weiteren sollte auch die Datenbindung (dazu mehr im nächsten Kapitel) und die grafische Aufbereitung als Vorteil von WPF angesehen werden. Ein Nachteil von WPF ist, dass es zum Teil noch nicht alle Steuerelemente von Windows Form gibt. Auch ist das Erstellen einer einfachen Applikation in Windows Forms einfacher und schneller umzusetzen als in WPF. WPF besitzt zudem keine eingebauten Dialoge (wie z. B. zum Öffnen oder Speichern einer Datei), diese müssen über den Namensraum Microsoft.Win32 bezogen werden (Klasse OpenFileDialog und SaveFileDialog). Die Dialoge müssen komplett durch den „eigenen“ Programmcode erstellt werden. Eine Platzierung des Steuerelements über den Designer ist nicht möglich.
Über die Klasse Window können wir einige Einstellungen für unser Fenster vornehmen. Die Eigenschaft ResizeMode bestimmt die Größenkonfiguration für das Fenster. Dafür werden Werte der Enumeration ResizeMode verwendet: NoResize (das Fenster kann nicht skaliert werden, Maximierungs- und Minimierungs-Button sind ausgeblendet), CanMinimize (das Fenster kann nicht skaliert werden, Maximierungs- und Minimierungs-Button sind eingeblendet, Maximierungs-Button ist ausgegraut), CanResize (das Fenster kann skaliert werden, Maximierungs- und Minimierungs-Button sind eingeblendet) und CanResizeWithGrip (das Fenster kann skaliert werden, Maximierungs- und Minimierungs-Button sind eingeblendet, ein „Skalierungs-Icon“ wird in der Ecke unten rechts angezeigt). Die Eigenschaft ShowInTaskbar gibt an, ob das Programm-Icon in der Taskleiste angezeigt werden soll. Title legt den Programmtitel fest, welcher u. a. in der Programmleiste angezeigt wird. Über die WindowState-Eigenschaft kann der aktuelle Status des Fensters abgefragt werden: Normal (Fenster geöffnet), Maximized (Fenster maximiert geöffnet) und Minimized (Fenster minimiert geöffnet). Wie auch bei der Form-Klasse, können wir über die Funktion Close() das Fenster schließen, mit Hide() das Fenster unsichtbar machen und mit Show() oder ShowDialog() ein Formular anzeigen.
Die Klasse Control ist, wie bereits oben genannt, die Basisklasse vieler Steuerelemente bzw. letztendlich aller Steuerelemente. Die Eigenschaft Background und Foreground legt die Hintergrund- und Vordergrund- bzw. Schriftfarbe fest oder ruft diese ab. BorderBrush ist eine Eigenschaft für die Farbe des Rahmens. BorderThickness legt hingegen die Breite des Rahmens fest. Bei den Farbangaben wird eine Farbe der Brush-Klasse verwendet (die statischen vordefinierten Farben befinden sich in der Brushes-Klasse). Height und Width legt die Höhe und Breite des Steuerelements fest. Eine Positionierung erfolgt je nach gewähltem Layout-Panel unterschiedlich. In den ersten Beispielen werden wir immer das Layout-Panel Grid verwenden. Hier erfolgt eine Ausrichtung z. B. über Abstände. Margin legt dabei den Außenabstand (vom Rahmen zum übergeordneten Element) fest. Mit der Eigenschaft Padding legen wir den Innenabstand (vom Rahmen zum Inhalt des Elementes) fest. Die HorizontalAlignment- und VerticalAlignment-Eigenschaft legt die Ausrichtung fest. In den Anfängen wollen wir immer eine Ausrichtung von der linken oberen Ecke ausführen, weshalb wir die Enumerations-Werte Left und Top benötigen. Wollen wir nicht das Steuerelement selbst, sondern den Inhalt des Steuerelementes positionieren, so gibt es die Eigenschaften HorizontalContentAlignment und VerticalContentAlignment.
MainWindow.xaml
<Window x:Class="CSV20.WPF_Grundlagen.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> </Window>
MainWindow.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace CSV20.WPF_Grundlagen { /// <summary> /// Interaktionslogik für MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } }
App.xaml
<Application x:Class="CSV20.WPF_Grundlagen.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>
App.xaml.cs
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; namespace CSV20.WPF_Grundlagen { /// <summary> /// Interaktionslogik für "App.xaml" /// </summary> public partial class App : Application { } }