Adressbuch
Das Adressbuch-Beispiel zeigt, wie Proxy-Modelle verwendet werden können, um verschiedene Ansichten auf Daten aus einem einzigen Modell anzuzeigen.
Dieses Beispiel zeigt ein Adressbuch, in dem die Kontakte alphabetisch in 9 Gruppen gruppiert werden können: ABC, DEF, GHI, ... ..., VW, ..., XYZ. Dies wird durch die Verwendung mehrerer Ansichten auf dasselbe Modell erreicht, von denen jede mit einer Instanz der Klasse QSortFilterProxyModel gefiltert wird.
Übersicht
Das Adressbuch enthält 5 Klassen: MainWindow
, AddressWidget
, TableModel
, NewAddressTab
und AddDialog
. Die Klasse MainWindow
verwendet AddressWidget
als zentrales Widget und bietet die Menüs File und Tools.
Die Klasse AddressWidget
ist eine Unterklasse von QTabWidget, die verwendet wird, um die 10 Registerkarten zu bearbeiten, die im Beispiel angezeigt werden: die 9 Alphabetgruppen-Registerkarten und eine Instanz von NewAddressTab
. Die Klasse NewAddressTab
ist eine Unterklasse von QWidget, die nur verwendet wird, wenn das Adressbuch leer ist und den Benutzer auffordert, einige Kontakte hinzuzufügen. AddressWidget
interagiert auch mit einer Instanz von TableModel
, um Einträge zum Adressbuch hinzuzufügen, zu bearbeiten und zu entfernen.
TableModel
ist eine Unterklasse von QAbstractTableModel, die die Standardmodell/View-API für den Datenzugriff bereitstellt. Sie enthält eine Liste der hinzugefügten Kontakte. Diese Daten sind jedoch nicht alle in einer einzigen Registerkarte sichtbar. Stattdessen wird QTableView verwendet, um 9 verschiedene Ansichten der gleichen Daten entsprechend den Alphabetgruppen bereitzustellen.
QSortFilterProxyModel ist die Klasse, die für die Filterung der Kontakte für jede Gruppe von Kontakten verantwortlich ist. Jedes Proxy-Modell verwendet eine QRegularExpression, um Kontakte herauszufiltern, die nicht zu der entsprechenden alphabetischen Gruppe gehören. Die Klasse AddDialog
wird verwendet, um Informationen vom Benutzer für das Adressbuch zu erhalten. Diese Unterklasse QDialog wird von NewAddressTab
instanziiert, um Kontakte hinzuzufügen, und von AddressWidget
, um Kontakte hinzuzufügen und zu bearbeiten.
Wir beginnen mit einem Blick auf die Implementierung von TableModel
.
Definition der TableModel-Klasse
Die Klasse TableModel
bietet eine Standard-API für den Zugriff auf die Daten in ihrer Kontaktliste durch die Unterklasse QAbstractTableModel. Die grundlegenden Funktionen, die dazu implementiert werden müssen, sind: rowCount()
, columnCount()
, data()
, headerData()
. Damit TableModel editierbar ist, muss es die Funktionen insertRows()
, removeRows()
, setData()
und flags()
implementieren.
struct Contact { QString name; QString address; bool operator==(const Contact &other) const { return name == other.name && address == other.address; } }; inline QDataStream &operator<<(QDataStream &stream, const Contact &contact) { return stream << contact.name << contact.address; } inline QDataStream &operator>>(QDataStream &stream, Contact &contact) { return stream >> contact.name >> contact.address; } class TableModel : public QAbstractTableModel { Q_OBJECT public: TableModel(QObject *parent = nullptr); TableModel(const QList<Contact> &contacts, QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; const QList<Contact> &getContacts() const; private: QList<Contact> contacts; };
Es werden zwei Konstruktoren verwendet, ein Standardkonstruktor, der TableModel
's eigene QList<Contact>
verwendet, und einer, der der Einfachheit halber QList<Contact>
als Argument nimmt.
Implementierung der TableModel-Klasse
Wir implementieren die beiden Konstruktoren wie in der Header-Datei definiert. Der zweite Konstruktor initialisiert die Liste der Kontakte im Modell mit dem Parameterwert.
TableModel::TableModel(QObject *parent) : QAbstractTableModel(parent) { } TableModel::TableModel(const QList<Contact> &contacts, QObject *parent) : QAbstractTableModel(parent), contacts(contacts) { }
Die Funktionen rowCount()
und columnCount()
geben die Dimensionen des Modells zurück. Während der Wert von rowCount()
in Abhängigkeit von der Anzahl der dem Adressbuch hinzugefügten Kontakte variiert, ist der Wert von columnCount()
immer 2, da wir nur Platz für die Spalten Name und Adresse benötigen.
int TableModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : contacts.size(); } int TableModel::columnCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : 2; }
Die Funktion data()
gibt entweder einen Namen oder eine Adresse zurück, je nach dem Inhalt des übergebenen Modellindexes. Die im Modellindex gespeicherte Zeilennummer wird verwendet, um auf ein Element in der Liste der Kontakte zu verweisen. Die Auswahl erfolgt über die Funktion QItemSelectionModel, die mit AddressWidget
erläutert wird.
QVariant TableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= contacts.size() || index.row() < 0) return QVariant(); if (role == Qt::DisplayRole) { const auto &contact = contacts.at(index.row()); switch (index.column()) { case 0: return contact.name; case 1: return contact.address; default: break; } } return QVariant(); }
Die Funktion headerData()
zeigt die Kopfzeile der Tabelle, den Namen und die Adresse an. Wenn Sie nummerierte Einträge für Ihr Adressbuch benötigen, können Sie eine vertikale Kopfzeile verwenden, die wir in diesem Beispiel ausgeblendet haben (siehe die Implementierung AddressWidget
).
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (section) { case 0: return tr("Name"); case 1: return tr("Address"); default: break; } } return QVariant(); }
Die Funktion insertRows()
wird aufgerufen, bevor neue Daten hinzugefügt werden, da die Daten sonst nicht angezeigt werden. Die Funktionen beginInsertRows()
und endInsertRows()
werden aufgerufen, um sicherzustellen, dass alle verbundenen Ansichten von den Änderungen Kenntnis haben.
bool TableModel::insertRows(int position, int rows, const QModelIndex &index) { Q_UNUSED(index); beginInsertRows(QModelIndex(), position, position + rows - 1); for (int row = 0; row < rows; ++row) contacts.insert(position, { QString(), QString() }); endInsertRows(); return true; }
Die Funktion removeRows()
wird aufgerufen, um Daten zu entfernen. Auch hier werden die Funktionen beginRemoveRows() und endRemoveRows() aufgerufen, um sicherzustellen, dass alle verbundenen Ansichten über die Änderungen informiert sind.
bool TableModel::removeRows(int position, int rows, const QModelIndex &index) { Q_UNUSED(index); beginRemoveRows(QModelIndex(), position, position + rows - 1); for (int row = 0; row < rows; ++row) contacts.removeAt(position); endRemoveRows(); return true; }
Die Funktion setData()
ist die Funktion, die Daten in die Tabelle einfügt, und zwar Element für Element und nicht Zeile für Zeile. Das bedeutet, dass setData()
zweimal aufgerufen werden muss, um eine Zeile im Adressbuch zu füllen, da jede Zeile 2 Spalten hat. Es ist wichtig, das Signal dataChanged() auszusenden, da es allen angeschlossenen Ansichten mitteilt, ihre Anzeigen zu aktualisieren.
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { const int row = index.row(); auto contact = contacts.value(row); switch (index.column()) { case 0: contact.name = value.toString(); break; case 1: contact.address = value.toString(); break; default: return false; } contacts.replace(row, contact); emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); return true; } return false; }
Die Funktion flags()
gibt die Elementflags für den angegebenen Index zurück.
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; }
Wir setzen das Flag Qt::ItemIsEditable, weil wir die Bearbeitung von TableModel
erlauben wollen. Obwohl wir in diesem Beispiel die Bearbeitungsfunktionen des QTableView Objekts nicht verwenden, aktivieren wir sie hier, damit wir das Modell in anderen Programmen wiederverwenden können.
Die letzte Funktion in TableModel
, getContacts()
gibt das Objekt QList<Kontakt> zurück, das alle Kontakte im Adressbuch enthält. Wir verwenden diese Funktion später, um die Liste der Kontakte zu erhalten, um auf vorhandene Einträge zu prüfen, die Kontakte in eine Datei zu schreiben und sie wieder zurückzulesen. Weitere Erklärungen finden Sie unter AddressWidget
.
const QList<Contact> &TableModel::getContacts() const { return contacts; }
AddressWidget Klassendefinition
Die Klasse AddressWidget
ist technisch gesehen die Hauptklasse in diesem Beispiel, da sie Funktionen zum Hinzufügen, Bearbeiten und Entfernen von Kontakten, zum Speichern der Kontakte in einer Datei und zum Laden aus einer Datei bereitstellt.
class AddressWidget : public QTabWidget { Q_OBJECT public: AddressWidget(QWidget *parent = nullptr); void readFromFile(); void writeToFile(); public slots: void showAddEntryDialog(); void addEntry(const QString &name, const QString &address); void editEntry(); void removeEntry(); signals: void selectionChanged (const QItemSelection &selected); private: void setupTabs(); inline static QString fileName = QStandardPaths::standardLocations(QStandardPaths::TempLocation).value(0) + QStringLiteral("/addressbook.dat"); TableModel *table; NewAddressTab *newAddressTab; };
AddressWidget
Die Klasse QTabWidget erweitert die Klasse um 10 Registerkarten (NewAddressTab
und die 9 Registerkarten der Alphabetgruppen) und manipuliert außerdem table
, das Objekt TableModel
, proxyModel
, das Objekt QSortFilterProxyModel, das wir zum Filtern der Einträge verwenden, und tableView
, das Objekt QTableView.
Implementierung der AddressWidget-Klasse
Der AddressWidget
Konstruktor akzeptiert ein übergeordnetes Widget und instanziiert NewAddressTab
, TableModel
und QSortFilterProxyModel. Das Objekt NewAddressTab
, das anzeigt, dass das Adressbuch leer ist, wird hinzugefügt und die restlichen 9 Registerkarten werden mit setupTabs()
eingerichtet.
AddressWidget::AddressWidget(QWidget *parent) : QTabWidget(parent), table(new TableModel(this)), newAddressTab(new NewAddressTab(this)) { connect(newAddressTab, &NewAddressTab::sendDetails, this, &AddressWidget::addEntry); addTab(newAddressTab, tr("Address Book")); setupTabs(); }
Die Funktion setupTabs()
wird verwendet, um die 9 Alphabetgruppen-Registerkarten, Tabellenansichten und Proxy-Modelle in AddressWidget
einzurichten. Jedes Proxy-Modell ist wiederum so eingestellt, dass es die Kontaktnamen nach der jeweiligen Alphabetgruppe filtert, wobei die Groß- und Kleinschreibung nicht berücksichtigt wird ( QRegularExpression ). Die Tabellenansichten werden ebenfalls in aufsteigender Reihenfolge sortiert, indem die Funktion sort() des entsprechenden Proxy-Modells verwendet wird.
Für jede Tabellenansicht wird selectionMode auf QAbstractItemView::SingleSelection und selectionBehavior auf QAbstractItemView::SelectRows gesetzt, so dass der Benutzer alle Elemente in einer Zeile gleichzeitig auswählen kann. Jedem QTableView Objekt wird automatisch ein QItemSelectionModel zugewiesen, das die ausgewählten Indizes festhält.
void AddressWidget::setupTabs() { using namespace Qt::StringLiterals; const auto groups = { "ABC"_L1, "DEF"_L1, "GHI"_L1, "JKL"_L1, "MNO"_L1, "PQR"_L1, "STU"_L1, "VW"_L1, "XYZ"_L1 }; for (QLatin1StringView str : groups) { const auto regExp = QRegularExpression(QLatin1StringView("^[%1].*").arg(str), QRegularExpression::CaseInsensitiveOption); auto proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(table); proxyModel->setFilterRegularExpression(regExp); proxyModel->setFilterKeyColumn(0); QTableView *tableView = new QTableView; tableView->setModel(proxyModel); tableView->setSelectionBehavior(QAbstractItemView::SelectRows); tableView->horizontalHeader()->setStretchLastSection(true); tableView->verticalHeader()->hide(); tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); tableView->setSelectionMode(QAbstractItemView::SingleSelection); tableView->setSortingEnabled(true); connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AddressWidget::selectionChanged); connect(this, &QTabWidget::currentChanged, this, [this, tableView](int tabIndex) { if (widget(tabIndex) == tableView) emit selectionChanged(tableView->selectionModel()->selection()); }); addTab(tableView, str); } }
Die Klasse QItemSelectionModel bietet ein Signal selectionChanged, das mit dem Signal selectionChanged()
von AddressWidget
verbunden ist. Wir verbinden auch das Signal QTabWidget::currentChanged() mit dem Lambda-Ausdruck, der auch das Signal selectionChanged()
von AddressWidget
ausgibt. Diese Verbindungen sind notwendig, um die Aktionen Edit Entry... und Remove Entry im Menü Extras von MainWindow
zu aktivieren. Dies wird in der Implementierung von MainWindow
näher erläutert.
Jede Tabellenansicht im Adressbuch wird als Registerkarte zu QTabWidget mit der entsprechenden Bezeichnung hinzugefügt, die aus QStringList von Gruppen stammt.
Wir bieten zwei addEntry()
Funktionen an: Eine, die dazu gedacht ist, Benutzereingaben entgegenzunehmen, und die andere, die die eigentliche Aufgabe des Hinzufügens neuer Einträge zum Adressbuch ausführt. Wir teilen die Verantwortung für das Hinzufügen von Einträgen in zwei Teile auf, damit newAddressTab
Daten einfügen kann, ohne ein Dialogfenster öffnen zu müssen.
Die erste Funktion addEntry()
ist ein Slot, der mit der Aktion MainWindow
's Add Entry... verbunden ist. Diese Funktion erstellt ein AddDialog
Objekt und ruft dann die zweite addEntry()
Funktion auf, um den Kontakt tatsächlich zu table
hinzuzufügen.
void AddressWidget::showAddEntryDialog() { AddDialog aDialog; if (aDialog.exec()) addEntry(aDialog.name(), aDialog.address()); }
Die zweite Funktion addEntry()
führt eine grundlegende Überprüfung durch, um doppelte Einträge im Adressbuch zu vermeiden. Wie bei TableModel
erwähnt, ist dies einer der Gründe, warum wir die Getter-Methode getContacts()
benötigen.
void AddressWidget::addEntry(const QString &name, const QString &address) { if (!name.front().isLetter()) { QMessageBox::information(this, tr("Invalid name"), tr("The name must start with a letter.")); } else if (!table->getContacts().contains({ name, address })) { table->insertRows(0, 1, QModelIndex()); QModelIndex index = table->index(0, 0, QModelIndex()); table->setData(index, name, Qt::EditRole); index = table->index(0, 1, QModelIndex()); table->setData(index, address, Qt::EditRole); removeTab(indexOf(newAddressTab)); } else { QMessageBox::information(this, tr("Duplicate Name"), tr("The name \"%1\" already exists.").arg(name)); } }
Wenn das Modell nicht bereits einen Eintrag mit demselben Namen enthält, rufen wir setData()
auf, um den Namen und die Adresse in die erste und zweite Spalte einzufügen. Andernfalls zeigen wir eine QMessageBox an, um den Benutzer zu informieren.
Hinweis: Das newAddressTab
wird entfernt, sobald ein Kontakt hinzugefügt wurde, da das Adressbuch nicht mehr leer ist.
Das Bearbeiten eines Eintrags ist nur eine Möglichkeit, die Adresse des Kontakts zu aktualisieren, da das Beispiel es dem Benutzer nicht erlaubt, den Namen eines bestehenden Kontakts zu ändern.
Zunächst holen wir uns mit QTabWidget::currentWidget() das Objekt QTableView der aktiven Registerkarte. Dann extrahieren wir die selectionModel
aus der tableView
, um die ausgewählten Indizes zu erhalten.
void AddressWidget::editEntry() { QTableView *temp = static_cast<QTableView*>(currentWidget()); QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model()); QItemSelectionModel *selectionModel = temp->selectionModel(); const QModelIndexList indexes = selectionModel->selectedRows(); QString name; QString address; int row = -1; for (const QModelIndex &index : indexes) { row = proxy->mapToSource(index).row(); QModelIndex nameIndex = table->index(row, 0, QModelIndex()); QVariant varName = table->data(nameIndex, Qt::DisplayRole); name = varName.toString(); QModelIndex addressIndex = table->index(row, 1, QModelIndex()); QVariant varAddr = table->data(addressIndex, Qt::DisplayRole); address = varAddr.toString(); }
Als nächstes extrahieren wir Daten aus der Zeile, die der Benutzer bearbeiten möchte. Diese Daten werden in einer Instanz von AddDialog
mit einem anderen Fenstertitel angezeigt. table
wird nur aktualisiert, wenn Änderungen an den Daten in aDialog
vorgenommen wurden.
AddDialog aDialog; aDialog.setWindowTitle(tr("Edit a Contact")); aDialog.editAddress(name, address); if (aDialog.exec()) { const QString newAddress = aDialog.address(); if (newAddress != address) { const QModelIndex index = table->index(row, 1, QModelIndex()); table->setData(index, newAddress, Qt::EditRole); } } }
Das Entfernen von Einträgen erfolgt mit der Funktion removeEntry()
. Die ausgewählte Zeile wird entfernt, indem über das Objekt QItemSelectionModel selectionModel
darauf zugegriffen wird. newAddressTab
wird nur dann wieder in AddressWidget
eingefügt, wenn der Benutzer alle Kontakte aus dem Adressbuch entfernt.
void AddressWidget::removeEntry() { QTableView *temp = static_cast<QTableView*>(currentWidget()); QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model()); QItemSelectionModel *selectionModel = temp->selectionModel(); const QModelIndexList indexes = selectionModel->selectedRows(); for (QModelIndex index : indexes) { int row = proxy->mapToSource(index).row(); table->removeRows(row, 1, QModelIndex()); } if (table->rowCount(QModelIndex()) == 0) insertTab(0, newAddressTab, tr("Address Book")); }
Die Funktion writeToFile()
wird verwendet, um eine Datei zu speichern, die alle Kontakte im Adressbuch enthält. Die Datei wird in einem benutzerdefinierten .dat
Format gespeichert. Der Inhalt der Kontaktliste wird mit QDataStream auf file
geschrieben. Wenn die Datei nicht geöffnet werden kann, wird eine QMessageBox mit der entsprechenden Fehlermeldung angezeigt.
void AddressWidget::writeToFile() { QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::information(this, tr("Unable to open file"), file.errorString()); return; } QDataStream out(&file); out << table->getContacts(); }
Mit der Funktion readFromFile()
wird eine Datei mit allen Kontakten des Adressbuchs geladen, die zuvor mit writeToFile()
gespeichert wurde. Mit QDataStream wird der Inhalt einer Datei .dat
in eine Liste von Kontakten eingelesen, und jeder dieser Kontakte wird mit addEntry()
hinzugefügt.
void AddressWidget::readFromFile() { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::information(this, tr("Unable to open file"), file.errorString()); return; } QList<Contact> contacts; QDataStream in(&file); in >> contacts; if (contacts.isEmpty()) { QMessageBox::information(this, tr("No contacts in file"), tr("The file you are attempting to open contains no contacts.")); } else { for (const auto &contact: std::as_const(contacts)) addEntry(contact.name, contact.address); } }
NewAddressTab Klassendefinition
Die Klasse NewAddressTab
stellt eine informative Registerkarte bereit, die dem Benutzer mitteilt, dass das Adressbuch leer ist. Er erscheint und verschwindet je nach Inhalt des Adressbuchs, wie in der Implementierung von AddressWidget
erwähnt.
Die Klasse NewAddressTab
erweitert QWidget und enthält eine QLabel und eine QPushButton.
class NewAddressTab : public QWidget { Q_OBJECT public: NewAddressTab(QWidget *parent = nullptr); public slots: void addEntry(); signals: void sendDetails(const QString &name, const QString &address); };
Implementierung der Klasse NewAddressTab
Der Konstruktor instanziiert addButton
, descriptionLabel
und verbindet das Signal von addButton
mit dem Slot addEntry()
.
NewAddressTab::NewAddressTab(QWidget *parent) : QWidget(parent) { auto descriptionLabel = new QLabel(tr("There are currently no contacts in your address book. " "\nClick Add to add new contacts.")); auto addButton = new QPushButton(tr("Add")); connect(addButton, &QAbstractButton::clicked, this, &NewAddressTab::addEntry); auto mainLayout = new QVBoxLayout; mainLayout->addWidget(descriptionLabel); mainLayout->addWidget(addButton, 0, Qt::AlignCenter); setLayout(mainLayout); }
Die Funktion addEntry()
ähnelt der Funktion addEntry()
von AddressWidget
in dem Sinne, dass beide Funktionen ein Objekt AddDialog
instanziieren. Die Daten aus dem Dialog werden extrahiert und an den AddressWidget
's addEntry()
Slot gesendet, indem das sendDetails()
Signal ausgegeben wird.
void NewAddressTab::addEntry() { AddDialog aDialog; if (aDialog.exec()) emit sendDetails(aDialog.name(), aDialog.address()); }
AddDialog Klassendefinition
Die Klasse AddDialog
erweitert QDialog und stellt dem Benutzer ein QLineEdit und ein QTextEdit zur Verfügung, um Daten in das Adressbuch einzugeben.
class AddDialog : public QDialog { Q_OBJECT public: AddDialog(QWidget *parent = nullptr); QString name() const; QString address() const; void editAddress(const QString &name, const QString &address); private: QLineEdit *nameText; QTextEdit *addressText; };
Implementierung der AddDialog-Klasse
Der Konstruktor von AddDialog
richtet die Benutzeroberfläche ein, indem er die erforderlichen Widgets erstellt und sie in Layouts platziert.
AddDialog::AddDialog(QWidget *parent) : QDialog(parent), nameText(new QLineEdit), addressText(new QTextEdit) { auto nameLabel = new QLabel(tr("Name")); auto addressLabel = new QLabel(tr("Address")); auto okButton = new QPushButton(tr("OK")); auto cancelButton = new QPushButton(tr("Cancel")); auto gLayout = new QGridLayout; gLayout->setColumnStretch(1, 2); gLayout->addWidget(nameLabel, 0, 0); gLayout->addWidget(nameText, 0, 1); gLayout->addWidget(addressLabel, 1, 0, Qt::AlignLeft|Qt::AlignTop); gLayout->addWidget(addressText, 1, 1, Qt::AlignLeft); auto buttonLayout = new QHBoxLayout; buttonLayout->addWidget(okButton); buttonLayout->addWidget(cancelButton); gLayout->addLayout(buttonLayout, 2, 1, Qt::AlignRight); auto mainLayout = new QVBoxLayout; mainLayout->addLayout(gLayout); setLayout(mainLayout); connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept); connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); setWindowTitle(tr("Add a Contact")); } QString AddDialog::name() const { return nameText->text(); } QString AddDialog::address() const { return addressText->toPlainText(); } void AddDialog::editAddress(const QString &name, const QString &address) { nameText->setReadOnly(true); nameText->setText(name); addressText->setPlainText(address); }
Um dem Dialog das gewünschte Verhalten zu verleihen, verbinden wir die Schaltflächen OK und Cancel mit den Slots accept() und reject() des Dialogs. Da das Dialogfeld nur als Container für Namens- und Adressinformationen dient, müssen wir keine weiteren Funktionen dafür implementieren.
Definition der Klasse MainWindow
Die Klasse MainWindow
erweitert QMainWindow und implementiert die Menüs und Aktionen, die für die Bearbeitung des Adressbuchs erforderlich sind.
![]() | ![]() |
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); private slots: void updateActions(const QItemSelection &selection); void openFile(); void saveFile(); private: void createMenus(); AddressWidget *addressWidget; QAction *editAct; QAction *removeAct; };
Die Klasse MainWindow
verwendet ein AddressWidget
als zentrales Widget und stellt das Menü Datei mit den Aktionen Open, Close und Exit sowie das Menü Tools mit den Aktionen Add Entry..., Edit Entry... und Remove Entry bereit.
Implementierung der MainWindow-Klasse
Der Konstruktor für MainWindow
instanziiert AddressWidget, legt es als zentrales Widget fest und ruft die Funktion createMenus()
auf.
MainWindow::MainWindow() : QMainWindow(), addressWidget(new AddressWidget) { setCentralWidget(addressWidget); createMenus(); setWindowTitle(tr("Address Book")); }
Die Funktion createMenus()
richtet die Menüs File und Tools ein und verbindet die Aktionen mit ihren jeweiligen Slots. Sowohl die Edit Entry... - als auch die Remove Entry -Aktionen sind standardmäßig deaktiviert, da solche Aktionen bei einem leeren Adressbuch nicht ausgeführt werden können. Sie werden erst aktiviert, wenn ein oder mehrere Kontakte hinzugefügt werden.
void MainWindow::createMenus() { QMenu *fileMenu = menuBar()->addMenu(tr("&File")); QAction *openAct = new QAction(tr("&Open..."), this); fileMenu->addAction(openAct); connect(openAct, &QAction::triggered, this, &MainWindow::openFile); ... editAct = new QAction(tr("&Edit Entry..."), this); editAct->setEnabled(false); toolMenu->addAction(editAct); connect(editAct, &QAction::triggered, addressWidget, &AddressWidget::editEntry); toolMenu->addSeparator(); removeAct = new QAction(tr("&Remove Entry"), this); removeAct->setEnabled(false); toolMenu->addAction(removeAct); connect(removeAct, &QAction::triggered, addressWidget, &AddressWidget::removeEntry); connect(addressWidget, &AddressWidget::selectionChanged, this, &MainWindow::updateActions); }
Wir verbinden nicht nur die Signale aller Aktionen mit ihren jeweiligen Slots, sondern auch das Signal selectionChanged()
von AddressWidget
mit dem Slot updateActions()
.
Die Funktion openFile()
öffnet eine benutzerdefinierte Datei addressbook.dat
, die Adressbuchkontakte enthält. Diese Funktion ist ein Slot, der mit openAct
im Menü File verbunden ist.
void MainWindow::openFile() { addressWidget->readFromFile(); }
Die Funktion saveFile()
speichert eine benutzerdefinierte Datei addressbook.dat
, die die Adressbuchkontakte enthalten wird. Diese Funktion ist ein Slot, der mit saveAct
im Menü File verbunden ist.
void MainWindow::saveFile() { addressWidget->writeToFile(); }
Die Funktion updateActions()
aktiviert und deaktiviert Edit Entry... und Remove Entry in Abhängigkeit vom Inhalt des Adressbuchs. Wenn das Adressbuch leer ist, sind diese Aktionen deaktiviert, andernfalls sind sie aktiviert. Diese Funktion ist ein Slot, der mit dem Signal AddressWidget
's selectionChanged()
verbunden ist.
void MainWindow::updateActions(const QItemSelection &selection) { QModelIndexList indexes = selection.indexes(); if (!indexes.isEmpty()) { removeAct->setEnabled(true); editAct->setEnabled(true); } else { removeAct->setEnabled(false); editAct->setEnabled(false); } }
main()-Funktion
Die Hauptfunktion für das Adressbuch instanziiert QApplication und öffnet ein MainWindow
, bevor die Ereignisschleife ausgeführt wird.
int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mw; mw.show(); return app.exec(); }
© 2025 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.