25.4 Das Steuerelement »DataGrid«
Die Datenbindungsmöglichkeiten der WPF-Steuerelemente sind wirklich bemerkenswert. Ob ListBox, ComboBox, TreeView oder ListView, Sie können die Daten ganz nach Wunsch in einem ganz individuellen Layout präsentieren. Alle zu beschreiben, würde den Rahmen dieses Buches deutlich sprengen. Dennoch sollten wir uns ein Steuerelement noch einmal etwas genauer ansehen: das DataGrid. Dabei handelt es sich sicher zugleich um das Control mit den meisten Möglichkeiten.
Ein DataGrid zeigt die Daten einer beliebigen Datenliste in Zeilen und Spalten an. Sie können ein DataGrid mit dessen Eigenschaft ItemsSource an eine Datenquelle binden, die über DataContext beschrieben wird. Hier zunächst der C#-Code, der dem DataGrid die Products-Tabelle der Northwind-Datenbank übergibt:
gridProducts.DataContext = ds.Tables[0];
Nun kann das DataGrid an DataContext gebunden werden:
<DataGrid Name="gridProducts" ItemsSource="{Binding}" />
Eine direkte Übergabe der ADO.NET-DataTable an die Eigenschaft ItemsSource scheitert, weil die DataTable nicht in die Schnittstelle IEnumerable konvertiert werden kann. Mit diesem Code werden alle erforderlichen Spalten automatisch erzeugt, weil die Eigenschaft AutoGenerateColumns des DataGrids per Vorgabe auf true eingestellt ist.
Sie können Einfluss auf das Erstellen der Spalten nehmen, indem Sie das Ereignis AutoGeneratingColumn behandeln, das beim Erzeugen jeder Spalte ausgelöst wird.
<DataGrid Name="gridProducts" ItemsSource="{Binding}"
AutoGeneratingColumn="gridProducts_AutoGeneratingColumn" />
Listing 25.33 Registrieren des Ereignishandlers von »AutoGeneratingColumn«
Der EventArgs-Parameter des Events liefert in seinen Eigenschaften zahlreiche Informationen, um auf die Spaltendarstellung Einfluss auszuüben. So beschreibt die Eigenschaft Column beispielsweise die Referenz auf die neu hinzuzufügende Spalte oder PropertyName den Spaltenbezeichner. Sie können sehr einfach das Erstellen einer bestimmten Spalte unterbinden, wenn Sie die Eigenschaft Cancel des EventArgs-Parameters auf true setzen oder die Kopfzeile im DataGrid umbenennen, wie das folgende Listing zeigt:
private void gridProducts_AutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e) {
if (e.PropertyName == "ReorderLevel" ||
e.PropertyName == "Discontinued")
e.Cancel = true;
else if (e.PropertyName == "ProductName")
e.Column.Header = "Artikelname";
}
Listing 25.34 Beeinflussen der Spaltenanzeige mit dem Event »AutoGeneratingColumn«
Abbildung 25.7 zeigt das Resultat der Maßnahme aus Listing 25.34.
Abbildung 25.7 Änderung der Spalten im »DataGrid«
25.4.1 Elementare Eigenschaften des »DataGrid«
Es gibt zahlreiche Eigenschaften, mit denen sich das allgemeine Layout des Tabellenelements zur Laufzeit beeinflussen lässt. Die wichtigsten Eigenschaften können Sie der folgenden Tabelle entnehmen.
Eigenschaft | Beschreibung |
ColumnHeaderHeight |
Die Höhe der Zeile, in der die Spaltenbezeichner angezeigt werden. |
ColumnWidth |
Legt die Standardspaltenbreite fest. Diese Eigenschaft wird durch den Typ DataGridLength beschrieben, der mit seinen Eigenschaften zahlreiche Optionen beschreibt. Der Standard ist SizeToHeader. |
GridLinesVisibility |
Diese Eigenschaft legt fest, welche Linien zwischen den einzelnen Zellen angezeigt werden sollen. Mögliche Optionen beschreibt die Enumeration DataGridGridlines mit All, None, Vertical und Horizontal. |
HeadersVisibility |
Diese Eigenschaft legt die Sichtbarkeit der Zeilen- und Spaltenköpfe fest. |
HorizontalGridLinesBrush |
Legt den Brush fest, mit dem die Linien zwischen den Zeilen im DataGrid gezeichnet werden. |
RowBackGround bzw. AlternatingRowBackGround |
Legt fest, mit welchem Hintergrund jede Zeile und jede alternierende Zeile gezeichnet werden soll. |
RowHeight |
Legt die Höhe jeder Zeile fest. |
VerticalGridLinesBrush |
Legt den Brush fest, mit dem die Linien zwischen den Spalten im DataGrid gezeichnet werden. |
25.4.2 Spalten definieren
Mit der Standardeinstellung AutoGenerateColumns=true werden – abhängig vom dargestellten Datentyp in der Spalte – fast alle Spalten als TextBlock-Spalten erzeugt. Das ist nicht sehr flexibel und gestattet keinerlei Gestaltungsspielraum. Legen Sie jedoch AutoGenerateColumns auf false fest, haben Sie die Option, aus insgesamt fünf Spaltentypen denjenigen auszusuchen, der Ihren Wünschen am besten entspricht. In Tabelle 25.4 sind die erwähnten fünf Spaltentypen beschrieben.
Spaltentyp | Beschreibung |
Dieser Typ beschreibt DataGrid-Spalten, die zur Darstellung von Textinhalten dienen. Dazu dient für jede Zelle ein TextBlock-Element, das nur dann gegen TextBox ausgetauscht wird, wenn die Zelle editiert werden soll. |
|
In der Spalte wird in jeder Zelle eine CheckBox angezeigt. Dieser Typ wird auch automatisch eingesetzt, wenn die Spalte einen booleschen Wert beschreibt. |
|
In der Spalte wird ein anklickbarer Link angezeigt. |
|
Diese Spalte sieht zunächst so aus wie eine Spalte vom Typ DataGridTextColumn. Im Editiermodus wird daraus dann eine ComboBox. |
|
Diese Spalte stellt die flexibelste Variante dar. Sie ermöglicht mit einem DataTemplate eine freie Gestaltungsmöglichkeit. |
Wir wollen uns den Einsatz einiger der aufgeführten Spaltentypen an einem Beispiel ansehen. Auch dazu greifen wir auf eine Tabelle der Northwind-Datenbank zurück. Diesmal wird es aber nicht die Tabelle Products sein, weil sie in dieser Hinsicht zu wenig Potenzial bietet. Stattdessen wollen wir mit der Tabelle Orders arbeiten, die in Abbildung 25.8 mit der Einstellung AutoGenerateColumns=true angezeigt wird.
Im folgenden, schrittweisen Aufbau einer individuellen Anzeige wollen wir uns auf einige Spalten der Tabelle beschränken. Schwerpunktmäßig werden uns dabei die Spalten ShippedDate, CustomerID und ShipVia interessieren, deren Spaltentyp wir an allgemein übliche Anforderungen anpassen wollen. Dabei soll die Spalte ShippedDate vom Typ DataGridTemplateColumn sein, damit der Anwender im Editiermodus ein intuitiv besser zu handhabendes DatePicker-Element zur Änderung des Datums benutzen kann. Die Spalten für CustomerID und ShipVia sollen hingegen durch DataGridComboBox-Typen dargestellt werden.
Abbildung 25.8 Die Ausgabe der Tabelle »Orders«
Benutzerdefinierte Anpassung der Spaltentypen
Stellen Sie die Eigenschaft AutoGenerateColumn des DataGrid auf false ein, wird das Festlegen der anzuzeigenden Spalten im DataGrid Ihnen überlassen. Nun können Sie selbst explizit angeben, welche Spalten der Tabelle angezeigt werden sollen und von welchem Spaltentyp diese sein sollen. Alles, was Sie tun müssen, ist das Füllen der Eigenschaft DataGrid.Columns mit den entsprechenden Spalten. Das könnte beispielsweise wie im folgenden Listing gezeigt aussehen.
<Window ...
Title="Bestellungen" Height="350" Width="760">
<Grid>
<DataGrid Name="gridProducts" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Bestell-Nr."
Binding="{Binding OrderID}"/>
<DataGridTextColumn Header="Kunde"
Binding="{Binding CustomerID}"/>
<DataGridTextColumn Header="Lieferdatum"
Binding="{Binding ShippedDate}"/>
<DataGridTextColumn Header="Lieferant"
Binding="{Binding ShipVia}"/>
<DataGridTextColumn Header="Lieferadresse"
Binding="{Binding ShipAddress}"/>
<DataGridTextColumn Header="Stadt"
Binding="{Binding ShipCity}"/>
<DataGridTextColumn Header="Land"
Binding="{Binding ShipCountry}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Listing 25.35 Einfache benutzerdefinierte Spaltengenerierung
Das Listing ist sehr einfach gehalten, denn es wird für die gewünschten Spalten nur der Standardspaltentyp DataGridTextColumn verwendet. Jede Spalte des DataGrid wird an eine Spalte der Tabelle Orders mit Binding gebunden. Bemerkenswert ist im Grunde genommen nur, dass die Spaltenüberschrift im Tabellensteuerelement mit Header explizit angegeben wird.
Der Spaltentyp »DataGridTemplateColumn«
Zuerst widmen wir uns der Spalte im DataGrid, die an die Spalte ShippedDate der Tabelle gebunden wird. In der Spalte wird das Lieferdatum angezeigt. Das legt natürlich sofort nahe, im Editiermodus die Eingabe des Benutzers durch ein DatePicker-Element zu unterstützen. Da ein solches nicht direkt als Spaltentyp angegeben werden kann (siehe auch Tabelle 25.4), müssen wir den Weg über ein Objekt vom Typ DataGridTemplateColumn gehen.
Ein DataGridTemplateColumn hat zwei Eigenschaften, die es uns ermöglichen, zwischen dem normalen Ansichtsmodus und dem Editiermodus zu unterscheiden:
Innerhalb jeder der beiden genannten Eigenschaften kann mit einem DataTemplate-Objekt beschrieben werden, was innerhalb einer Zelle der betreffenden Spalte angezeigt werden soll. In der Normalansicht soll es ein TextBlock-Element sein, im Editiermodus, wie oben erwähnt, ein DatePicker-Element.
<DataGridTemplateColumn Header="Lieferdatum">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding ShippedDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ShippedDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Listing 25.36 Änderung der Spalte »ShippedDate«
Ersetzen Sie die alte Spaltendefinition durch die in Listing 25.36 gezeigte, sieht das Resultat schon ganz passabel aus, wenn wir in einer Zelle dieser Spalte durch Doppelklick in den Editiermodus wechseln. Ein Kalender öffnet sich und ermöglicht die einfache Auswahl eines neuen Datums. Störend wird aber zumindest im deutschen Sprachraum das amerikanische Anzeigeformat des Datums einschließlich der Zeitangabe sein. Das hat sich nicht gegenüber dem geändert, was bereits in Abbildung 25.8 zu sehen ist.
Um eine im deutschen Sprachraum übliche Datumsanzeige zu ermöglichen, müssen wir eine Converter-Klasse bereitstellen und die Methode Convert der Schnittstelle IValueConverter entsprechend implementieren. Dazu wird der Übergabewert an die Methode Convert in den Typ DateTime umgewandelt und darauf die Methode ToShortDateString aufgerufen. Berücksichtigen müssen wir dabei allerdings, dass der Inhalt des zu konvertierenden Zelleninhalts leer sein kann. Die zweite Methode des Interfaces, ConvertBack, spielt hier keine Rolle und muss daher nicht implementiert werden.
public class DateConverter : IValueConverter {
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture) {
if (value.ToString() == "") return "";
return ((DateTime)value).ToShortDateString();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) {
return DateTime.Parse(value.ToString());
}
}
Listing 25.37 Die »Converter«-Klasse
Um den Konverter benutzen zu können, legen wir im Resources-Abschnitt des Window-Elements ein Objekt vom Typ DateConverter an. Dabei dürfen wir nicht vergessen, den Namespace, in dem sich die Klasse befindet, als XML-Namespace bekannt zu geben. Dann bleibt nur noch, den Konverter dem TextBlock im DataTemplate der Eigenschaft DataGridTemplateColumn.CellTemplate anzugeben.
<Window ...
xmlns:local="clr-namespace:DataGridSample"
Title="Bestellungen" Height="350" Width="760">
<Window.Resources>
<local:DateConverter x:Key="dateConverter" />
</Window.Resources>
<Grid>
<DataGrid Name="gridProducts" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
[...]
<DataGridTemplateColumn Header="Lieferdatum">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding ShippedDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ShippedDate,
ConverterCulture=de-DE,
Converter={StaticResource dateConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
[...]
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Listing 25.38 Ergänzungen im XAML-Code
Der Spaltentyp »DataGridComboBoxColumn«
Einfacher als eine DataGridTemplateColumn ist das Bereitstellen einer DataGridComboBoxColumn. Wir wollen diesen Spaltentyp für die Spalten CustomerID und ShipVia einsetzen, um statt der für den Anwender uninteressanten Identifier den Namen des Kunden bzw. den des Lieferanten anzuzeigen. Um an die letztgenannten Informationen zu gelangen, ist es notwendig, die entsprechenden Tabellen aus der Datenbank abzufragen. Auch hierzu werden wieder ADO.NET-Klassen benutzt, obwohl man die Informationen leichter mit dem EDM (Entity Data Model) des Entity Frameworks erhalten würde. In der Praxis hat sich das Entity Framework aber bisher noch nicht so weit durchgesetzt, dass dessen Bevorzugung in diesem Beispiel gerechtfertigt wäre. Hier zunächst der C#-Code, um die drei notwendigen Tabellen Orders, Customers und Shippers in den lokalen Speicher zu laden.
public MainWindow() {
SqlConnection con = new SqlConnection(@"...");
DataSet ds = new DataSet();
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Orders", con);
da.Fill(ds, "Orders");
da = new SqlDataAdapter("SELECT CustomerID, " + "CompanyName FROM Customers", con);
da.Fill(ds, "Customers");
da = new SqlDataAdapter("SELECT ShipperID, CompanyName " + "FROM Shippers", con);
da.Fill(ds, "Shippers");
InitializeComponent();
gridProducts.DataContext = ds.Tables["Orders"];
customerColumn.ItemsSource = ds.Tables["Customers"].DefaultView;
shippersColumn.ItemsSource = ds.Tables["Shippers"].DefaultView;
}
Listing 25.39 Laden der Tabellen mit ADO.NET
Die Bezeichner customerColumn und shippersColumn sind die beiden DataGridComboBoxColumn-Objekte.
Es reicht, wenn wir uns an dieser Stelle nur der Spalte mit den Lieferanten widmen. Die Spalte der Kunden wird analog aufgebaut. Aus der Tabelle der Lieferanten (Shippers) werden die beiden Spalten ShipperId und CompanyName abgefragt. Die ComboBox hat die Aufgabe, statt der ID des Lieferanten im DataGrid dessen Firmennamen anzuzeigen. Dazu ist die Eigenschaft ItemsSource des Objekts an die Tabelle der Lieferanten im lokalen Speicher gebunden (siehe Listing 25.39).
Wir setzen nun die Eigenschaft DisplayMemberPath auf die Spalte der Tabelle, die in der ComboBox angezeigt werden soll, also CompanyName. Die Eigenschaft SelectedValuePath gibt die Spalte der gebundenen Tabelle an, die mit einer Spalte einer in Beziehung stehenden Tabelle steht. Hier ist es also ShipperID. SelectedValueBinding bindet sich schließlich an die in Beziehung stehende Tabelle (also Orders) und hier wiederum an die Spalte ShipVia.
<DataGridComboBoxColumn x:Name="shippersColumn" Header="Lieferung"
DisplayMemberPath="CompanyName"
SelectedValuePath="ShipperID"
SelectedValueBinding="{Binding ShipVia}" />
Listing 25.40 Das »DataGridComboBoxColumn«-Objekt der Spalte »ShipVia«
Sehr ähnlich ist auch die Definition der zweiten Spalte für die Kunden.
<DataGridComboBoxColumn x:Name="customerColumn" Header="Kunde"
DisplayMemberPath="CompanyName"
SelectedValuePath="CustomerID"
SelectedValueBinding="{Binding CustomerID}" />
Listing 25.41 Das »DataGridComboBoxColumn«-Objekt der Spalte »CustomerID«
Abbildung 25.9 Die Spalte »Lieferdatum« im Editiermodus
In beiden Spalten werden nun die Namen und nicht mehr die Identifier angezeigt. Wechselt der Anwender in den Editiermodus, werden in den ComboBoxen nunmehr alle Kunden bzw. Lieferanten angezeigt.
In Abbildung 25.9 ist das Ergebnis der benutzerdefinierten Spalten zu sehen. Die Spalte Lieferdatum befindet sich dabei im Editiermodus (weil dieser am spektakulärsten ist, also reine Effekthascherei ).
25.4.3 Details einer Zeile anzeigen
Das DataGrid unterstützt auch einen speziellen Bereich, der unterhalb der aktuell ausgewählten Zeile angezeigt werden kann und dazu dient, dem Anwender Details der Zeile zu präsentieren. In Abbildung 25.10 sehen Sie, was damit gemeint ist.
Abbildung 25.10 Zusätzliche Detailansicht der ausgewählten Datenzeile
Zur Darstellung dieses speziellen Bereichs dient die Eigenschaft RowDetailsTemplate des DataGrid-Elements. Innerhalb der Eigenschaft beschreibt ein DataTemplate den Aufbau des Anzeigebereichs. Zur Umsetzung des RowDetail-Bereichs aus Abbildung 25.10 wird innerhalb des DataTemplate zunächst ein Border-Element beschrieben, das seinerseits ein StackPanel in horizontaler Elementausrichtung enthält. TextBlock-Elemente zeigen die Daten an, die aus Bindungen bezogen werden.
<DataGrid>
[...]
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border Margin="5" Padding="3" BorderBrush="Blue"
BorderThickness="3" CornerRadius="5">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Black" FontSize="14"
Text="Lieferdetails: "/>
<TextBlock Foreground="Red" FontWeight="Bold" FontSize="14"
Text="{Binding ShipAddress}" />
<TextBlock Foreground="Red" FontSize="14" Text=", "/>
<TextBlock Foreground="Red" FontWeight="Bold" FontSize="14"
Text="{Binding ShipCity}"/>
<TextBlock Foreground="Red" FontSize="14" Text=", "/>
<TextBlock Foreground="Red" FontWeight="Bold" FontSize="14"
Text="{Binding ShipCountry}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Listing 25.42 Das »DataGrid.RowDetailsTemplate«-Objekt
Das Anzeigeverhalten kann mit der Eigenschaft RowDetailsVisibilityMode des DataGrid beeinflusst werden. Per Vorgabe ist diese Eigenschaft auf VisibleWhenSelected eingestellt. Das heißt, der Detailbereich wird für die aktuell selektierte Zeile im DataGrid angezeigt. Außerdem bietet sich die Einstellung Visible an, was bedeutet, dass sofort alle Details für alle Zeilen angezeigt werden. Die dritte Alternative Collapsed bewirkt, dass die Details nie angezeigt werden.
Das komplette Beispiel finden Sie auf der Buch-DVD unter ..\Kapitel 25\DataGridSample.
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.