博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
跟我一起学QT11:Address Book的编写
阅读量:5890 次
发布时间:2019-06-19

本文共 11532 字,大约阅读时间需要 38 分钟。

  hot3.png

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) {        QPair
pair = 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) {        QPair
pair(" ", " "); 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是副本        QPair
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;}

    数据的插入是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);    }}

转载于:https://my.oschina.net/voler/blog/347685

你可能感兴趣的文章
Linux经常用到的命令以及快捷键
查看>>
计算题:挣值、预测、沟通、盈亏平衡点、
查看>>
js 实现 aop
查看>>
Dalvik VM和JVM的比较以及Android新的虚拟机ART
查看>>
【CSU 1803】2016
查看>>
SQLServer 批量备份与还原
查看>>
51Nod 1010 只包含因子2 3 5的数 Label:None
查看>>
Java中String和byte[]间的转换浅析
查看>>
什么是异步
查看>>
WordPress 主题切换
查看>>
cookie和session
查看>>
【java】path和classpath
查看>>
UVa 10057 - A mid-summer night's dream
查看>>
解决3 字节的 UTF-8 序列的字节 3 无效
查看>>
浅谈浏览器兼容性问题-(1)产生、看待与思
查看>>
iOS8中定位服务的变化(CLLocationManager协议方法不响应,无法回掉GPS方法,不出现获取权限提示)...
查看>>
BeanUtils\DBUtils
查看>>
VC 创建托盘,托盘tooltip。右键托盘菜单,点击别的地方会隐藏掉的问题。
查看>>
第一天,新的定义
查看>>
WPF EventSetter Handler Command
查看>>