Das Model-View-System
Die Verwaltung und Darstellung von Daten in Qt kann einfach, aber mächtig in einem Model-View-System passieren. Dabei verwaltet das Model die Daten, während das View-Widget die Daten darstellt - dazwischen vermittelt ein Delegate.
Natürlich kann man mittels QAbstractItemModel selbst Models bauen, aber man kann auch fertige benutzen. Qt bringt mit:
- QStringListModel, das QStrings also Textschnipsel speichert.
- QStandardItemModel, das mittels QStandardItem quasi jede Art von Daten aufnimmt und als Tabelle speichert.
- QFileSystemModel, das Dateien und Ordner in einem Dateisystem anzeigen kann (siehe Datei-Manager).
- QSqlQueryModel, QSqlTableModel, QSqlRelationalTableModel zur Verwaltung von SQL-Daten aus SQLite, PostgreSQL etc.
Dazu gibt es mit QSortFilterProxyModel ein Model zum schnellen Filtern und Sortieren der Daten, ohne das Model jedes Mal anpassen zu müssen. Dieses braucht ein anderes Model als Datenquelle. Mit Qt 6.10 kommt demnächst ein weiteres Model hinzu: QRangeModel, das für viele "Gruppen-Klassen" wie vector/QVector etc. schnell ein Model bauen kann.
Als Views stehen standardmäßig
- QListView - Listenansicht
- QTableView - Tabellenansicht
- QTreeView - Baumansicht
zur Verfügung. Natürlich kann man auch hier eigene Widgets schreiben.
Delegates
Das eigentlich spannende sind die Delegates, die die Daten konkret anzeigen und auch verändern können. Der "normale Delegate" zeigt vorhandene Daten einfach als Text an. Alle, die schon mal eine Tabellenkalkulations-Software verwendet haben, wissen, dass es praktisch sein kann, wenn sich bspw. Ausgaben rot färben, während Einnahmen grün werden oder in einem Datumsfeld ein Datums-Popup erscheint, damit man nicht überlegen muss, ob letzte Woche Donnerstag jetzt der 6. oder 7. war.
Dafür gibt es die Klasse QStyledItemDelegate, die man mehr oder weniger beliebig zurechtbiegen kann. Sie bietet folgende Kern-Funktionen, die wir verändern können:
- paint(...) bestimmt darüber, wie das Widget gezeichnet wird.
- createEditor(...) erzeugt einen Editor, der von einer Datumsauswahl, ein Drop-Down-Menü über eine Spinbox für Zahlen bis hin zu ganz eigenen Widgets fast alles sein kann. Hier kann man auch verhindern, dass Daten veränderbar sind, indem man den Editor NULL setzt.
- sizeHint(...) gibt dem View an, wie groß der Delegate ist.
- editorEvent(...) um zu verändern, wie mit dem Delegate interagiert wird - das ergibt erst im Laufe der Reihe Sinn.
Mit dazu gehören die Funktionen setEditorData(...) und setModelData(...), die Daten aus dem Model holen und in den Editor spielen oder zurück vom Editor in das Model schreiben.
Bei allem steht einem die volle Fülle an Qt- und C++-Funktionalität zur Verfügung.
Die Reihe
In den folgenden Artikeln gehe ich die einzelnen Funktionen von QStyledItemDelegate durch und was man damit interessantes machen kann. Mit dabei (nicht unbedingt in dieser Reihenfolge):
- DateDelegate
- SpinboxDelegate
- ComboboxDelegate
- ButtonDelegate
- RadioButtonDelegate
Der Code ist in folgendem Codeberg-Repository einsehbar:
https://codeberg.org/Lerothas/QStyledItemDelegate-Demo
Editor & Installation
Ich verwende QtCreator zur Programmierung und werde mich darauf beziehen. Das Programm ist aber nicht notwendig für diese Reihe. Es geht auch alles mit einem "normalen" Texteditor und clang oder g++.
DIe Installation ist in openSUSE am einfachsten, indem man das Qt6-Pattern installiert.
sudo zypper in -t pattern devel_qt6
Ähnlich geht es in anderen Distributionen, wobei man hier und da noch eine Konfiguration des Compilers etc. vornehmen muss. Wirklich notwendig für diese Anleitung ist eigentlich nur das qt6-base-Entwicklungs-Paket. Das heißt je nach Distribution qt6-base-devel
oder auch
qt6-base-dev
etc.. Dabei beziehe ich mich in der Anleitung auf die Version Qt 6.9, die beim Schreiben des Artikels aktuell ist.
Das verwendete Model
Für diese Demo verwende ich ein einfaches QStandardItemModel, das gefüllt ist mit
- Einfachem Text
- Datum
- Zahl
- Einer Kategorie
- Text für einen Button
- Zahl für Radio-Buttons
Der Quellcode dafür findet sich in der Klasse "MainWindow", da sie an sich nicht wichtig ist für die Delegates. Trotzdem möchte ich den Code kurz erklären.
Wir erstellen ein QStandardItemModel über den entsprechenden Konstruktur. Dieses Model enthält noch keine Daten, also weder Reihen noch Spalten. Da ich auf dieses Model von mehreren Quellen zugreifen will, deklariere ich es in der Header-Datei.
QStandardItemModel *model = new QStandardItemModel();
Das QStandardItemModel nimmt als Reihen (row) eine Liste an QStandardItems an: QList
Beispiele:
QList row;
// QStandardItem mit Text:
QStandardItem *text = new QStandardItem( "Einfacher Text" );
row.append( text ); // Anhängen an die Reihe
// QStandardItem mit einem Datum als Text
QStandardItem *datum = new QStandardItem();
datum->setData( QDate::currentDate(), Qt::DisplayRole );
row.append( datum );
// QStandardItem mit einer Komma-Zahl:
QStandardItem *zahl = new QStandardItem();
zahl->setData( 42.38, Qt::DisplayRole );
row.append( zahl );
// QStandardItem mit Text, der aber über eine ComboBox realisiert werden soll:
QStandardItem *combobox = new QStandardItem();
combobox->setData( "Item 1", Qt::DisplayRole );
row.append( combobox );
// QStandardItem, das später mit einem Button versehen sein soll:
QStandardItem *button = new QStandardItem();
button->setData( "Button", Qt::DisplayRole );
row.append( button );
// QStandardItem mit einem Integer, über den später ein jeweiliger Radiobutton ausgewählt wird:
QStandardItem *radiobuttons = new QStandardItem("Radiobuttons");
radiobuttons->setData( 0, Qt::DisplayRole );
row.append( radiobuttons );
// Einfügen der Reihe
model->appendRow( row );
Somit ist unser "Dummy-Model" fertig für die Delegates. Aktuell sieht der QTableView so aus:
In den folgenden Teilen werden wir die Spalten 2 bis 6 über Delegates anpassen.
Viel Spaß!