Benutzerdefinierte Spalten für das WPF-DataGrid erstellen

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:

image

Das Beispiel ist natürlich wieder wie gewohnt im Anschluss verfügbar.

Download Beispiel

Veröffentlicht von Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.

Beteilige dich an der Unterhaltung

2 Kommentare

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

  1. 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

    1. 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

Cookie-Einstellungen
Auf dieser Website werden Cookie verwendet. Diese werden für den Betrieb der Website benötigt oder helfen uns dabei, die Website zu verbessern.
Alle Cookies zulassen
Auswahl speichern
Individuelle Einstellungen
Individuelle Einstellungen
Dies ist eine Übersicht aller Cookies, die auf der Website verwendet werden. Sie haben die Möglichkeit, individuelle Cookie-Einstellungen vorzunehmen. Geben Sie einzelnen Cookies oder ganzen Gruppen Ihre Einwilligung. Essentielle Cookies lassen sich nicht deaktivieren.
Speichern
Abbrechen
Essenziell (1)
Essenzielle Cookies werden für die grundlegende Funktionalität der Website benötigt.
Cookies anzeigen