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

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






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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值