QT5.14.2自带Examples:Address Book
地址簿示例
地址簿示例展示了如何使用代理模式,基于同一个模型数据展示不同的视图。
这个例子提供了一个地址簿,支持将联系人按人名的头字母划分为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相关推荐
- QT5.14.2自带Examples:Local Fortune Server/Client
概述 这是两个示例,需要配合使用.可以在本机为两个应用程序建立socket通信. 主要是对QLocalServer和QLocalSocket的使用. QLocalServer 调用 listen(), ...
- QT5.14.2自带Examples:Bars
概述 本示在Widget应用程序中使用Q3DBars绘制3D柱状图,显示芬兰奥卢和赫尔辛基的平均气温(2006-2013),并通过UI操作,对显示效果进行调整.展示了以下内容: 使用Q3DBars和一 ...
- Qt5.14.2MinGW-32静态编译及压缩过程配置教程
目录 下载 下载Qt5.14.2 下载upxn 下载Python2 安装 检查配置 检查perl版本 检查python版本 静态编译 配置 编译 安装 新增静态编译环境 添加Qt Version 添加 ...
- 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. ...
- 基于Qt5.14.2和mingw的Qt源码学习(三) — 元对象系统简介及moc工具是如何保存类属性和方法的
基于Qt5.14.2和mingw的Qt源码学习(三) - 元对象系统简介及moc工具是如何保存类属性和方法的 一.什么是元对象系统 1.元对象系统目的 2.实现元对象系统的关键 3.元对象系统的其他一 ...
- 自用笔记-Qt5.14.2开发Android环境搭建
所需软件 qt-opensource-windows-x86-5.14.2.exe android-ndk-r21e-windows-x86_64 android-sdk_r24.4.1-window ...
- Qt5.14.2移植到SOM-RK3399开发板
Qt5.14.2移植到SOM-RK3399开发板 1. 主机开发环境 2. 安装aarch64-linux-gnu-g++交叉编译工具 3. 移植Qt 3.1 修改Qt源码中的Makefile说明文档 ...
- win10 + Qt5.14.0(mingw73_64) 下的 OpenCV4.5.1源码编译 注意事项
大体的流程 别的博客上有,大体比较类似.这里不进行详细的介绍. CMake 的版本 不能过高,例如3.19.x 之类的,我这里用的是3.10.x,否则会莫名其妙的失败.(用了最新的CMake导致的 ...
- QT5.14.2基于PCL1.11.1显示点云(基于Windows VS2019开发环境)
文章目录 一.安装 1.1 PCL安装 1.2 QT安装 1.3 VTK编译 二.程序配置 1. 基于mscv创建QT的程序 2. 配置QT工程文件和依赖项 3. 编写点云显示的小程序 总结 一.安装 ...
最新文章
- 疫情之下,村干部们的智慧越来越...
- animation动画的使用
- python中的pandas的两种基本使用_pandas中join()的两种应用方法
- Matlab---傅里叶变换---通俗理解(一)
- C#中利用反射循环给一些字段赋值
- boost::spirit模块实现演示语法的普通计算器示例
- 构建通用类型- 继承 VS 聚合
- 输入序列不连续的序列检测
- jstl处理栏目与子栏目_芬顿氧化法废水处理工程技术规范(征求意见稿)
- 远程视频监控之驱动篇(LED)
- 深度干货!一文读懂人脸识别技术(建议收藏)
- 书店购物车--增删改
- ASPICE过程开发
- JSP页面请求和响应
- 测试岗位面试前复习之【测试基础知识篇】
- 婴儿电动摇篮车摇篮床单芯片蓝牙芯片IC方案
- 【汇正财经】企业资本的意义
- (转)计算机视觉领域大牛的博客以及研究机构
- PHP非诚勿扰-我不是“拍黄片”的!
- 亮剑精神”之疯狂高考
热门文章
- 虚拟化封装到存储服务器,确保VMware成功的虚拟化存储
- mvp+ExpandableListView二级列表+全选和全不选+单价合计
- js中Number()与new Number()
- 前端/运维电话面试被问到的问题
- [封装插件]创建一个弹力球的效果
- DIV+CSS实操四:经管系网页内容模块内容添加(一)
- 二进制文件是什么?有什么用途?底层原理是什么?
- 大学计算机基础实践教程素材文档,大学计算机基础实验素材 example11.pdf
- matlab技巧操作之颜色阈值
- fastadmin 使用手记