Nachdem wir im letzten Kapitel die Funktionen paint(), createEditor(), setEditorData() und setModelData() am Beispiel eines Datums kennen gelernt haben, machen wir das in diesem Kapitel mit Zahlen. Denn ein normaler ItemDelegate kann zwar Zahlen einwandfrei anzeigen, aber man kann "alles" eintragen, auch Text. Das wollen wir aber nicht, es soll bei Zahlen bleiben.
Dafür benutzen wir eine QSpinBox bzw. QDoubleSpinBox.
Wie im Kapitel 1 erstellen wir mit QCreator eine neue Klasse, die QStyledItemDelegate beerbt und nennen Sie ganz kreativ "SpinboxDelegate".
QSpinBox und QDoubleSpinBox
Bevor wir mit dem Code für den Delegate loslegen, schauen wir uns kurz an, was die beiden QWidgets können.
- minimum/setMinimum: minimale Zahl
- maximum/setMaximum: maximale Zahl
- value/setValue: setzt die Zahl
- prefix/setPrefix: Text vor die Zahl
- suffix/setSuffix: Text nach der Zahl (bspw. %, t,...)
- singleStep/setSingleStep: Schrittweite über die Pfeiltasten bzw. beim Scrollen
Für QDoubleSpinBox gibt es noch:
- decimals/setDeximals: Anzahl an Nachkommastellen, die angezeigt und gesetzt werden. Bspw. gibt eine QDoubleSpinBox mit 2 Dezimalstellen bei Eingabe von 2,555 den Wert 2,56 zurück.
Ich möchte den (Double)SpinboxDelegate gerne so allgemein wie möglich benutzbar machen, d.h. ich werde alle diese Dinge nach außen erreichbar machen - abgesehen von value/setValue, das brauchen wir nur für das Model.
Der Konstruktor, private und Einstellungen
Der Konstruktor bleibt erst mal "leer", aber wir definieren ein paar private Variablen:
//spinboxdelegate.h
explicit SpinboxDelegate( QObject *parent = nullptr);
private:
int min=0, max=100, step=1; //1
QString prefix="", suffix=""; //2
- Minimum, Maximum und Schrittweite werden auf 0, 100 respektive 1 gesetzt.
- Prefix und Suffix bleiben leer.
// spinboxdelegate.cpp
SpinboxDelegate::SpinboxDelegate(QObject *parent)
: QStyledItemDelegate{parent}
{}
Damit wir die Eigenschaften jetzt setzen können, schreiben wir dafür winzige Funktionen, die die privaten Eigenschaften setzen.
// spinboxdelegate.h
void setMinimum( int _min ) {min=_min;};
void setMaximum( int _max ) {max=_max;};
void setSingleStep( int _step ) {step=_step;};
void setPrefix( QString _prefix ) {prefix=_prefix;};
void setSuffix( QString _suffix ) {suffix=_suffix;};
Diese nehmen den jeweiligen Wert (int oder QString) und schreiben ihn in die Variablen. Abrufbar von außen müssen die Variablen nicht sein, da sie nur intern zur Anzeige bzw. Bearbeitung dienen, aber im Model nicht abgespeichert werden. Üblicherweise ist das ja eine SQL-Datenbank oder so, die kann mit diesen Informationen sowieso erst mal nichts anfangen. Wichtig ist aber, dass sie gesetzt werden können.
paint()
Die Header-Datei unterscheidet sich nicht von der beim DateDelegate:
// spinboxdelegate.h
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
Erst die Source-Datei wird spannend:
// spinboxdelegate.cpp
void SpinboxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{
QRect rect = option.rect; // 1
int pointSize = option.font.pointSize(); // 2
rect.adjust( 0, 5, -5, 0 ); // 3
painter->drawText( rect, suffix, QTextOption(Qt::AlignRight) ); // 4
rect = rect.marginsRemoved( QMargins( 0,0,suffix.size()*pointSize, 0 ) ); // 5
painter->drawText( rect, index.data(0).toString(), QTextOption(Qt::AlignRight) ); // 6
rect = rect.marginsRemoved( QMargins( 0, 0, index.data(0).toString().size()*pointSize, 0 ) ); // 7
painter->drawText( rect, prefix, QTextOption(Qt::AlignRight) ); // 8
}
- Das kennen wir bereits: wir holen uns das Rechteck, das wir vom View bekommen, damit wir darin "malen" können.
- Speichern der Schriftgröße
- Verschieben des Rechtecks etwas, damit es schöner aussieht.
- Schreibe Suffix in das Rechteck rechtsbündig.
- Verkleinere das Rechteck von rechts um die "Größe" des Suffix. Das ist nicht schön geschätzt, aber ich habe es nicht besser hinbekommen, da leider die meisten Schriftgrößen nicht mono sind, also eine einheitliche Zeichenbreite haben. So ist es immer hin alles schön "geordnet".
- Schreiben der Zahl als Text, ebenfalls rechtsbündig.
- Verkleinern des Rechtecks.
- Schreiben des Prefix.
Ziel ist es, dass die Zahlen rechtsbündig angezeigt werden.
createEditor()
// spinboxdelegate.cpp
QWidget *SpinboxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
QSpinBox *editor = new QSpinBox(parent); // 1
editor->setFrame(false); // 2
editor->setMinimum( min ); // 3
editor->setMaximum( max ); // 4
editor->setPrefix( prefix ); // 5
editor->setSuffix( suffix ); // 6
editor->setSingleStep( step ); // 7
return editor; // 8
}
- Erstellt ein QSpinBox-Widget.
- Setzt keinen Rahmen. Das passt besser in die Tabelle, denn die hat (aktuell) ja selbst ein Grid.
- Setzt das Minimum der QSpinBox auf den vorher festgelegten Wert.
- Setzt das Maximum der QSpinBox auf den vorher festgelegten Wert.
- Setzt ein Prefix.
- Setzt ein Suffix.
- Setzt die Schrittweite für die Pfeiltasten und die Änderung per Scrollrad. Per Eingabe lässt sich jeder Integer-Wert zwischen min und max eintippen.
- Gibt das QSpinBox-Widget als Editor zurück.
setEditorData()
// spinboxdelegate.cpp
void SpinboxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt(); // 1
QSpinBox *spinBox = static_cast(editor); // 2
spinBox->setValue(value); // 3
}
- Holt den Wert vom Index und speichert ihn als Integer.
- Castet den Editor zu einer QSpinBox.
- Setzt den Wert der QSpinBox.
Man kann Schritt 1 und 3 auch zusammen nehmen. Hier habe ich das der Verständlichkeit halber nicht getan. Das sähe dann so aus:
spinbox->setValue(index.model()->data(index, Qt::EditRole).toInt());
setModelData()
// spinboxdelegate.cpp
void SpinboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast(editor); // 1
int value = spinBox->value(); // 2
model->setData(index, value, Qt::EditRole); // 3
}
- Castet den Editor zu QSpinBox.
- Hilt den Wert der QSpinBox.
- Setzt den Wert als Wert des Indexes im Model.
Auch hier kann man 2 und 3 zusammenfassen:
model->setData(index, spinBox->value(), Qt::EditRole);
QDoubleSpinBox
Analog zur QSpinBox für Integer-Werte kann man auch eine QDoubleSpinBox benutzen, die double- bzw. float-Werte verträgt. Für den genauen Code schaut ihr einfach ins Repository. Letztlich ergeben sich folgende Änderungen:
- QSpinBox ersetzen durch QDoubleSpinBox.
- Alle Vorkommnisse von "int" mit "double" ersetzen.
- Der QDoubleSpinBox eine Funktion geben, die die Anzahl an Decimalstellen ändert (1) bzw. beim Erstellen der QDoubleSpinBox innerhalb von createEditor() weitergibt (2).
void setDecimals( int _decimals ) {decimals=_decimals;}; // 1
(...)
editor->setDecimals( decimals ); // 2
Das sieht dann so aus:
Fazit
Wie ihr seht, ist das Erstellen von weiteren Delegates garnicht so schwer, wenn man mal einen erstellt hat. Fragen und Anmerkungen bitte in die Kommentare.