Das DataGrid
-Element in WPF bietet bereits einige vordefinierte Spaltentypen an. Es finden sich die folgenden Typen:
- DataGridCheckBoxColumn
- DataGridComboBoxColumn
- DataGridHyperlinkColumn
- DataGridTextColumn
Dieses Blog bietet viele weitere Artikel, Tipps und Tricks zum Thema Windows Presentation Foundation (WPF).
Wem diese Typen nicht reichen, der kann sich unter Verwendung des Typs DataGridTemplateColumn
seine Spalte via Vorlagen seinen Wünschen entsprechend anpassen. Zum Anzeigen von Datumswerten in einem DataGrid
könnte nun eine Vorlage so definiert werden:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Datum">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyDate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Analog dazu könnte für den Bearbeitungsmodus die Eigenschaft CellEditingTemplate
verwendet werden, um dafür einen DatePicker
zur Verfügung zu stellen.
Sollen nun in einer (oder gar mehreren Tabellen) zahlreiche Spalten desselben Typs, allerdings mit unterschiedlichen Bindings dargestellt werden, dann würde dies bedeuten, dass für jeden einzelnen Fall eigene Vorlagen zu erstellen sind. Dies ist sowohl für die Erstellung aufwändig, als auch für die weitere Pflege. Daher bietet es sich an, einen eigenen Spaltentyp zu definieren.
Ausgangspunkt dabei ist die abstrakte Klasse DataGridBoundColumn
. Diese bietet bereits die notwendige Funktionalität für das Data Binding an und muss nur mehr dafür sorgen, dass dieses auf eigens gesetzte Elemente angewandt wird. Nachfolgend ist die Implementierung einer Klasse DataGridDateColumn
zu finden. Diese zeigt einen gebundenen Datumswert in einem TextBlock
-Element an. Wird der Bearbeitungsmodus gestartet, wird ein DatePicker
-Element verwendet.
public class DataGridDateColumn : DataGridBoundColumn
{
private static Style _defaultEditingElementStyle;
private static Style _defaultElementStyle;
static DataGridDateColumn()
{
DataGridBoundColumn.ElementStyleProperty.OverrideMetadata(typeof(DataGridDateColumn), new FrameworkPropertyMetadata(DefaultElementStyle));
DataGridBoundColumn.EditingElementStyleProperty.OverrideMetadata(typeof(DataGridDateColumn), new FrameworkPropertyMetadata(DefaultEditingElementStyle));
}
protected override System.Windows.FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
DatePicker dp = new DatePicker();
this.ApplyStyle(true, false, dp);
this.ApplyBinding(dp, DatePicker.SelectedDateProperty);
return dp;
}
private void ApplyStyle(bool isEditing, bool defaultToElementStyle, FrameworkElement element)
{
Style style = this.PickStyle(isEditing, defaultToElementStyle);
if (style != null)
{
element.Style = style;
}
}
private Style PickStyle(bool isEditing, bool defaultToElementStyle)
{
Style elementStyle = isEditing ? this.EditingElementStyle : this.ElementStyle;
if ((isEditing && defaultToElementStyle) && (elementStyle == null))
{
elementStyle = this.ElementStyle;
}
return elementStyle;
}
private void ApplyBinding(DependencyObject target, DependencyProperty property)
{
BindingBase binding = this.Binding;
if (binding != null)
{
BindingOperations.SetBinding(target, property, binding);
}
else
{
BindingOperations.ClearBinding(target, property);
}
}
protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
TextBlock tb = new TextBlock();
this.ApplyStyle(false, false, tb);
this.ApplyBinding(tb, TextBlock.TextProperty);
return tb;
}
public static Style DefaultEditingElementStyle
{
get
{
if (_defaultEditingElementStyle == null)
{
Style style = new Style(typeof(DatePicker));
style.Setters.Add(new Setter(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center));
style.Setters.Add(new Setter(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Top));
style.Seal();
_defaultEditingElementStyle = style;
}
return _defaultEditingElementStyle;
}
}
public static Style DefaultElementStyle
{
get
{
if (_defaultElementStyle == null)
{
Style style = new Style(typeof(TextBlock));
style.Setters.Add(new Setter(FrameworkElement.MarginProperty, new Thickness(2.0, 0.0, 2.0, 0.0)));
style.Seal();
_defaultElementStyle = style;
}
return _defaultElementStyle;
}
}
}
Die Einbindung geschieht in der gewohnten Form – es muss lediglich ein XML-Namespace für den Zugriff auf unseren CLR-Namespace erstellt werden:
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<local:DataGridDateColumn Binding="{Binding StartDate}"
Header="Startdatum"/>
<local:DataGridDateColumn Binding="{Binding EndDate}"
Header="Enddatum"/>
</DataGrid.Columns>
</DataGrid>
Hier nun das Ergebnis:
Das Beispiel ist natürlich wieder wie gewohnt im Anschluss verfügbar.
Hallo Norbert.
Super Beispiel, nur habe ich das Problem, dass die Methode GenerateElement extrem oft aufgerufen wird, wenn ich mit vielen Daten arbeite (5000 Datensätze, 100 Spalten).
Das DataGrid ist ziemlich langsam. Liegt es an der eigenen Spalte oder am DataGrid selbst?
Freundliche Grüsse,
LuWa
Hallo,
schwer zu sagen, da ich deine eigene Spalte nicht kenne. Da du aber doch einige Datensätze und vor allem sehr viele Spalten hast, wundern mich Performanceprobleme nicht. Eventuell lassen sich die Daten ja anders abbilden.
Norbert