文档章节

跟我一起学QT11:Address Book的编写

fzyz_sb
 fzyz_sb
发布于 2014/11/22 22:24
字数 2259
阅读 153
收藏 0

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列: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<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();
}



    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<QString, QString> 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<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;
}



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



    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<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);
    }
}






© 著作权归作者所有

fzyz_sb
粉丝 411
博文 209
码字总数 447144
作品 0
武汉
程序员
私信 提问
Python系列教程:爬虫入门 1

  边爬边学,边学边爬(新手慢慢来,可以私聊我)   1.首先获取目标页面      2.这里以豆瓣为例,来获取页面中的所有书籍的名称   (仅供学习交流)   https://www.douban.com/...

Python火火火
2017/05/02
0
0
eyeos 云计算操作系统

随着云计算概念的引入,许多WebOS不断涌出,本篇主要针对eyeos进行简要介绍。eyeos是一个开源的基于Web的操作系统,它是由PHP、XML和Javascript编写而成,属于纯开源软件。eyeos包括一个桌面...

junwong
2012/03/09
1K
0
C语言编程学习项目实战:图书管理系统

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界
2018/06/04
0
0
EXC_BAD_ACCESS的排查

如何排查EXCBADACCESS错误 刚开始学obj-c,遇到这个问题,网上搜了下,说的都有点短(本人愚钝,理解能力弱~~)。搞定后发上来,记录、共享。。 我的xcode是4.2的 操作如下: 1、增加NSZombieE...

小春0618
2014/06/22
0
0
Address Book UI and Address Book frameworks

iOS系统的关于操作通讯录的框架 Address Book UI中提供了系统的UI界面,方便开发 Address Book 提供了直接访问Address Book数据库的方法(满足某些开发中访问Address Book中的数据的需求) 系统...

DavidHacker
2016/03/31
14
0

没有更多内容

加载失败,请刷新页面

加载更多

通过微服务来正确实施SOA

对于组织来说,能够构建、发展和扩展大型应用程序是至关重要的, 但所涉及的挑战使其成为一项艰巨的任务。正因为如此, 微服务凭借能够将单个组件拆分成围绕特定业务功能的独立服务,已成为构建...

Linux就该这么学
11分钟前
2
0
从 Spark 到 Kubernetes — MaxCompute 的云原生开源生态实践之路

2019年5月14日,喜提浙江省科学技术进步一等奖的 MaxCompute 是阿里巴巴自研的 EB 级大数据计算平台。该平台依托阿里云飞天基础架构,是阿里巴巴在10年前做飞天系统的三大件之分布式计算部分...

阿里云官方博客
14分钟前
1
0
使用python来操作redis用法详解

1、redis连接 redis提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的red...

dragon_tech
14分钟前
2
0
给研发工程师的代码质量利器 | SOFAChannel#5 直播整理

> SOFA:Channel,有趣实用的分布式架构频道。 > > 本文根据 SOFAChannel#5 直播分享整理,主题:给研发工程师的代码质量利器 —— 自动化测试框架 SOFAActs。 > > 回顾视频以及 PPT 查看地址...

SOFAStack
17分钟前
1
0
段错误总结

https://blog.csdn.net/e_road_by_u/article/details/61415732 一、段错误是什么 一句话来说,段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问...

悲催的古灵武士
18分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部