DataGrid

In WPF 4.0 wurde das DataGrid Steuerelement als Standardsteuerelement hinzugefügt. Mit diesem Steuerelement lassen sich Datensätze in einem Raster darstellen und bearbeiten. Das DataGrid ist sehr umfangreich und mächtig. Im nachfolgenden wird lediglich der grundlegende Einsatz demonstriert.

Zur Demonstration wird als erstes eine Klasse Person erzeugt.

public class Person
{
   public int Id { get; set; }
   public string LastName { get; set; }

   public string FirstName { get; set; }
   public string Email { get; set; }


   public Person(int id, string lastName, string firstName, string email)
    {
      Id = id;
      LastName = lastName;
      FirstName = firstName;
      Email = email;
    }
}

Innerhalb des MainWindow-Konstruktors wird ein Property (Eigenschaft) zur Aufnahme der Liste mit Personen angelegt und DataContext wird auf die MainWindow-Instanz (this) gesetzt, da die Liste der Personen in MainWindow verfügbar ist. Es lassen sich aber auch andere Datenquellen verwenden.


public MainWindow()
{
   InitializeComponent();
   p = new List<Person>();
   p.Add(new Person(1, „Mustermann“, „Max“, „m.mustermann@mustermann.de“));
   p.Add(new Person(2, „Musterfrau“, „Siglinde“, „sigmufra@smf.de“));
   p.Add(new Person(3, „Doe“, „John“, „j.doe@jdoe.com“));

   DataContext = this;

}

Im nächsten Schritt wird in der MainWindow.xaml ein DataGrid hinzugefügt und die ItemsSource Property an p gebunden.

<Window x:Class=“DataGrid.MainWindow“
  xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8220;
  xmlns:x=“http://schemas.microsoft.com/winfx/2006/xaml&#8220;
  xmlns:d=“http://schemas.microsoft.com/expression/blend/2008&#8243;
  xmlns:mc=“http://schemas.openxmlformats.org/markup-compatibility/2006&#8243;
xmlns:local=“clr-namespace:DataGrid“
  mc:Ignorable=“d“
  Title=“MainWindow“ Height=“350″ Width=“525″>
  <Grid>
    <DataGrid ItemsSource=“{Binding p}“ />
  </Grid>
</Window>

Startet man das Programm, erhält man auch schon das nachfolgende Fenster. Die Spalten (Columns) werden vom DataGrid ohne weiteres Hinzutun zur Laufzeit (also dynamisch) erzeugt. Per Vorgabe ist das Property AutoGenerateColumns des DataGrid auf true gesetzt. Ändert man dieses Property auf false, erzeugt das DataGrid die Spalten nicht mehr automatisch.


Verzichtet man auf die automatische Erzeugung der Spalten durch das DataGrid , muss man sich selbst darum kümmern und die entsprechenden Definitionen mittels DataGrid.Columns in der XAML-Datei vornehmen.


<DataGrid ItemsSource=“{Binding p}“ AutoGenerateColumns=“False“>
  <DataGrid.Columns>
    <DataGridTextColumn Header=“Name“ Binding=“{Binding LastName}“ />
    <DataGridTextColumn Header=“Vorname“ Binding=“{Binding FirstName}“ />
    <DataGridTextColumn Header=“eMail“ Binding=“{Binding email}“ />
  </DataGrid.Columns>
</DataGrid>

Durch diese Vorgehensweise lassen sich z.B. die Spaltenbezeichnungen ändern oder nicht benötigte Spalten können ausgelassen werden. Außerdem werden die Columns des Grid im Designer bereits entsprechend der Definition dargestellt.

Bei Datentabellen mit vielen Columns würde es aber einen nicht unerheblichen Aufwand bedeuten, wenn man zwecks Änderung von Spaltenbezeichnungen oder Auslassen von Spalten alle benötigten Columns mit DataGrid.Columns definieren müsste. Eine alternative Möglichkeit bietet das Event AutoGeneratingColumn. Bei jedem automatischen Erzeugen einer Spalte wird dieser Event aufgerufen.


<DataGrid ItemsSource=“{Binding p}“
AutoGeneratingColumn=“DataGrid_AutoGeneratingColumn“>
</DataGrid>

Innerhalb des Eventhandlers lässt sich dann überprüfen, welche Spalte gerade erzeugt wurde. Wird Cancel auf true gesetzt, wird eine Spalte ausgelassen. Die Änderung der Spaltenüberschrift erfolgt über Column.Header.

private void DataGrid_AutoGeneratingColumn(
object sender,DataGridAutoGeneratingColumnEventArgs e)

      {

         switch (e.PropertyName) {
            case „Id“: e.Cancel = true; break;
            case „LastName“: e.Column.Header = „Name“; break;
            case „FirstName“: e.Column.Header = „Vorname“; break;
         }
      }

Das Grid-Steuerelement

Bei einem Grid handelt es sich um ein Steuerelement, mit dem sich Raster erzeugen lassen, dessen Bereich dynamisch ausgenutzt werden kann. Ändert sich die Größe des Grids, passen sich die Zellen automatisch entsprechend der Größenänderung an.

Mit dem nachfolgenden XAML-Code wird ein Grid-Steuerelement mit einem Raster von 4 Spalten und 3 Zeilen erzeugt. Wie man sieht, sind alle Zellen über das gesamte Grid gleichmäßig verteilt und damit gleich groß.

Fügt man nun eine weitere Spalte ein, werden die Zellen des Bereichs in der Breite entsprechend angepasst.

Um die automatische Größenveränderung in Aktion zu sehen, kann das Steuerelement im Browser betrachtet werden. Damit man die einzelnen Zellen auch sieht, setze ich das Attribut ShowGridLines für das Grid auf true

Nach dem Start der Anwendung präsentiert sich das Grid in meinem Browser wie in der nachfolgenden Darstellung gezeigt. Es umfasst 15 Zellen mit gleicher Größe.

Ändere ich nun die Größe meines Browserfensters, passt sich das Grid dynamisch an. Es sind weiterhin 15 gleich große Zellen zu sehen – jedoch sind die einzelnen Zellen jetzt kleiner.

Sofern Breite und Höhe des Grid nicht explizit definiert werden, erbt das Grid die Größe vom übergeordneten Element. Die Größe eines Grid kann mit Width und Height festgelegt werden.

Neben der Größe des Grid, lassen sich auch die Breite und Höhe der Spalten bzw. Zeilen festlegen. Bei der fixen Angabe in Pixeln verliert eine solche Zelle aber seine Dynamik, d.h. auch bei einer Größenänderung des Grid, behält die betreffende Zelle die festgelegte Größe bei.

In diesem Beispiel hat die erste Zeile eine Höhe von 20 Pixeln. Die restlichen Zeilen werden auf die verbleibende Höhe des Grids aufgeteilt. Da diese beiden Zeilen in der Größe dynamisch sind und bei beiden die Gewichtung 1 vorliegt (1*), sind auch beide gleichhoch. D.h. die Resthöhe wird durch 2 dividiert und somit ist jede Zeile der verbleibenden zwei Zeilen gleich hoch.

Gibt man jetzt beispielsweise der dritten Zeile eine Gewichtung von 3 (3*), wird die restlich verfügbare Höhe durch 4 dividiert – dann erhält die Zeile 2 eine Höhe von Resthöhe/4 und Zeile 3 eine Höhe von Resthöhe/4*3. D.h. bei einer Gesamthöhe des Grid von 300 Pixeln sind die Zeilenhöhen wie folgt:

  • Zeile 1: 20 Pixel     à Resthöhe = 300 – 20 = 280
  • Zeile 2: 70 Pixel     à 280/4 = 70
  • Zeile 3: 210 Pixel     à (280/4) *3 = 210

Unter Umständen möchte man aber für bestimmte Zellen eventuell eine Minimal- oder Maximalgröße angeben. Damit lassen sich dann auch dynamische Zellen realisieren, bei denen eine bestimmte Größe nicht unter- bzw. überschritten wird.

Um nun Elemente in den einzelnen Zellen des Rasters unterzubringen, müssen die Attached Properties Column und Row des Grid verwendet werden. In dem nachfolgenden Beispiel positioniere ich einen Button in der dritten Spalte von Zeile 3.

Wie man im XAML-Code oben sieht verwende ich sowohl für Column als auch Row den Wert 2 – das liegt ganz einfach daran, dass beim Raster sowohl für Zeilen als auch für Spalten mit 0 begonnen wird. Die erste Zelle links oben befindet sich demnach in der Spalte 0, Zeile 0.

Für die Schalter wurden keine Größen angegeben und daher erben diese die Größe der Zelle in der sie platziert wurden. Bei Größenänderung des Grid werden die Schalter automatisch in ihrer Größe angepasst.

Wird für den Schalter eine Größenangabe gemacht, behält dieser auch bei Größenänderungen des Rasters die eingestellte Größe bei.

Wird beispielsweise nur die Breite festgelegt, wird die Höhe wieder in Abhängigkeit der Rasterzelle bei Größenänderungen angepasst – lediglich die Breite bleibt dann unverändert.

Die im Grid enthaltene Layoutdynamik bietet ganz tolle Möglichkeiten. Das nachfolgende Beispiel soll dies demonstrieren.

In diesem Beispiel enthält eine Zelle des Rasters sogar ein weiteres Grid, bestehend aus 2 Zeilen und 2 Spalten.

Lässt man dieses Beispielprogramm laufen, sehen die roten Kreise und dir grünen Rechtecke ziemlich gleich aus (vorausgesetzt das Browserfenster hat die entsprechende Größe).

Bei Veränderung der Größe des Browserfensters erkennt man aber den Unterschied – bei den links angeordneten Elementen wird einfach abgeschnitten, wenn der Platz nicht ausreicht. Bei dem Polygon verhält es sich übrigens genauso. Wenn man sich die untere Abbildung anschaut könnte man glauben das Polygon wäre verkleinert – hier wurde aber auch einfach nur abgeschnitten. Schalter und Ellipse des 2×2 Grid passen sich wieder dynamisch an.

In einer Zelle können auch mehrere Elemente verwendet werden. In dem nachfolgenden Beispiel werden in Zelle 0,1 vier Rechtecke mit verschiedenen Farben definiert. Dabei wird über die Reihenfolge der Definitionen die Ausgabe festgelegt, d.h. die zuletzt definierten Elemente überlagern die vorherigen. Über Canvas.ZIndex kann dies beeinflusst werden. Wird beispielsweise für das gelbe Rechteck der ZIndex 1 verwendet, sind das schwarze und weiße Rechteck nicht mehr zu sehen, da das gelbe Rechteck darüber liegt.

Eine weitere Möglichkeit ist das Zusammenfassen von Spalten oder Zeilen durch die Attached Properties Grid.RowSpan und Grid.ColumnSpan. Damit lassen sich Zellen verbinden um entsprechende Ausgaben zu realisieren.

Im nachfolgenden der dazu notwendige XAML-Code. Die Definition der Rechtecke ist dabei etwas nach hinten verschoben, damit die Reihenfolge nicht über das ZIndex Attribut erfolgen muss.

Im Beispiel wurden lediglich 2 Zellen einer Zeile zusammengefasst. Eine Zusammenfassung kann sich aber auch über mehrere Spalten und Zeilen erstrecken.

Die Layoutmöglichkeiten des Grid bieten vielfältige Möglichkeiten zur Realisierung eigener Projekte. Es macht eine Menge Spaß mit dem Grid zu experimentieren und durch die Schachtelung von Elementen lässt sich einiges erreichen.

LinearGradientBrush – Lineare Farbverläufe mit XAML

Mit dem nachfolgenden XAML-Code wird ein einfacher diagonaler Farbverlauf – ausgehend von der oberen linken Ecke zur rechten unteren Ecke – gezeichnet. Dies ist das Standardverhalten eines LinearGradientBrush, wenn StartPoint und EndPoint nicht angegeben werden. In diesem Beispiel soll der Farbverlauf oben links mit der Farbe grün beginnen und unten rechts mit der Farbe rot beendet werden.

Die ausführlichere Schreibweise mit Angabe von StartPoint und EndPoint macht das Gleiche wie o.a. Beispielcode.


Entlang der Achse wird vom Punkt 0,0 zum Punkt 1,1 der Farbverlauf gezeichnet.

Während das Füllen eines Rechtecks mit einer bestimmten Farbe ganz einfach über Fill gelöst werden kann

<Rectangle Width=“200″ Height=“150″ Fill=“BlueViolet“ />

ist die Verwendung und Definition eines linearen Farbverlaufes doch etwas komplexer. Dazu wird innerhalb von <Rectangle.Fill> der lineare Farbverlauf definiert. Als erstes wird ein LinearGradientBrush – also ein Pinsel zum Zeichnen von linearen Farbverläufen – definiert. Über StartPoint und EndPoint wird dann festgelegt in welcher Richtung der Farbverlauf gezeichnet werden soll. Weiterhin ist anzugeben mit welcher Farbe begonnen und mit welcher beendet werden soll. Dies erfolgt über GradientStop. Es lassen sich auch durchaus mehr als zwei GradientStops verwenden. Ein einzelner GradientStop macht keinen Sinn, da dies einem normalen Füllen entsprechen würde. Welche Bedeutung Offset hat – dazu später mehr. An dieser Stelle reicht es lediglich zu wissen, dass die GradientStops erforderlich sind. Ich möchte mich jetzt erst mal auf StartPoint und EndPoint konzentrieren.

Einen Farbverlauf von links nach rechts erreicht man durch nachfolgenden Code


Hierbei wird lediglich der Endpunkt auf 1,0 (was der oberen rechten Ecke entspricht) geändert. Mit diesem Wissen bewaffnet ist es ein Einfaches einen Farbverlauf in einer gewünschten Richtung umzusetzen. Wenn sie sich die nachfolgende Grafik ansehen – was müssen sie als StartPoint und EndPoint angeben, um einen Farbverlauf von rechts oben nach links unten zu zeichnen?

Sofern sie als StartPoint 1,0 und EndPoint 0,1 angenommen haben, lagen sie richtig.


Als nächstes komme ich auf GradientStop zurück. Mit GradientStop wird über Color=“Green“ beispielsweise die zu verwendende Farbe grün eingestellt. Was hat es nun mit der Angabe Offset auf sich? Nun, mit dem Offset wird angegeben, ab welcher Stelle auf der Achse ein Farbwert gelten soll.

Für grün wurde der Offset 0.0 gewählt – das ist die Stelle mit dem türkisfarbenden Kreis und für rot der Offset 1.0 – dargestellt mit dem blauen Kreis. D.h. der Farbverlauf erfolgt auf der gesamten Achse von grün nach rot.

Jetzt möchte ich aber sicherstellen, dass nach 75% über die Achse, die Farbe rot gelten soll. Um dies umzusetzen, ändere ich den Offset für die Farbe rot auf 0.75. Wie unten zu sehen ist, verschiebt sich der blaue Kreis, der den Offset 0.75 kennzeichnet. An dieser Stelle gilt somit die Farbe rot.

Der Offset kann auch über den Endpunkt hinaus gesetzt werden. Damit ist die Farbe rot am Endpunkt 0,1 noch nicht erreicht.


Mit StartPoint und EndPoint lassen sich auch weitere Effekte erreichen und die Werte entsprechend der eigenen Wünsche angepasst werden.

Bereits weiter oben wurde schon angesprochen, dass sich auch mehrere GradientStops verwenden lassen. Im nächsten Beispiel möchte ich einen Farbverlauf mit mehreren Farben verwenden.


Damit erfolgt der lineare Farbverlauf von unten nach oben über die angegebenen Farben. Die Offsets legen fest, ab wo auf der Achse eine Farbe gelten soll.

Man sieht, dass man mit dem LinearGradientBrush auf sehr einfache Weise tolle Effekte hinbekommen kann. Neben den bisher gezeigten weichen Farbübergängen lassen sich auch direkte Farbübergänge realisieren. In dem nachfolgenden Beispiel wird eine deutsche Flagge über GradientStops definiert und von unten nach oben gezeichnet. Hierbei fällt auf, dass die Farbe rot zweimal als GradiantStop auftaucht. Dies ist notwendig, da die Farbe rot von Offset 0.33 bis Offset 0.66 gelten soll. Würde man das nicht so tun, wäre ein weicher Farbübergang das Ergebnis.



Mit SpreadMethod=“Repeat“ lassen sich Farbverläufe auch wiederholen.


Eine weitere Möglichkeit ist das Spiegeln von Farbverläufen. Dazu wird für SpreadMethod der Wert Reflect verwendet.


Man hat noch jede Menge weitere Möglichkeiten beim Erzeugen von linearen Farbverläufen. So lässt sich beispielsweise auch der Alpha-Kanal für Farben eines Farbverlaufs verwenden. Damit sind dann auch Farbverläufe mit Transparenz möglich.