Beispiel für einen Drilldown
Das Drill-Down-Beispiel zeigt, wie man mithilfe der Klassen „ QSqlRelationalTableModel “ und „ QDataWidgetMapper “ Daten aus einer Datenbank ausliest und Änderungen übermittelt.

Beim Ausführen der Beispielanwendung kann ein Benutzer Informationen zu jedem Element abrufen, indem er auf das entsprechende Bild klickt. Die Anwendung öffnet ein Informationsfenster, in dem die Daten angezeigt werden, und ermöglicht es dem Benutzer, sowohl die Beschreibung als auch das Bild zu ändern. Die Hauptansicht wird aktualisiert, sobald der Benutzer seine Änderungen übermittelt.
Das Beispiel besteht aus drei Klassen:
ImageItemist eine benutzerdefinierte Grafikobjektklasse, die zur Anzeige der Bilder verwendet wird.Viewist das Haupt-Anwendungs-Widget, mit dem der Benutzer die verschiedenen Elemente durchsuchen kann.InformationWindowzeigt die angeforderten Informationen an und ermöglicht es den Benutzern, diese zu ändern und ihre Änderungen an die Datenbank zu übermitteln.
Wir werden uns zunächst die Klasse „ InformationWindow “ ansehen, um zu erfahren, wie Sie Daten aus einer Datenbank lesen und ändern können. Anschließend werden wir das Haupt-Anwendungs-Widget, d. h. die Klasse „ View “, sowie die zugehörige Klasse „ ImageItem “ betrachten.
Definition der Klasse „InformationWindow“
Die Klasse „ InformationWindow “ ist ein benutzerdefiniertes Widget, das von „ QWidget “ erbt:
class InformationWindow : public QDialog
{
Q_OBJECT
public:
InformationWindow(int id, QSqlRelationalTableModel *items,
QWidget *parent = nullptr);
int id() const;
Q_SIGNALS:
void imageChanged(int id, const QString &fileName);Wenn wir ein Informationsfenster erstellen, übergeben wir dem Konstruktor die zugehörige Element-ID, einen Zeiger auf das Modell und ein übergeordnetes Element. Wir verwenden den Modellzeiger, um unser Fenster mit Daten zu füllen, während wir den Parameter „parent“ an die Basisklasse weitergeben. Die ID wird zur späteren Verwendung gespeichert.
Sobald ein Fenster erstellt ist, verwenden wir die öffentliche Funktion ` id() `, um es zu lokalisieren, sobald Informationen für den angegebenen Standort angefordert werden. Wir verwenden die ID außerdem, um das Haupt-Anwendungs-Widget zu aktualisieren, wenn die Benutzer ihre Änderungen an die Datenbank übermitteln, d. h., wir senden ein Signal aus, das die ID und den Dateinamen als Parameter enthält, sobald die Benutzer das zugehörige Bild ändern.
private Q_SLOTS:
void revert();
void submit();
void enableButtons(bool enable);Da wir den Benutzern erlauben, einige der Daten zu ändern, müssen wir Funktionen zum Rückgängigmachen und Übermitteln ihrer Änderungen bereitstellen. Der Slot ` enableButtons() ` dient der einfachen Handhabung, um die verschiedenen Schaltflächen bei Bedarf zu aktivieren und zu deaktivieren.
private:
void createButtons();
int itemId;
QString displayedImage;
QComboBox *imageFileEditor = nullptr;
QLabel *itemText = nullptr;
QTextEdit *descriptionEditor = nullptr;
QPushButton *closeButton = nullptr;
QPushButton *submitButton = nullptr;
QPushButton *revertButton = nullptr;
QDialogButtonBox *buttonBox = nullptr;
QDataWidgetMapper *mapper = nullptr;
};Die Funktion ` createButtons() ` ist ebenfalls eine Hilfsfunktion, die zur Vereinfachung des Konstruktors bereitgestellt wird. Wie oben erwähnt, speichern wir die Element-ID zur späteren Verwendung. Außerdem speichern wir den Namen der aktuell angezeigten Bilddatei, um feststellen zu können, wann das Signal ` imageChanged() ` ausgesendet werden soll.
Das Informationsfenster nutzt die Klasse „ QLabel “, um den Namen eines Elements anzuzeigen. Die zugehörige Bilddatei wird mithilfe einer „ QComboBox “-Instanz angezeigt, während die Beschreibung über „ QTextEdit “ dargestellt wird. Darüber hinaus verfügt das Fenster über drei Schaltflächen zur Steuerung des Datenflusses und zur Ein- bzw. Ausblendung des Fensters.
Abschließend deklarieren wir einen Mapper. Die Klasse „ QDataWidgetMapper “ sorgt für die Zuordnung zwischen einem Abschnitt eines Datenmodells und Widgets. Wir werden den Mapper verwenden, um Daten aus der angegebenen Datenbank zu extrahieren und die Datenbank zu aktualisieren, sobald der Benutzer die Daten ändert.
Implementierung der Klasse „InformationWindow“
Der Konstruktor nimmt drei Argumente entgegen: eine Element-ID, einen Datenbankzeiger und ein übergeordnetes Widget. Der Datenbankzeiger ist eigentlich ein Zeiger auf ein „ QSqlRelationalTableModel “-Objekt, das ein bearbeitbares Datenmodell (mit Unterstützung für Fremdschlüssel) für unsere Datenbanktabelle bereitstellt.
InformationWindow::InformationWindow(int id, QSqlRelationalTableModel *items,
QWidget *parent)
: QDialog(parent)
{
QLabel *itemLabel = new QLabel(tr("Item:"));
QLabel *descriptionLabel = new QLabel(tr("Description:"));
QLabel *imageFileLabel = new QLabel(tr("Image file:"));
createButtons();
itemText = new QLabel;
descriptionEditor = new QTextEdit;Zunächst erstellen wir die verschiedenen Widgets, die zur Anzeige der in der Datenbank enthaltenen Daten erforderlich sind. Die meisten Widgets werden auf einfache Weise erstellt. Beachten Sie jedoch die Combobox, die den Namen der Bilddatei anzeigt:
imageFileEditor = new QComboBox;
imageFileEditor->setModel(items->relationModel(1));
imageFileEditor->setModelColumn(items->relationModel(1)->fieldIndex("file"));In diesem Beispiel werden Informationen zu den Artikeln in einer Datenbanktabelle namens „items“ gespeichert. Beim Erstellen des Modells werden wir einen Fremdschlüssel verwenden, um eine Beziehung zwischen dieser Tabelle und einer zweiten Datenbanktabelle namens „images“ herzustellen, die die Namen der verfügbaren Bilddateien enthält. Wir werden bei der Betrachtung der Klasse „ View “ darauf zurückkommen, wie dies geschieht. Der Grund für die Erstellung einer solchen Beziehung ist jedoch, dass wir sicherstellen wollen, dass der Benutzer nur zwischen vordefinierten Bilddateien wählen kann.
Das der Datenbanktabelle „images“ entsprechende Modell ist über die Funktion ` relationModel()` von ` QSqlRelationalTableModel` verfügbar, die den Fremdschlüssel (in diesem Fall die Spaltennummer „imagefile“) als Argument benötigt. Wir verwenden die Funktion ` setModel()` von ` QComboBox`, damit die Combobox das Modell „images“ verwendet. Und da dieses Modell zwei Spalten („itemid“ und „file“) hat, legen wir mit der Funktion ` QComboBox::setModelColumn()` fest, welche Spalte sichtbar sein soll.
mapper = new QDataWidgetMapper(this);
mapper->setModel(items);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
mapper->setItemDelegate(new QSqlRelationalDelegate(mapper));
mapper->addMapping(imageFileEditor, 1);
mapper->addMapping(itemText, 2, "text");
mapper->addMapping(descriptionEditor, 3);
mapper->setCurrentIndex(id);Anschließend erstellen wir den Mapper. Die Klasse „ QDataWidgetMapper “ ermöglicht es uns, datenbezogene Widgets zu erstellen, indem wir sie Abschnitten eines Item-Modells zuordnen.
Die Funktion ` addMapping()` fügt eine Zuordnung zwischen dem angegebenen Widget und dem spezifizierten Abschnitt des Modells hinzu. Ist die Ausrichtung des Mappers horizontal (Standardeinstellung), handelt es sich bei dem Abschnitt um eine Spalte im Modell, andernfalls um eine Zeile. Wir rufen die Funktion ` setCurrentIndex()` auf, um die Widgets mit den Daten zu initialisieren, die der angegebenen Element-ID zugeordnet sind. Jedes Mal, wenn sich der aktuelle Index ändert, werden alle Widgets mit den Inhalten aus dem Modell aktualisiert.
Außerdem legen wir die Übermittlungsrichtlinie des Mappers auf „ QDataWidgetMapper::ManualSubmit “ fest. Das bedeutet, dass keine Daten an die Datenbank übermittelt werden, bis der Benutzer explizit eine Übermittlung anfordert (die Alternative ist „ QDataWidgetMapper::AutoSubmit “, bei der Änderungen automatisch übermittelt werden, wenn das entsprechende Widget den Fokus verliert). Schließlich legen wir den Item-Delegaten fest, den die Mapper-Ansicht für ihre Elemente verwenden soll. Die Klasse „ QSqlRelationalDelegate “ stellt einen Delegaten dar, der im Gegensatz zum Standard-Delegaten die Combobox-Funktionalität für Felder ermöglicht, die Fremdschlüssel in andere Tabellen sind (wie „imagefile“ in unserer Tabelle „items“).
connect(descriptionEditor, &QTextEdit::textChanged, this, [this]() { enableButtons(true); });
connect(imageFileEditor, &QComboBox::currentIndexChanged, this, [this]() { enableButtons(true); });
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(itemLabel, itemText);
formLayout->addRow(imageFileLabel, imageFileEditor);
formLayout->addRow(descriptionLabel, descriptionEditor);
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(formLayout);
layout->addWidget(buttonBox);
setLayout(layout);
itemId = id;
displayedImage = imageFileEditor->currentText();
setWindowFlags(Qt::Window);
enableButtons(false);
setWindowTitle(itemText->text());
}Schließlich verbinden wir die „Something’s changed“-Signale in den Editoren mit unserem benutzerdefinierten Slot „ enableButtons “, wodurch die Benutzer ihre Änderungen entweder übermitteln oder rückgängig machen können. Für die Verbindung mit dem Slot „ enableButtons “ müssen wir Lambdas verwenden, da dessen Signatur nicht mit „ QTextEdit::textChanged “ und „ QComboBox::currentIndexChanged “ übereinstimmt.
Wir fügen alle Widgets in ein Layout ein, speichern die Element-ID und den Namen der angezeigten Bilddatei zur späteren Verwendung und legen den Fenstertitel sowie die Anfangsgröße fest.
Beachten Sie, dass wir außerdem das Fensterflag „ Qt::Window “ setzen, um anzugeben, dass unser Widget tatsächlich ein Fenster ist, mit einem Fenstersystem-Rahmen und einer Titelleiste.
int InformationWindow::id() const
{
return itemId;
}Wenn ein Fenster erstellt wird, wird es erst gelöscht, wenn die Hauptanwendung beendet wird (d. h., wenn der Benutzer das Informationsfenster schließt, wird es lediglich ausgeblendet). Aus diesem Grund möchten wir nicht mehr als ein „ InformationWindow “-Objekt pro Element erstellen und stellen die öffentliche Funktion „ id() “ bereit, um feststellen zu können, ob für einen bestimmten Ort bereits ein Fenster existiert, wenn der Benutzer Informationen darüber anfordert.
void InformationWindow::revert()
{
mapper->revert();
enableButtons(false);
}Der Slot „ revert() “ wird immer dann ausgelöst, wenn der Benutzer auf die Schaltfläche „ Revert “ klickt.
Da wir die „ QDataWidgetMapper::ManualSubmit “-Submit-Richtlinie festgelegt haben, werden keine der Änderungen des Benutzers in das Modell zurückgeschrieben, es sei denn, der Benutzer entscheidet sich ausdrücklich dafür, alle Änderungen zu übermitteln. Dennoch können wir den Slot „ revert()“ von „ QDataWidgetMapper “ nutzen, um die Editor-Widgets zurückzusetzen und alle Widgets mit den aktuellen Daten des Modells neu zu füllen.
void InformationWindow::submit()
{
QString newImage(imageFileEditor->currentText());
if (displayedImage != newImage) {
displayedImage = newImage;
emit imageChanged(itemId, newImage);
}
mapper->submit();
mapper->setCurrentIndex(itemId);
enableButtons(false);
}Ebenso wird der Slot „ submit() “ immer dann ausgelöst, wenn der Benutzer beschließt, seine Änderungen durch Klicken auf die Schaltfläche „ Submit “ zu übermitteln.
Wir verwenden den Slot „ submit()“ von „ QDataWidgetMapper “, um alle Änderungen aus den zugeordneten Widgets an das Modell, d. h. an die Datenbank, zu übermitteln. Für jeden zugeordneten Abschnitt liest der Item-Delegate dann den aktuellen Wert aus dem Widget aus und setzt ihn im Modell. Schließlich wird die Funktion „ submit()“ des Modells aufgerufen, um dem Modell mitzuteilen, dass es alles, was es zwischengespeichert hat, an den permanenten Speicher übermitteln soll.
Beachten Sie, dass wir vor der Übermittlung von Daten anhand der zuvor gespeicherten Variablen ` displayedImage ` als Referenz prüfen, ob der Benutzer eine andere Bilddatei ausgewählt hat. Wenn sich der aktuelle und der gespeicherte Dateiname unterscheiden, speichern wir den neuen Dateinamen und senden das Signal ` imageChanged() `.
void InformationWindow::createButtons()
{
closeButton = new QPushButton(tr("&Close"));
revertButton = new QPushButton(tr("&Revert"));
submitButton = new QPushButton(tr("&Submit"));
closeButton->setDefault(true);
connect(closeButton, &QPushButton::clicked, this, &InformationWindow::close);
connect(revertButton, &QPushButton::clicked, this, &InformationWindow::revert);
connect(submitButton, &QPushButton::clicked, this, &InformationWindow::submit);Die Funktion „ createButtons() “ dient der Vereinfachung, d. h., sie soll den Konstruktor vereinfachen.
Wir legen die Schaltfläche „ Close “ als Standardschaltfläche fest, d. h. als die Schaltfläche, die gedrückt wird, wenn der Benutzer auf „ Enter “ klickt, und verbinden ihr Signal „ clicked()“ mit dem Slot „ close()“ des Widgets. Wie oben erwähnt, wird das Widget beim Schließen des Fensters lediglich ausgeblendet; es wird nicht gelöscht. Außerdem verbinden wir die Schaltflächen „ Submit “ und „ Revert “ mit den entsprechenden Slots „ submit() “ und „ revert() “.
buttonBox = new QDialogButtonBox(this);
buttonBox->addButton(submitButton, QDialogButtonBox::AcceptRole);
buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole);
buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole);
}Die Klasse „ QDialogButtonBox “ ist ein Widget, das Schaltflächen in einem Layout anzeigt, das dem aktuellen Widget-Stil entspricht. Dialoge wie unser Informationsfenster zeigen Schaltflächen in der Regel in einem Layout an, das den Interface-Richtlinien der jeweiligen Plattform entspricht. Unterschiedliche Plattformen weisen ausnahmslos unterschiedliche Layouts für ihre Dialoge auf. „ QDialogButtonBox “ ermöglicht es uns, Schaltflächen hinzuzufügen, wobei automatisch das für die Desktop-Umgebung des Benutzers geeignete Layout verwendet wird.
Die meisten Schaltflächen eines Dialogfelds erfüllen bestimmte Rollen. Wir weisen den Schaltflächen „ Submit “ und „ Revert “ die Rolle „ reset “ zu, d. h., wir geben an, dass durch das Drücken der Schaltfläche die Felder auf die Standardwerte zurückgesetzt werden (in unserem Fall die in der Datenbank enthaltenen Informationen). Die Rolle „ reject “ gibt an, dass ein Klick auf die Schaltfläche dazu führt, dass der Dialog abgelehnt wird. Da wir jedoch lediglich das Informationsfenster ausblenden, bleiben alle vom Benutzer vorgenommenen Änderungen erhalten, bis der Benutzer sie explizit rückgängig macht oder übermittelt.
void InformationWindow::enableButtons(bool enable)
{
revertButton->setEnabled(enable);
submitButton->setEnabled(enable);
}Der Slot „ enableButtons() “ wird aufgerufen, um die Schaltflächen zu aktivieren, sobald der Benutzer die angezeigten Daten ändert. Ebenso werden die Schaltflächen deaktiviert, wenn der Benutzer die Änderungen übermitteln möchte, um anzuzeigen, dass die aktuellen Daten in der Datenbank gespeichert werden.
Damit ist die Klasse „ InformationWindow “ fertiggestellt. Sehen wir uns nun an, wie wir sie in unserer Beispielanwendung verwendet haben.
Definition der View-Klasse
Die Klasse „ View “ repräsentiert das Hauptfenster der Anwendung und erbt von „ QGraphicsView “:
class View : public QGraphicsView
{
Q_OBJECT
public:
View(const QString &items, const QString &images, QWidget *parent = nullptr);
protected:
void mouseReleaseEvent(QMouseEvent *event) override;
private Q_SLOTS:
void updateImage(int id, const QString &fileName);Die Klasse „ QGraphicsView “ ist Teil des Graphics View Framework, das wir zur Anzeige der Bilder verwenden werden. Um auf Benutzerinteraktionen reagieren zu können, indem beim Klicken auf ein Bild das entsprechende Informationsfenster angezeigt wird, implementieren wir die Funktion „ mouseReleaseEvent()“ von „ QGraphicsView “ neu.
Beachten Sie, dass der Konstruktor die Namen zweier Datenbanktabellen erwartet: Eine enthält die detaillierten Informationen zu den Elementen, die andere die Namen der verfügbaren Bilddateien. Wir stellen außerdem einen privaten „ updateImage() “-Slot bereit, um das Signal „ imageChanged() “ von „ InformationWindow “ abzufangen, das immer dann ausgelöst wird, wenn der Benutzer ein mit dem Element verknüpftes Bild ändert.
private:
void addItems();
InformationWindow *findWindow(int id) const;
void showInformation(ImageItem *image);
QGraphicsScene *scene;
QList<InformationWindow *> informationWindows;Die Funktion ` addItems() ` ist eine Hilfsfunktion, die zur Vereinfachung des Konstruktors bereitgestellt wird. Sie wird nur einmal aufgerufen, um die verschiedenen Elemente zu erstellen und sie der Ansicht hinzuzufügen.
Die Funktion ` findWindow() ` hingegen wird häufig verwendet. Sie wird von der Funktion ` showInformation() ` aufgerufen, um festzustellen, ob für das angegebene Element bereits ein Fenster erstellt wurde (immer wenn wir ein ` InformationWindow `-Objekt erstellen, speichern wir einen Verweis darauf in der Liste ` informationWindows `). Letztere Funktion wird wiederum von unserer benutzerdefinierten Implementierung von ` mouseReleaseEvent() ` aufgerufen.
QSqlRelationalTableModel *itemTable;
};Abschließend deklarieren wir einen Zeiger vom Typ „ QSqlRelationalTableModel “. Wie bereits erwähnt, bietet die Klasse „ QSqlRelationalTableModel “ ein bearbeitbares Datenmodell mit Unterstützung für Fremdschlüssel. Bei der Verwendung der Klasse „ QSqlRelationalTableModel “ sollten Sie einige Punkte beachten: Die Tabelle muss über einen deklarierten Primärschlüssel verfügen, und dieser Schlüssel darf keine Verknüpfung zu einer anderen Tabelle enthalten, d. h., er darf kein Fremdschlüssel sein. Beachten Sie außerdem: Wenn eine relationale Tabelle Schlüssel enthält, die auf nicht vorhandene Zeilen in der referenzierten Tabelle verweisen, werden die Zeilen mit den ungültigen Schlüsseln über das Modell nicht bereitgestellt. Es liegt in der Verantwortung des Benutzers oder der Datenbank, die referenzielle Integrität aufrechtzuerhalten.
Implementierung der View-Klasse
Obwohl der Konstruktor die Namen sowohl der Tabelle mit den Bürodetails als auch der Tabelle mit den Namen der verfügbaren Bilddateien abfragt, müssen wir nur ein ` QSqlRelationalTableModel `-Objekt für die Tabelle „items“ erstellen:
View::View(const QString &items, const QString &images, QWidget *parent)
: QGraphicsView(parent)
{
itemTable = new QSqlRelationalTableModel(this);
itemTable->setTable(items);
itemTable->setRelation(1, QSqlRelation(images, "itemid", "file"));
itemTable->select();Der Grund dafür ist, dass wir, sobald wir über ein Modell mit den Artikeldetails verfügen, mithilfe der Funktion ` setRelation()` von ` QSqlRelationalTableModel` eine Beziehung zu den verfügbaren Bilddateien herstellen können. Diese Funktion erstellt einen Fremdschlüssel für die angegebene Modellspalte. Der Schlüssel wird durch das bereitgestellte „ QSqlRelation “-Objekt spezifiziert, das aus dem Namen der Tabelle, auf die sich der Schlüssel bezieht, dem Feld, auf das der Schlüssel abgebildet wird, und dem Feld, das dem Benutzer angezeigt werden soll, aufgebaut ist.
Beachten Sie, dass die Angabe der Tabelle lediglich festlegt, auf welche Tabelle das Modell angewendet wird, d. h., wir müssen die Funktion ` select()` des Modells explizit aufrufen, um unser Modell zu füllen.
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 465, 365);
setScene(scene);
addItems();
setMinimumSize(470, 370);
setMaximumSize(470, 370);
QLinearGradient gradient(QPointF(0, 0), QPointF(0, 370));
gradient.setColorAt(0, QColor("#868482"));
gradient.setColorAt(1, QColor("#5d5b59"));
setBackgroundBrush(gradient);
}Anschließend erstellen wir den Inhalt unserer Ansicht, d. h. die Szene und ihre Elemente. Die Beschriftungen sind reguläre „ QGraphicsTextItem “-Objekte, während die Bilder Instanzen der Klasse „ ImageItem “ sind, die von „ QGraphicsPixmapItem “ abgeleitet ist. Wir werden in Kürze darauf zurückkommen, wenn wir die Funktion „ addItems() “ näher betrachten.
Abschließend legen wir die Größenbeschränkungen und den Fenstertitel des Haupt-Anwendungs-Widgets fest.
void View::addItems()
{
int itemCount = itemTable->rowCount();
int imageOffset = 150;
int leftMargin = 70;
int topMargin = 40;
for (int i = 0; i < itemCount; i++) {
QSqlRecord record = itemTable->record(i);
int id = record.value("id").toInt();
QString file = record.value("file").toString();
QString item = record.value("itemtype").toString();
int columnOffset = ((i % 2) * 37);
int x = ((i % 2) * imageOffset) + leftMargin + columnOffset;
int y = ((i / 2) * imageOffset) + topMargin;
ImageItem *image = new ImageItem(id, QPixmap(":/" + file));
image->setData(0, i);
image->setPos(x, y);
scene->addItem(image);
QGraphicsTextItem *label = scene->addText(item);
label->setDefaultTextColor(QColor("#d7d6d5"));
QPointF labelOffset((120 - label->boundingRect().width()) / 2, 120.0);
label->setPos(QPointF(x, y) + labelOffset);
}
}Die Funktion ` addItems() ` wird nur einmal beim Erstellen des Hauptanwendungsfensters aufgerufen. Für jede Zeile in der Datenbanktabelle extrahieren wir zunächst den entsprechenden Datensatz mithilfe der Modellfunktion ` record()`. Die Klasse ` QSqlRecord ` kapselt sowohl die Funktionalität als auch die Eigenschaften eines Datenbankdatensatzes und unterstützt das Hinzufügen und Entfernen von Feldern sowie das Festlegen und Abrufen von Feldwerten. Die Funktion „ QSqlRecord::value()“ gibt den Wert des Feldes mit dem angegebenen Namen oder Index als „ QVariant “-Objekt zurück.
Für jeden Datensatz erstellen wir ein Beschriftungselement sowie ein Bildelement, berechnen deren Position und fügen sie der Szene hinzu. Die Bildelemente werden durch Instanzen der Klasse „ ImageItem “ dargestellt. Der Grund, warum wir eine benutzerdefinierte Elementklasse erstellen müssen, ist, dass wir die Hover-Ereignisse des Elements abfangen möchten, um das Element zu animieren, wenn der Mauszeiger über dem Bild schwebt (standardmäßig akzeptieren keine Elemente Hover-Ereignisse). Weitere Details finden Sie in der Dokumentation zum Graphics View Framework und in den Graphics View-Beispielen.
void View::mouseReleaseEvent(QMouseEvent *event)
{
if (QGraphicsItem *item = itemAt(event->position().toPoint())) {
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item))
showInformation(image);
}
QGraphicsView::mouseReleaseEvent(event);
}Wir implementieren den Ereignis-Handler ` mouseReleaseEvent()` von ` QGraphicsView` neu, um auf Benutzerinteraktionen zu reagieren. Wenn der Benutzer auf eines der Bildelemente klickt, ruft diese Funktion die private Funktion ` showInformation() ` auf, um das zugehörige Informationsfenster einzublenden.
Das Graphics View Framework stellt die Funktion ` qgraphicsitem_cast()` bereit, um festzustellen, ob die angegebene ` QGraphicsItem `-Instanz von einem bestimmten Typ ist. Beachten Sie, dass wir das Ereignis an die Implementierung der Basisklasse weiterleiten, wenn es sich auf keines unserer Bildelemente bezieht.
void View::showInformation(ImageItem *image)
{
int id = image->id();
if (id < 0 || id >= itemTable->rowCount())
return;
InformationWindow *window = findWindow(id);
if (!window) {
window = new InformationWindow(id, itemTable, this);
connect(window, &InformationWindow::imageChanged,
this, &View::updateImage);
window->move(pos() + QPoint(20, 40));
window->show();
informationWindows.append(window);
}
if (window->isVisible()) {
window->raise();
window->activateWindow();
} else
window->show();
}Die Funktion ` showInformation() ` erhält ein ` ImageItem `-Objekt als Argument und extrahiert zunächst die Element-ID des Elements.
Anschließend wird geprüft, ob für diesen Ort bereits ein Informationsfenster erstellt wurde. Falls für den angegebenen Ort noch kein Fenster existiert, erstellen wir eines, indem wir die Element-ID, einen Zeiger auf das Modell und unsere Ansicht als übergeordnetes Element an den Konstruktor von ` InformationWindow ` übergeben. Beachten Sie, dass wir das Signal „ imageChanged() “ des Informationsfensters mit dem Slot „ updateImage() “ dieses Widgets verbinden, bevor wir ihm eine geeignete Position zuweisen und es zur Liste der vorhandenen Fenster hinzufügen. Wenn bereits ein Fenster für den angegebenen Ort existiert und dieses Fenster sichtbar ist, wird sichergestellt, dass das Fenster an den Anfang des Widget-Stapels gebracht und aktiviert wird. Ist es ausgeblendet, führt der Aufruf seines Slots „ show()“ zum gleichen Ergebnis.
void View::updateImage(int id, const QString &fileName)
{
QList<QGraphicsItem *> items = scene->items();
while(!items.empty()) {
QGraphicsItem *item = items.takeFirst();
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) {
if (image->id() == id){
image->setPixmap(QPixmap(":/" +fileName));
image->adjust();
break;
}
}
}
}Der Slot „ updateImage() “ nimmt eine Element-ID und den Namen einer Bilddatei als Argumente entgegen. Er filtert die Bildelemente heraus und aktualisiert dasjenige, das der angegebenen Element-ID entspricht, mit der bereitgestellten Bilddatei.
InformationWindow *View::findWindow(int id) const
{
for (auto window : informationWindows) {
if (window && (window->id() == id))
return window;
}
return nullptr;
}Die Funktion „ findWindow() “ durchsucht einfach die Liste der vorhandenen Fenster und gibt einen Zeiger auf das Fenster zurück, das der angegebenen Element-ID entspricht, oder „ nullptr “, falls das Fenster nicht existiert.
Schauen wir uns zum Schluss noch kurz unsere benutzerdefinierte ` ImageItem `-Klasse an:
Definition der Klasse „ImageItem“
Die Klasse „ ImageItem “ dient dazu, die Animation der Bildelemente zu vereinfachen. Sie erbt von „ QGraphicsPixmapItem “ und implementiert deren Hover-Ereignisbehandler neu:
class ImageItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
public:
enum { Type = UserType + 1 };
ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent = nullptr);
int type() const override { return Type; }
void adjust();
int id() const;
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private Q_SLOTS:
void setFrame(int frame);
void updateItemPosition();
private:
QTimeLine timeLine;
int recordId;
double z;
};Wir deklarieren einen „ Type “-Enum-Wert für unser benutzerdefiniertes Element und implementieren „ type()“ neu. Dies geschieht, damit wir „ qgraphicsitem_cast()“ sicher verwenden können. Darüber hinaus implementieren wir eine öffentliche Funktion „ id() “, um die zugehörige Position identifizieren zu können, sowie eine öffentliche Funktion „ adjust() “, die aufgerufen werden kann, um sicherzustellen, dass das Bildelement unabhängig von der ursprünglichen Bilddatei die bevorzugte Größe erhält.
Die Animation wird mithilfe der Klasse „ QTimeLine “ zusammen mit den Ereignisbehandlungsroutinen und dem privaten Slot „ setFrame() “ implementiert: Das Bildelement wird vergrößert, wenn der Mauszeiger darüber fährt, und kehrt zu seiner ursprünglichen Größe zurück, sobald der Mauszeiger den Rand verlässt.
Schließlich speichern wir die Element-ID, mit der dieser bestimmte Datensatz verknüpft ist, sowie einen Z-Wert. Im Graphics View Framework bestimmt der Z-Wert eines Elements dessen Position im Elementstapel. Ein Element mit hohem Z-Wert wird über einem Element mit niedrigerem Z-Wert gezeichnet, wenn beide dasselbe übergeordnete Element haben. Außerdem stellen wir eine Funktion „ updateItemPosition() “ bereit, um die Ansicht bei Bedarf zu aktualisieren.
Implementierung der `ImageItem`-Klasse
Die Klasse „ ImageItem “ ist im Grunde nur eine „ QGraphicsPixmapItem “ mit einigen zusätzlichen Funktionen, d. h., wir können die meisten Argumente des Konstruktors (die Pixmap, das übergeordnete Element und die Szene) an den Konstruktor der Basisklasse weitergeben:
ImageItem::ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent)
: QGraphicsPixmapItem(pixmap, parent)
{
recordId = id;
setAcceptHoverEvents(true);
timeLine.setDuration(150);
timeLine.setFrameRange(0, 150);
connect(&timeLine, &QTimeLine::frameChanged, this, &ImageItem::setFrame);
connect(&timeLine, &QTimeLine::finished, this, &ImageItem::updateItemPosition);
adjust();
}Anschließend speichern wir die ID für spätere Verwendung und stellen sicher, dass unser Bildelement Hover-Ereignisse akzeptiert. Hover-Ereignisse werden ausgelöst, wenn kein Element den Mauszeiger gerade erfasst. Sie werden gesendet, wenn der Mauszeiger in ein Element eintritt, wenn er sich innerhalb des Elements bewegt und wenn der Mauszeiger ein Element verlässt. Wie bereits erwähnt, akzeptiert standardmäßig keines der Elemente des Graphics View Frameworks Hover-Ereignisse.
Die Klasse „ QTimeLine “ stellt eine Zeitleiste zur Steuerung von Animationen bereit. Ihre Eigenschaft „ duration “ enthält die Gesamtdauer der Zeitleiste in Millisekunden. Standardmäßig läuft die Zeitleiste einmal vom Anfang bis zum Ende durch. Die Funktion „ QTimeLine::setFrameRange()“ setzt den Frame-Zähler der Zeitleiste; während die Zeitleiste läuft, wird bei jedem Frame-Wechsel das Signal „ frameChanged()“ ausgelöst. Wir legen die Dauer und den Frame-Bereich für unsere Animation fest und verbinden die Signale „ frameChanged()“ und „ finished()“ der Zeitleiste mit unseren privaten Slots „ setFrame() “ und „ updateItemPosition() “.
Abschließend rufen wir „ adjust() “ auf, um sicherzustellen, dass das Element die gewünschte Größe erhält.
void ImageItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
{
timeLine.setDirection(QTimeLine::Forward);
if (z != 1.0) {
z = 1.0;
updateItemPosition();
}
if (timeLine.state() == QTimeLine::NotRunning)
timeLine.start();
}
void ImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
{
timeLine.setDirection(QTimeLine::Backward);
if (z != 0.0)
z = 0.0;
if (timeLine.state() == QTimeLine::NotRunning)
timeLine.start();
}Immer wenn der Mauszeiger das Bildelement betritt oder verlässt, werden die entsprechenden Ereignisbehandler ausgelöst: Zunächst legen wir die Richtung der Zeitleiste fest, wodurch das Element sich vergrößert bzw. verkleinert. Anschließend ändern wir den z-Wert des Elements, falls dieser noch nicht auf den erwarteten Wert gesetzt ist.
Bei Hover-Enter-Ereignissen aktualisieren wir die Position des Elements sofort, da wir möchten, dass das Element über allen anderen Elementen erscheint, sobald es sich zu vergrößern beginnt. Bei Hover-Leave-Ereignissen hingegen verschieben wir die eigentliche Aktualisierung, um das gleiche Ergebnis zu erzielen. Beachten Sie jedoch, dass wir beim Erstellen unseres Elements das Signal „ finished()“ der Zeitleiste mit dem Slot „ updateItemPosition() “ verknüpft haben. Auf diese Weise erhält das Element die richtige Position im Elementstapel, sobald die Animation abgeschlossen ist. Schließlich starten wir die Zeitleiste, falls sie noch nicht läuft.
void ImageItem::setFrame(int frame)
{
adjust();
QPointF center = boundingRect().center();
setTransform(QTransform::fromTranslate(center.x(), center.y()), true);
setTransform(QTransform::fromScale(1 + frame / 300.0, 1 + frame / 300.0), true);
setTransform(QTransform::fromTranslate(-center.x(), -center.y()), true);
}Wenn die Zeitleiste läuft, löst sie aufgrund der Verbindung, die wir im Item-Konstruktor hergestellt haben, den Slot „ setFrame() “ aus, sobald sich das aktuelle Bild ändert. Dieser Slot steuert die Animation und vergrößert oder verkleinert das Bild-Item Schritt für Schritt.
Zunächst rufen wir die Funktion ` adjust() ` auf, um sicherzustellen, dass wir mit der ursprünglichen Größe des Elements beginnen. Anschließend skalieren wir das Element mit einem Faktor, der vom Fortschritt der Animation abhängt (unter Verwendung des Parameters ` frame `). Beachten Sie, dass die Transformation standardmäßig relativ zur oberen linken Ecke des Elements erfolgt. Da wir möchten, dass das Element relativ zu seiner Mitte transformiert wird, müssen wir das Koordinatensystem verschieben, bevor wir das Element skalieren.
Letztendlich bleiben nur die folgenden Hilfsfunktionen übrig:
void ImageItem::adjust()
{
setTransform(QTransform::fromScale(120.0 / boundingRect().width(),
120.0 / boundingRect().height()));
}
int ImageItem::id() const
{
return recordId;
}
void ImageItem::updateItemPosition()
{
setZValue(z);
}Die Funktion „ adjust() “ definiert und wendet eine Transformationsmatrix an, um sicherzustellen, dass unser Bildelement unabhängig von der Größe des Quellbildes in der gewünschten Größe angezeigt wird. Die Funktion „ id() “ ist trivial und dient lediglich dazu, das Element identifizieren zu können. Im Slot „ updateItemPosition() “ rufen wir die Funktion „ QGraphicsItem::setZValue()“ auf und legen dabei die Höhe des Elements fest.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.