0. 源代码下载
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_OBJECTpublic: TableModel(QObject *parent = 0); TableModel(QList> 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 > getList();private: //用于存储一个N行2列的表格内容(2列分别为姓名和地址) QList > 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列:Name和Address
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) { QPairpair = listOfPairs.at(index.row()); //如果为第一列,则返回name,否则返回address if (index.column() == 0) return pair.first; else if (index.column() == 1) return pair.second; } return QVariant();}
data函数基于model index返回name或者address
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) { QPairpair(" ", " "); listOfPairs.insert(position, pair); //在listOfPairs中初始化rows行 } endInsertRows(); return true;}
在插入数据后,数据并不会立即显示。而beginInsertRows()和endInsertRows()函数的作用就是确保view能知道这次Model数据的改变(MV结构)。同理removeRows()函数实现如下:
//删除从第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是副本 QPairp = 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;}
数据的插入是item by item(一项项),而非row by row(一行行)的插入的。这意味着新增数据需要执行两遍setData:一遍插入name,一遍插入address。而且必须发送dataChanged信号来通知view数据已经改变,要更新界面了。
而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_OBJECTpublic: 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();}
newAddressTab在地址簿(程序刚执行时候地址簿肯定为空)为空时候新增一个地址,而剩余的9个tabs表格则由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); }}
而connect信号槽(主要作用是关联到MainWindow中的Edit Entry和Remove Entry),官网上给出这部分代码一张图:
3. addEntry()的实现
第一个没带任何参数的addEntry()是用于MainWindow的Add Entry...。这里重要的是第二个addEntry()的实现:
void AddressWidget::addEntry(QString name, QString address){ //当前table表格的数据(这里会产生9张table表格) QList>list = table->getList(); QPair 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(currentWidget()); //QSortFilterProxyModel在model和view之间提供排序和过滤 QSortFilterProxyModel *proxy = static_cast (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 (currentWidget()); QSortFilterProxyModel *proxy = static_cast (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); }}