地址簿示例

地址簿示例展示了如何使用代理模式,基于同一个模型数据展示不同的视图。

这个例子提供了一个地址簿,支持将联系人按人名的头字母划分为9组: ABC, DEF, GHI, … , VW, …, XYZ。该功能通过QSortFilterProxyModel 类对模型进行过滤,生成9个代理模型对象,分别将过滤后的数据交给9个视图对象进行显示。对象图如下所示:

概述

本示例包含五个类: MainWindow, AddressWidget, TableModel, NewAddressTab 和 AddDialog。MainWindow 类使用AddressWidget 作为 central widget 并提供了 File 和 Tools 菜单.

AddressWidget 是 QTabWidget 的子类,用于实现10个tab:9个字母分组,和1个NewAddressTab ( QWidget 的子类当模型中数据为空时会显示 )。AddressWidget 还负责与TableModel实例的交互工作,包含增加、编辑、删除地址簿中的条目。
TableModel 是 QAbstractTableModel 的子类,提供了标准的 model/view API 操作模型中的数据。数据为一个联系人列表,每一个条目包含一个结构体{联系人名称,地址}。但是,这个数据并没有展现在一个单独的tab中,而且是通过字母分组,展示到了9个tab中,每个tab包含一个QTableView 对象用户显示过滤后的数据。
QSortFilterProxyModel用户过滤联系人数据。每一个代理模型使用QRegExp(正则表达式)过滤掉不属于该组的条目。是QDialog的子类,用于从用户输入获取地址簿的信息。 在NewAddressTab 和 AddressWidget 中可以被调用生成,进行数据输入。

实现步骤

- 生成工程

启动Creator4.11.1
文件->新建文件或项目
创建一个Qt Widgets Application项目

后面按要求一路点下去,给项目起一个自己的名字,例如:myAddressBook。
在details步骤里,取消Generate form选项,UI的内容我都将采用代码实现。入下图所示:

Kits步骤里,一定要选择一个已有的编译器,入下图所示。多选几个也可以。例如,在我机器里,没有安装MSVC2017,如果只选择了该选项,在编译的时候将无法编程生成源文件。这里我选择了QT内置的WinGW编译器

完成后,将会看到一些默认的源文件,点击运行试一试效果:

-新建类

按上面概述的描述,先新建下面四个类的源文件(MainWindow已经有了):
AddressWidget, TableModel, NewAddressTab 和 AddDialog。
右键工程名称,选择Add New…,创建相应的C++ Class文件具体如下所示:



这里为了方便,所有的基类都选择QWidget。基类不是QWidget的后面可以在代码里进行修改。
重复上面操作,构出所需的所有类。如果现在不想一起创建,也可以后面再补充。

-源码分析

构造一个软件,我们应该从内往外,先创建TableModel,AddDialog,NewAddressTab,这些部分好比是构造外层的必需品; 然后再完成AddressWidget和MainWindow。
但为了测试、学习方便。我们可以先把MainWindow的框架搭建起来,有些目前用不到的函数可以先写入出头文件,然后注释掉,后面再补充完善。

从外往里的构建方式,往往会造成软件结构的混乱,在现实编程中应该尽量采用从内往外的方式。也就是先准备好源部件,再进行组装。最后按下开关,见证奇迹(当然,如何对模块进行测试又是一门大学问)。但从内往外的编程方式作为教学方法会不够直观,所以这里我们还是采用从外往里的构建过程。
有些程序员,项目初期往往令老板和客户抓狂。因为前期,甚至中后期都看不到什么效果,只能听程序员汇报完成了某一个模块,感觉都是准备工作,后面还遥遥无期。但最终的成品总是调理更清晰,bug也会比较少,总工期更短。

MainWindow

我们先来分析一下主窗口的源码,具体见注释。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include "addresswidget.h"
#include <QMainWindow>
class MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);//注意这里没有析构函数,因为例子里没有,这里我也就删掉了。//代码里采用了QT5的信号槽语法,下面这一行可以删掉
private slots://  这部分内容需要构建完addressWidget以后会用到,先注释掉。// void updateActions(const QItemSelection &selection);void openFile();void saveFile();
private:void createMenus();//注意这里是一个指针,在使用前,需要new一个对象出来才可以。否则会出现运行错误AddressWidget *addressWidget;//下面两个个action需要作为类的私有数据,是因为菜单项生成后,//后面还行需要控制菜单项是否可用(setEnabled)。所以不能像其他菜单项一样,new完以后完全交给父窗口维护。QAction *editAct;QAction *removeAct;
};
#endif // MAINWINDOW_H

#include "mainwindow.h"
#include <QtWidgets>
MainWindow::MainWindow(QWidget *parent)
//addressWidget原本只是个空指针,必须先赋值。虽然很简单,但容易漏掉。: QMainWindow(parent), addressWidget(new AddressWidget)
{setCentralWidget(addressWidget);createMenus();setWindowTitle(tr("Address Book"));
}
//createMenus的代码比较简单,就不详细写注释了。
//此时我们的AddressWidget类并没有完成,所以注释掉了需要AddressWidget的部分。
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);QAction *saveAct = new QAction(tr("&Save As..."), this);fileMenu->addAction(saveAct);connect(saveAct, &QAction::triggered, this, &MainWindow::saveFile);fileMenu->addSeparator();QAction *exitAct = new QAction(tr("E&xit"), this);fileMenu->addAction(exitAct);connect(exitAct, &QAction::triggered, this, &QWidget::close);QMenu *toolMenu = menuBar()->addMenu(tr("&Tools"));QAction *addAct = new QAction(tr("&Add Entry..."), this);toolMenu->addAction(addAct);
//    connect(addAct, &QAction::triggered,
//            addressWidget, &AddressWidget::showAddEntryDialog);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);
}
//openFile,saveFile是将文件内容导入内存中,以及将内存数据保持到文件中
//具体内容交给了addressWidget去完成。
void MainWindow::openFile()
{//    QString fileName = QFileDialog::getOpenFileName(this);
//    if (!fileName.isEmpty())
//        addressWidget->readFromFile(fileName);
}void MainWindow::saveFile()
{//    QString fileName = QFileDialog::getSaveFileName(this);
//    if (!fileName.isEmpty())
//        addressWidget->writeToFile(fileName);
}//updateActions函数会再addressWidget有条目被选中是被调用。默认disable的编辑和删除菜单项将会enable
//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);
//    }
//}

AddressWidget

由于我们采用的是从外到里的方式,构建AddressWidget所需的TableModel和NewAddressTab还没有完成。因此,我们这里只是先把界面的中需要出现的10个tab显示出来。

#ifndef ADDRESSWIDGET_H
#define ADDRESSWIDGET_H
#include "newaddresstab.h"
#include "tablemodel.h"
#include <QItemSelection>
#include <QTabWidget>
//注意,这里基类是QTabWidget
class AddressWidget : public QTabWidget
{Q_OBJECT
public:AddressWidget(QWidget *parent = nullptr);
private://为tabwidget添加tabs。后面我还将在这里设置model和view。void setupTabs();TableModel *table;NewAddressTab *newAddressTab;
};#endif // ADDRESSWIDGET_H
#include "addresswidget.h"
#include "adddialog.h"
#include <QtWidgets>AddressWidget::AddressWidget(QWidget *parent) : QTabWidget(parent),table(new TableModel(this)),newAddressTab(new NewAddressTab(this))
{   //添加一个tab,改tab只有在地址簿数据为空的时候才会出现。addTab(newAddressTab, tr("Address Book"));setupTabs();
}
//只是简单的添加了9个字母组分类的tabs。
void AddressWidget::setupTabs()
{const auto groups = { "ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ" };for (const QString &str : groups) {QTableView *tableView = new QTableView;addTab(tableView, str);}
}

现在来看看运行结果吧:

AddressWidget 是与用户交互的主要部分,也是本示例中最关键的部分,目前我们只完成了一小部分。下面我们将先完成所需的“元器件”( TableModel , NewAddressTab ),再回来补充完善剩下的部分。

NewAddressTab

NewAddressTab 的实现比较简单,只是在界面中显示一段提示信息,加上一个Add按钮。Add按钮的功能实现需要AddDialog的实现,这部分功能我们将后面完善。

#ifndef NEWADDRESSTAB_H
#define NEWADDRESSTAB_H#include <QWidget>class NewAddressTab : public QWidget
{Q_OBJECT
public:explicit NewAddressTab(QWidget *parent = nullptr);void addEntry();signals:
//sendDetails这里只是一个singal,不需要在cpp文件中实现。只需要通过信号槽机制,把参数传出去就行。void sendDetails(const QString &name, const QString &address);
};#endif // NEWADDRESSTAB_H
#include "newaddresstab.h"
#include "adddialog.h"
#include <QtWidgets>
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);
}void NewAddressTab::addEntry()
{//    AddDialog aDialog;//    if (aDialog.exec())
//        emit sendDetails(aDialog.name(), aDialog.address());
}

TableModel

总算到了TableModel,TableModel本该是最早构建的内容。没有model,就没有数据,也就View不出任何东西。
QT的model/view机制,使得model中的数据发生变化时,views会自动更新。TableModel管理的是整个电话簿的数据,在AddressWidget,添加tabs的过程中我们将为9个tab创建9个代理model,与9个view一一对应。具体参见上面的对象图。

#ifndef TABLEMODEL_H
#define TABLEMODEL_H#include <QAbstractTableModel>
#include <QVector>
//! [0]
//结构Contact包含名字和地址
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;
}
//注意这里基类要写成QAbstractTableModel
class TableModel : public QAbstractTableModel
{Q_OBJECTpublic:TableModel(QObject *parent = nullptr);//源码中有下面的构造函数,但本例中没有用到可以删掉。//TableModel(const QVector<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;//源码中有ItemFlags,但本例中没有用到可以删掉。// 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 QVector<Contact> &getContacts() const;private:QVector<Contact> contacts;
};#endif // TABLEMODEL_H
#include "tablemodel.h"TableModel::TableModel(QObject *parent): QAbstractTableModel(parent)
{}
//行数,如果不存在父级索引,表示没有数据返回0。否则返回条目的数量。
//contacts是一个vector容器,通过size()函数可以获得容器中的条目个数。
int TableModel::rowCount(const QModelIndex &parent) const
{return parent.isValid() ? 0 : contacts.size();
}
//列数,与行数类似。列数只可能为0,或2(名称,地址)
int TableModel::columnCount(const QModelIndex &parent) const
{return parent.isValid() ? 0 : 2;
}
//返回model中的某一个数据,并不是一个条目,而是表格里的一个项。
//我们的model是基于QAbstractTableModel,数据的索引类似二维数组的下标。
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();
}
//获取表头项
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();
}
//插入数据(起点,行数,父级索引)
bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{//没有实质性的作用,用来避免编译器警告,对于tree来说,index需要使用,我们这里是table,index用不上。Q_UNUSED(index);//必须吧contacts的操作,包含在begin和end中,这样对应的views才会自动更新。beginInsertRows(QModelIndex(), position, position + rows - 1);//插入时,name和address都为空字符串。需要配合setData一起使用。for (int row = 0; row < rows; ++row)contacts.insert(position, { QString(), QString() });endInsertRows();return true;
}
//与插入类似
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;
}
//注意这里的数据角色需要时Qt::EditRole
//通过索引找到需要修改的项,通过value修改它。
//使用方式举例:setData(index, name, Qt::EditRole);
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);//需要通知数据的使用者,数据发生了变化。views才会更新数据。dataChanged是基类的函数。//topLeft, bottomRight两个index划出需要更新的范围。emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});return true;}return false;
}const QVector<Contact> &TableModel::getContacts() const
{return contacts;
}

AddDialog

AddDialog是一个零时窗口,用户修改或添加条目。

#ifndef ADDDIALOG_H
#define ADDDIALOG_H#include <QDialog>
class QLineEdit;
class QTextEdit;class AddDialog : public QDialog
{Q_OBJECTpublic:AddDialog(QWidget *parent = nullptr);QString name() const;QString address() const;void editAddress(const QString &name, const QString &address);private:QLineEdit *nameText;QTextEdit *addressText;
};#endif // ADDDIALOG_H
#include "adddialog.h"
#include <QtWidgets>
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);
}

NewAddressTab(2)

现在回到NewAddressTab,补充完善addEntry()函数(取消我们之前加上的注释即可)。
点击Add之后我们将可以看到我们的AddDialog窗口,输入信息,点击OK后,名称和地址将会通过信号槽机制发送出去。真正执行插入操作的是AddressWidget::addEntry

AddressWidget(2)

现在构建AddressWidget所需的源部件都已经准备完善。可以来看看完整的代码了。

#ifndef ADDRESSWIDGET_H
#define ADDRESSWIDGET_H
#include "newaddresstab.h"
#include "tablemodel.h"
#include <QItemSelection>
#include <QTabWidget>
//注意,这里基类是QTabWidget
class AddressWidget : public QTabWidget
{Q_OBJECT
public:AddressWidget(QWidget *parent = nullptr);void readFromFile(const QString &fileName);void writeToFile(const QString &fileName);public slots:void showAddEntryDialog();void addEntry(const QString &name, const QString &address);void editEntry();void removeEntry();signals:void selectionChanged (const QItemSelection &selected);
private://为tabwidget添加tabs。后面我还将在这里设置model和view。void setupTabs();TableModel *table;NewAddressTab *newAddressTab;
};#endif // ADDRESSWIDGET_H
#include "addresswidget.h"
#include "adddialog.h"#include <QtWidgets>AddressWidget::AddressWidget(QWidget *parent): QTabWidget(parent),table(new TableModel(this)),newAddressTab(new NewAddressTab(this))
{//newAddressTab中点击add按钮,将会出发AddressWidget::addEntryconnect(newAddressTab, &NewAddressTab::sendDetails,this, &AddressWidget::addEntry);addTab(newAddressTab, tr("Address Book"));setupTabs();
}void AddressWidget::showAddEntryDialog()
{AddDialog aDialog;//和newAddressTab中点击add按钮效果一样。都是将AddDialog的信息,发送给AddressWidget::addEntryif (aDialog.exec())addEntry(aDialog.name(), aDialog.address());
}void AddressWidget::addEntry(const QString &name, const QString &address)
{if (!table->getContacts().contains({ name, address })) {//index的行总是0,也就是从表头插入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);//此时数据条目不为空,newAddressTab没必要存在了。removeTab(indexOf(newAddressTab));} else {QMessageBox::information(this, tr("Duplicate Name"),tr("The name \"%1\" already exists.").arg(name));}
}void AddressWidget::editEntry()
{QTableView *temp = static_cast<QTableView*>(currentWidget());//我们已经在setuptabs里为view设置了model,所以我们知道view的类型为代理modelQSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());//QTableView内置QItemSelectionModel,记录被选中的indexes。QItemSelectionModel *selectionModel = temp->selectionModel();const QModelIndexList indexes = selectionModel->selectedRows();QString name;QString address;int row = -1;//从model中提取数据//这里只能单选,所以可以不用for。自己使用const QModelIndex &index =indexes.first();效果一样for (const QModelIndex &index : indexes) {//通过mapToSource,可以找到真正的原始模型中的数据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();}//把上面获取的数据输入到AddDialogAddDialog aDialog;aDialog.setWindowTitle(tr("Edit a Contact"));aDialog.editAddress(name, address);//修改值,保存回model。只能修改addressif (aDialog.exec()) {const QString newAddress = aDialog.address();if (newAddress != address) {const QModelIndex index = table->index(row, 1, QModelIndex());//setData中会调用 emit dataChanged,通知views更新数据。table->setData(index, newAddress, Qt::EditRole);}}
}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"));
}void AddressWidget::setupTabs()
{const auto groups = { "ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ" };for (const QString &str : groups) {//正则表达式,以str任意一个开头的单词const auto regExp = QRegularExpression(QString("^[%1].*").arg(str),QRegularExpression::CaseInsensitiveOption);auto proxyModel = new QSortFilterProxyModel(this);proxyModel->setSourceModel(table);proxyModel->setFilterRegularExpression(regExp);proxyModel->setFilterKeyColumn(0);//上面是models,下面是viewsQTableView *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);//当QTabWidget当前选项发生变化时,需要selectionChanged通知菜单(编辑,删除)//MainWindow::updateActions会被执行。修改菜单项的状态。connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged,this, &AddressWidget::selectionChanged);
//当选中切的tab发送变化时,tabview会清除选中状态。但我们的菜单并不知道,编辑,删除仍然可用。
//这时候进行编辑,删除会找不到index而造成程序的crash。所以需要使用emit selectionChanged
//这里使用了lambda函数,[需要传入函数的变量](参数){函数体}connect(this, &QTabWidget::currentChanged, this, [this, tableView](int tabIndex) {if (widget(tabIndex) == tableView)emit selectionChanged(tableView->selectionModel()->selection());});addTab(tableView, str);}
}
//文件的读写依赖于TableModel中<<和>>符号的重载。
void AddressWidget::readFromFile(const QString &fileName)
{QFile file(fileName);if (!file.open(QIODevice::ReadOnly)) {QMessageBox::information(this, tr("Unable to open file"),file.errorString());return;}QVector<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: qAsConst(contacts))addEntry(contact.name, contact.address);}
}void AddressWidget::writeToFile(const QString &fileName)
{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();
}

MainWindow(2)

最后再完善一下MainWindow,就可以大功告成了!!!
这里面的内容,都是对AddressWidget的调用,看代码就很清楚了,不需要太多解释。

#include "mainwindow.h"
#include <QtWidgets>
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), addressWidget(new AddressWidget)
{setCentralWidget(addressWidget);createMenus();setWindowTitle(tr("Address Book"));
}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);QAction *saveAct = new QAction(tr("&Save As..."), this);fileMenu->addAction(saveAct);connect(saveAct, &QAction::triggered, this, &MainWindow::saveFile);fileMenu->addSeparator();QAction *exitAct = new QAction(tr("E&xit"), this);fileMenu->addAction(exitAct);connect(exitAct, &QAction::triggered, this, &QWidget::close);QMenu *toolMenu = menuBar()->addMenu(tr("&Tools"));QAction *addAct = new QAction(tr("&Add Entry..."), this);toolMenu->addAction(addAct);connect(addAct, &QAction::triggered,addressWidget, &AddressWidget::showAddEntryDialog);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);
}void MainWindow::openFile()
{QString fileName = QFileDialog::getOpenFileName(this);if (!fileName.isEmpty())addressWidget->readFromFile(fileName);
}void MainWindow::saveFile()
{QString fileName = QFileDialog::getSaveFileName(this);if (!fileName.isEmpty())addressWidget->writeToFile(fileName);
}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);}
}

QT5.14.2自带Examples:Address Book相关推荐

  1. QT5.14.2自带Examples:Local Fortune Server/Client

    概述 这是两个示例,需要配合使用.可以在本机为两个应用程序建立socket通信. 主要是对QLocalServer和QLocalSocket的使用. QLocalServer 调用 listen(), ...

  2. QT5.14.2自带Examples:Bars

    概述 本示在Widget应用程序中使用Q3DBars绘制3D柱状图,显示芬兰奥卢和赫尔辛基的平均气温(2006-2013),并通过UI操作,对显示效果进行调整.展示了以下内容: 使用Q3DBars和一 ...

  3. Qt5.14.2MinGW-32静态编译及压缩过程配置教程

    目录 下载 下载Qt5.14.2 下载upxn 下载Python2 安装 检查配置 检查perl版本 检查python版本 静态编译 配置 编译 安装 新增静态编译环境 添加Qt Version 添加 ...

  4. Qt5.14.2移植到SOM-RK3399开发板上的问题解决办法

    Qt5.14.2移植到SOM-RK3399开发板上的问题解决办法 1. 概述 2. 配置Qt5.9.5 2.1 mkspec配置文件 2.2 配置脚本 2.3 执行make docs时出现的错误 2. ...

  5. 基于Qt5.14.2和mingw的Qt源码学习(三) — 元对象系统简介及moc工具是如何保存类属性和方法的

    基于Qt5.14.2和mingw的Qt源码学习(三) - 元对象系统简介及moc工具是如何保存类属性和方法的 一.什么是元对象系统 1.元对象系统目的 2.实现元对象系统的关键 3.元对象系统的其他一 ...

  6. 自用笔记-Qt5.14.2开发Android环境搭建

    所需软件 qt-opensource-windows-x86-5.14.2.exe android-ndk-r21e-windows-x86_64 android-sdk_r24.4.1-window ...

  7. Qt5.14.2移植到SOM-RK3399开发板

    Qt5.14.2移植到SOM-RK3399开发板 1. 主机开发环境 2. 安装aarch64-linux-gnu-g++交叉编译工具 3. 移植Qt 3.1 修改Qt源码中的Makefile说明文档 ...

  8. win10 + Qt5.14.0(mingw73_64) 下的 OpenCV4.5.1源码编译 注意事项

    大体的流程 别的博客上有,大体比较类似.这里不进行详细的介绍. CMake 的版本 不能过高,例如3.19.x   之类的,我这里用的是3.10.x,否则会莫名其妙的失败.(用了最新的CMake导致的 ...

  9. QT5.14.2基于PCL1.11.1显示点云(基于Windows VS2019开发环境)

    文章目录 一.安装 1.1 PCL安装 1.2 QT安装 1.3 VTK编译 二.程序配置 1. 基于mscv创建QT的程序 2. 配置QT工程文件和依赖项 3. 编写点云显示的小程序 总结 一.安装 ...

最新文章

  1. 疫情之下,村干部们的智慧越来越...
  2. animation动画的使用
  3. python中的pandas的两种基本使用_pandas中join()的两种应用方法
  4. Matlab---傅里叶变换---通俗理解(一)
  5. C#中利用反射循环给一些字段赋值
  6. boost::spirit模块实现演示语法的普通计算器示例
  7. 构建通用类型- 继承 VS 聚合
  8. 输入序列不连续的序列检测
  9. jstl处理栏目与子栏目_芬顿氧化法废水处理工程技术规范(征求意见稿)
  10. 远程视频监控之驱动篇(LED)
  11. 深度干货!一文读懂人脸识别技术(建议收藏)
  12. 书店购物车--增删改
  13. ASPICE过程开发
  14. JSP页面请求和响应
  15. 测试岗位面试前复习之【测试基础知识篇】
  16. 婴儿电动摇篮车摇篮床单芯片蓝牙芯片IC方案
  17. 【汇正财经】企业资本的意义
  18. (转)计算机视觉领域大牛的博客以及研究机构
  19. PHP非诚勿扰-我不是“拍黄片”的!
  20. 亮剑精神”之疯狂高考

热门文章

  1. 虚拟化封装到存储服务器,确保VMware成功的虚拟化存储
  2. mvp+ExpandableListView二级列表+全选和全不选+单价合计
  3. js中Number()与new Number()
  4. 前端/运维电话面试被问到的问题
  5. [封装插件]创建一个弹力球的效果
  6. DIV+CSS实操四:经管系网页内容模块内容添加(一)
  7. 二进制文件是什么?有什么用途?底层原理是什么?
  8. 大学计算机基础实践教程素材文档,大学计算机基础实验素材 example11.pdf
  9. matlab技巧操作之颜色阈值
  10. fastadmin 使用手记