QStyledItemDelegate 1: DateDelegate

  Stefan Draxlbauer   Lesezeit: 6 Minuten Auf Mastodon ansehen

Das Ziel in diesem Kapitel ist die Darstellung des Datums als "Tag.Monat.Jahr" bzw. "dd.MM.yyyy" und die Auswahl eines Tages über eine Kalender-Anzeige. Das sieht dann so aus:

qstyleditemdelegate 1: datedelegate

Hinweis: Seit dem letzten Artikel hat sich die Struktur des Models verändert, weil ich etwas vergessen hatte. Bitte also in diesen Artikel auch reinschauen.

Das Ziel in diesem Kapitel ist die Darstellung des Datums als "Tag.Monat.Jahr" bzw. "dd.MM.yyyy" und die Auswahl eines Tages über eine Kalender-Anzeige. Das sieht dann so aus:

Quellcode: https://codeberg.org/Lerothas/QStyledItemDelegate-Demo

Eine neue Delegate-Klasse

In QtCreator erstellen wir eine neue Klasse über

  1. Rechtsklick auf die "Source Files" oder "Header Files".
  2. "Add New..."
  3. "C/C++" -> "C++ Class" -> "Next"
  4. Class name: "DateDelegate" (oder was auch immer euch gefällt)
  5. Base Class: "" -> "QStyledItemDelegate" (die Grundlage für "alle" Delegates)
  6. Next

Hier noch mal graphisch:

Wir haben dann eine leere Klasse ohne Funktionen - abgesehen vom Konstruktor, den wir heute aber in Ruhe lassen wollen. Wir fangen an mit der paint()-Funktion.

Ich könnt euch auch den Quellcode einfach aus meinem Repository kopieren.

paint()

Header-Datei:

void paint(QPainter* painter, const QStyleOptionViewItem& option,
               const QModelIndex& index) const override;

Zur Erklärung:

  1. const override: QStyledItemDelegate bringt bereits eine const paint()-Funktion mit, diese überschreiben wir.
  2. void: keine Rückgabe
  3. paint: Name der Funktion
  4. QPainter* painter: Das ist die Klasse, die das Objekt malt. Über die Veränerung der Eigenschaften des QPainters werden wir später sowas wie Farbe oder Strichdicke ändern.
  5. QStyleOptionViewItem: darin steht sowas wie die Ausrichtung des Textes etc.
  6. QModelIndex: Der Index, dessen Daten dargestellt werden. Kommt vom Model und wird durch den jeweiligen View an den Delegate weiter gegeben.

Nichts davon müssen wir irgendwie verändern, das macht alles das View (QTableView) für uns.

Source-Datei

void DateDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QRect rect = option.rect;
    rect.adjust( 5, 5, 0, 0 );
    painter->drawText( rect, index.data(0).toDate().toString("dd.MM.yyyy") );
}

Erklärung:

  1. QRect rect = option.rect : gibt uns das Rechteck in der Tabelle, die uns zur Verfügung steht. Es ist klug, in diesem Rechteck drinnen zu bleiben 😜
  2. rect.adjust( 5, 5, 0, 0 ) : wir rücken etwas nach innen, damit wir nicht direkt am Rand des Delegates schreiben. Dabei ist die Reihenfolge dx1, dy1, dx2 und dy2 für links, oben, rechts, unten.
  3. painter->drawText(...) : wir benutzen den QPainter, den wir vom QTableView (oder anderem View) erhalten, um Text zu schreiben.
  4. rect : dieser Text soll in rect geschrieben werden.
  5. index.data(0).toDate() : holt Daten aus dem gegebenen Index und wandelt ihn in einen QDate um. Die 0 steht dabei für die Daten. Für mehr siehe Qt::ItemDataRole.
  6. .toString(dd.MM.yyyy) : schreibt das Datum wie gewünscht aus.

Es ist etwas künstlich, das Datum zunächst anders zu schreiben und dann umgekehrt darstellen zu lassen. Aber für eine Sortierung der Daten ist es sehr hilfreich. Dafür werden wir das alles später aus einem Model holen, in dem es bspw. als Integer gespeichert ist. Für die Demo reicht uns der QString.

Das Datum wird jetzt korrekt dargestellt, aber wir haben noch keinen Editor. Würden wir jetzt doppel-klicken, könnten wir das Datum als Text bearbeiten, aber das ist wenig sinnvoll.

createEditor()

Header-Datei

QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

Wichtig:

  1. const override : wir sehen wieder, wir überschreiben eine existierende Funktion mit unserer eigenen.
  2. Es wird ein QWidget* zurückgegeben. D.h. wir müssen irgendein QWidget bzw. eine Unterklasse davon erstellen und zurückgeben. Dafür werden wir QDateEdit benutzen.

Der Rest ist entweder bekannt oder nicht wichtig, weil eh vom View gegeben.

Source-Datei

QWidget *DateDelegate::createEditor(QWidget *parent,
                                        const QStyleOptionViewItem &/* option */,
                                        const QModelIndex &/* index */) const
{
    QDateEdit *editor = new QDateEdit(parent); // 1
    editor->setDisplayFormat( "dd.MM.yyyy" ); // 2
    editor->setCalendarPopup( true ); // 3

    return editor; // 4
}
  1. Wir erstellen ein QDateEdit-Widget und geben parent mit, damit es den richtigen Bezug hat. Das parent ist das Eltern-Element im Grafikstack, nicht in der Klassen-Hierachie.
  2. Wir setzen die Darstellung auf dd.MM.yyyy analog zu paint().
  3. Wir setzen das Kalender-Popup, das den Kalender zur grafischen Auswahl eines Datum anzeigt, auf wahr.
  4. Wir geben den Editor als Ergebnis der Funktion zurück. Das Elternobjekt castet den jeweils passend nach QWidget.

Lassen wir das jetzt laufen, so wird ein leerer QDateEdit erstellt, denn wir greifen an keiner Stelle auf QModelIndex bzw. QModelIndex::data() zu. Das passiert in einer getrennten Funktion!

setEditorData()

Header-Datei

void setEditorData(QWidget *editor, const QModelIndex &index) const override;

Hier geht jetzt der vorher erstellte Editor als QWidget* ein. Mehr gibt es dazu nicht zu sagen.

Source-Datei

void DateDelegate::setEditorData(QWidget *editor,
                                     const QModelIndex &index) const
{
    QDate value = index.data(0).toDate(); // 1

    QDateEdit *dateEdit = static_cast< QDateEdit* >(editor); // 2
    dateEdit->setDate( value ); // 3
}
  1. Wir holen uns analog zu paint() das Datum als QDate aus dem Text des Indexes.
  2. Wir casten das editor-Widget zu einem QDateEdit. (Hier musste ich vor und nach QDateEdit ein Leerzeichen setzen, weil sonst Bludit das ausgeblendet hat o.O)
  3. Wir setzen das Datum.

setModelData()

Was noch fehlt: Wie kommen geänderte Daten jetzt wieder an das Model?

Header-Datei

void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;

Wir sehen hier neu: QAbstractItemModel. Das ist die Grund-Klasse für alle Models in Qt, interessiert uns für das Delegate aber nicht weiter. Hier könnte man natürlich auf eine bestimmte Art an Model einschränken, aber das lohnt sich zumindest in meinem Tutorial nicht.

Source-Datei

void DateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                    const QModelIndex &index) const
{
    QDateEdit *dateEdit = static_cast< QDateEdit* >(editor); // 1
    Date value = dateEdit->date(); // 2

    model->setData(index, value, Qt::EditRole); // 3
}
  1. Wir holen uns wieder das Editor-Widget und casten es in die Klasse QDateEdit.
  2. Wir holen uns das Datum als QDate über ->date().
  3. Wir setzen die Daten. Dabei greifen wir auf das Model zu, setzen die Daten des gegebenen Indexes auf den Wert value und zwar mit der Qt::EditRole, was das typische ist. (Qt::EditRole stimmt übrigens üblicherweise mit Qt::DisplayRole überein.)

Anstatt der EditRole könnte man auch das Design, das ToolTip,... ändern. Aber um die Dokumentation zu zitieren:

Qt::EditRole 2 The data in a form suitable for editing in an editor. (QString)

Also bleiben wir dabei.

Fazit

Wir haben einen einfachen Delegate gebaut, der ein Datum (gespeichert als QString) anzeigt und ein QDateEdit mit Kalender-Popup als Editor hat.

Quellcode

datedelegate.h

#ifndef DATEDELEGATE_H
#define DATEDELEGATE_H

#include 

class DateDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    DateDelegate();

    void paint(QPainter* painter, const QStyleOptionViewItem& option,
               const QModelIndex& index) const override;

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;

    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;
};

#endif // DATEDELEGATE_H

datedelegate.cpp

#include "datedelegate.h"

#include 
#include 
#include 

DateDelegate::DateDelegate() {}

void DateDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{
    QRect rect = option.rect;
    rect.adjust( 5, 5, 0, 0 );
    painter->drawText( rect, index.data(0).toDate().toString("dd.MM.yyyy") );
}

QWidget *DateDelegate::createEditor(QWidget *parent,
                                        const QStyleOptionViewItem &/* option */,
                                        const QModelIndex &/* index */) const
{
    QDateEdit *editor = new QDateEdit(parent);
    editor->setDisplayFormat( "dd.MM.yyyy" );
    editor->setCalendarPopup( true );

    return editor;
}

void DateDelegate::setEditorData(QWidget *editor,
                                     const QModelIndex &index) const
{
    QDate value = index.data(0).toDate();

    QDateEdit *dateEdit = static_cast< QDateEdit* >(editor);
    dateEdit->setDate( value );
}

void DateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                    const QModelIndex &index) const
{
    QDateEdit *dateEdit = static_cast< QDateEdit* >(editor);
    QDate value = dateEdit->date();

    model->setData(index, value, Qt::EditRole);
}

Tags

Qt6, C++

Es wurden noch keine Kommentare verfasst, sei der erste!