0. 源代码下载
https://github.com/leichaojian/qt/tree/master/addressbook
0. 程序效果图
1. 整体概述
地址簿包含五个类:MainWindow,AddressWidget,TableModel,NewAddressTab和AddDialog。
MainWindow:使用AddressWidget作为它当前的部件并提供File和Tools菜单。
AddressWidget:是QTabWidget的子类,用于操纵10个tabs表格:9个字母组表格和一个NewAddressTab。它也提供了新增,修改和删除地址的功能。
NewAddressTab:是QWidget的子类,当地址簿为空的时候,提供增加一个地址的功能。
TableModel: 是QAbstractTableModel的子类,提供标准的MV API来接触数据。它包含一个QList<QPairs>来处理地址的新增。
QSortFilterProxyModel和QRegExp:来达到排序和过滤的目的。
2. TableModel类的定义及其实现
1)TableModel类的定义
TableModel类继承于QAbstractTableModel,提供了标准的API来处理QList<QPairs>中的数据。基本要实现的函数是:rowCount(),columnCount(),data(),headerData()。但是对于可编辑的TableModel,还要额外实现:insertRows(),removeRows(),setData()和flags()函数。
/*********************
* QAbstractTableModel为抽象类,用于实现table表格
* 此程序中实现了N行两列的表格,用于存储姓名和地址
* *************************/
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
TableModel(QObject *parent = 0);
TableModel(QList<QPair<QString, QString> > listofPairs, QObject *parent = 0);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());
QList<QPair<QString, QString> > getList();
private:
//用于存储一个N行2列的表格内容(2列分别为姓名和地址)
QList<QPair<QString, QString> > listOfPairs;
};
2)TableModel类的实现
1. rowCount()和columnCount()的实现
//Q_UNUSED宏定义的作用为:避免不适用parent而编译情况下产生告警
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return listOfPairs.size();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
2. data()的实现
//返回名字或者地址(要读取的数据的行号和列号均存储在QModelIndex中)
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
//读取的数据应该在有效范围内,而listOfPairs代表所储存的数据
if (index.row() >= listOfPairs.size() || index.row() < 0)
return QVariant();
if (role == Qt::DisplayRole) {
QPair<QString, QString> pair = listOfPairs.at(index.row());
//如果为第一列,则返回name,否则返回address
if (index.column() == 0)
return pair.first;
else if (index.column() == 1)
return pair.second;
}
return QVariant();
}
3. headerData()的实现
//用于实现表头信息:姓名和地址
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:
return QVariant();
}
}
return QVariant();
}
4. insertRows()和removeRows()的实现
//在指定的position行上插入rows行
bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
//beginInsertRows的作用是开始一个插入行的操作(类似初始化操作,具体请参考QT助手)
beginInsertRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
QPair<QString, QString> pair(" ", " ");
listOfPairs.insert(position, pair); //在listOfPairs中初始化rows行
}
endInsertRows();
return true;
}
//删除从第position行开始的rows行
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) {
listOfPairs.removeAt(position);
}
endRemoveRows();
return true;
}
5. setData()和flags()的实现
//修改特定某行某列的数据(行号和列号由QModelIndex导出)
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
//找到要修改的行数据--这里p是副本
QPair<QString, QString> p = listOfPairs.value(row);
if (index.column() == 0)
p.first = value.toString();
else if (index.column() == 1)
p.second = value.toString();
else
return false;
listOfPairs.replace(row, p);
//引发信号:数据已经被修改
emit(dataChanged(index, index));
return true;
}
return false;
}
而flags函数的作用是标志哪些item项可以被修改(因为存在item项不能被修改的情况)
//返回项目标志(用于表明table是可编辑的--在此程序中没有用到,为了后期的扩展)
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
3. AddressWidget类的定义及其实现
1) AddressWidget类的定义
AddressWidget继承于QTabWidget,用于操纵10个tabs表(每个tab表包含的含义是:由table(具体table表格),TableModel对象,QSortFilterProxyModel对象(用于过滤数据的新增),tableview(MV中的V:视图模式)和QTableView对象)
class AddressWidget : public QTabWidget
{
Q_OBJECT
public:
AddressWidget(QWidget *parent = 0);
void readFromFile(const QString &fileName);
void writeToFile(const QString &fileName);
public slots:
void addEntry();
void addEntry(QString name, QString address);
void editEntry();
void removeEntry();
signals:
void selectionChanged (const QItemSelection &selected);
private:
void setupTabs();
TableModel *table;
NewAddressTab *newAddressTab;
QSortFilterProxyModel *proxyModel;
};
2) AddressWidget类的实现
1. 构造函数的实现
AddressWidget::AddressWidget(QWidget *parent)
: QTabWidget(parent)
{
table = new TableModel(this);
newAddressTab = new NewAddressTab(this);
//新增一个地址时候,触发sendDetails信号,而addEntry接收到新增的“姓名--地址”数据
connect(newAddressTab, SIGNAL(sendDetails(QString, QString)),
this, SLOT(addEntry(QString, QString)));
addTab(newAddressTab, "Address Book");
setupTabs();
}
2. setupTabs()的实现
//实现九列表格框
void AddressWidget::setupTabs()
{
QStringList groups;
groups << "ABC" << "DEF" << "GHI" << "JKL" << "MNO" << "PQR" << "STU" << "VW" << "XYZ";
for (int i = 0; i < groups.size(); ++i) {
QString str = groups.at(i);
QString regExp = QString("^[%1].*").arg(str);
//设定过滤模型--在model(模型中专门用于处理数据,而view则用于显示数据)
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(table);
proxyModel->setFilterRegExp(QRegExp(regExp, Qt::CaseInsensitive));
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(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SIGNAL(selectionChanged(QItemSelection)));
//这里新建的一张表格是groups的一部分(groups包含九张表格)
addTab(tableView, str);
}
}
3. addEntry()的实现
第一个没带任何参数的addEntry()是用于MainWindow的Add Entry...。这里重要的是第二个addEntry()的实现:
void AddressWidget::addEntry(QString name, QString address)
{
//当前table表格的数据(这里会产生9张table表格)
QList<QPair<QString, QString> >list = table->getList();
QPair<QString, QString> pair(name, address);
//姓名--地址不可重复
if (!list.contains(pair)) {
//在table的第0行0列(第一个参数)新增一行(第二个参数)
table->insertRows(0, 1, QModelIndex());
//index为第0行0列
QModelIndex index = table->index(0, 0, QModelIndex());
table->setData(index, name, Qt::EditRole);
//index为第0行第1列
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));
}
}
4. editEntry()和removeEntry()的实现
void AddressWidget::editEntry()
{
//得到当前的QTableView
QTableView *temp = static_cast<QTableView*>(currentWidget());
//QSortFilterProxyModel在model和view之间提供排序和过滤
QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
//选择model
QItemSelectionModel *selectionModel = temp->selectionModel();
//得到当前所要被修改的索引---这里indexes实际上就一项(因为只允许选择一行)
QModelIndexList indexes = selectionModel->selectedRows();
QString name;
QString address;
int row = -1;
foreach (QModelIndex index, indexes) {
//mapToSource:Returns the source model index corresponding to the given proxyIndex from the sorting filter model.
row = proxy->mapToSource(index).row();
//得到姓名数据---这里table指针是一个模型,关联具体表格
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();
}
AddDialog aDialog;
aDialog.setWindowTitle(tr("Edit a Contact"));
//只允许更改地址
aDialog.nameText->setReadOnly(true);
aDialog.nameText->setText(name);
aDialog.addressText->setText(address);
if (aDialog.exec()) {
QString newAddress = aDialog.addressText->toPlainText();
if (newAddress != address) {
//得到第row行第二列(1)的索引,通过setData来更新数据
QModelIndex index = table->index(row, 1, QModelIndex());
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();
QModelIndexList indexes = selectionModel->selectedRows();
foreach (QModelIndex index, indexes) {
int row = proxy->mapToSource(index).row();
//从row行开始删除一行
table->removeRows(row, 1, QModelIndex());
}
if (table->rowCount(QModelIndex()) == 0) {
insertTab(0, newAddressTab, "Address Book");
}
}
4. NewAddressTab类的定义及其实现
此类比较简单,效果图如下:
关键代码如下:
void NewAddressTab::addEntry()
{
AddDialog aDialog;
if (aDialog.exec()) {
QString name = aDialog.nameText->text();
QString address = aDialog.addressText->toPlainText();
//将信号发送出去
emit sendDetails(name, address);
}
}
5. AddDialog类的定义及其实现
此类比较简单,效果图如下:(具体实现请参考源代码)
6. MainWindow类的定义及其实现
此类也比较简单,关键代码如下:
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);
}
}