Qt中模型/视图结构

8.1 概述

MVC设计模式是起源于Smalltalk的一种与用户界面相关的设计模式。通过使用此模型,可以有效地分离数据和用户界面。MVC设计模式包括三个元素:表示数据的模型
(Model)、表示用户界面的视图(View)和定义了用户在界面上的操作控制(Controller)。
与MVC设计模式类似,Qt引入了模型/视图结构用于完成数据与界面的分离,即InterView框架。但不同的是,Qt的InterView框架中把视图和控制部件结合在一起,使得 
框架更为简洁。为了灵活地处理用户输入,InterView框架引入了代理(Delegate)。通过使用代理,能够自定义数据条目(item)的显示和编辑方式。
Qt的模型/视图结构分为了三种部分:模式(Model)、视图(View)和代理(Delegate)。其中,模型与数据通信,并为了其它部件提供接口;而视图从模型中获得用来
引用数据条目的模型索引(Model Index)。在视图中,代理负责绘制数据条目,当编辑条目时,代理和模型直接进行通信。模型/视图/代理之间通过信号和槽进行通信。
它们之间的关系如下:
     数据发生改变时,模型发出信号通知视图。
     用户对界面进行操作,视图发生信号。
     代理发出信号告知模型和视图编辑器目前的状态。

Qt中模型/视图结构_第1张图片

基本概念
1、模型(Model)
     InterView框架中的所有模型都基于抽象基类QAbstractItemModel类,此类由QProxyModel、QAbstractListModel、QAbstractTableModel、QAbstractProxyModel、
QDirModel、QFileSystemModel、QHelpContentModel 和 QStandardItemModel类继承。
2、视图(View)
     InterView框架中的所有视图都基于抽象基类QAbstractItemView类,此类由QColumnView、QHeaderView、QListView、QTableView和QTreeView类继承。
3、代理(Delegate)
     InterView框架中的所有代理都基于抽象基类QAbstractItemDelegate类,此类由QItemDelegate 和 QStyledItemDelegate类继承。

 运行效果

Qt中模型/视图结构_第2张图片

main.cpp

#include "mainwindow.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * 新建一个QDirModel对象,对数据访问做准备,QDirModel的创建还可以设置过滤器,即只有符合条件的文件或目录才能被访问
 * QDirModel类继承自QAbstractItemModel类,为访问本地文件系统提供数据模型,它提供了新建、删除、创建目录等一系列与
 * 文件操作相关的函数,此处只是用来显示本地文件系统
 *
 * 新建三种不同的View对象,以便文件目录可以以三种不同的方式显示
 *
 * tree.setModel(&model):调用setModel()函数设置View对象的Model为QDirModel对象的model。
 *
 * tree.setSelectionMode (QAbstractItemView::MultiSelection):设置QTreeView对象的选择方式为多选
 *
 *list.setSelectionModel (tree.selectionModel ()):设置QListView对象与QTreeView对象使用相同的选择类型
 *
 * 为了实现双击QTreeView对象中的某个目录时,QListView对象和QTableView对象中显示此选定目录下的所有文件和目录,
 * 需要链接QTreeView对象的doubleClicked()信号与QListView对象和QTableView对象的setRootIndex()槽函数
*/
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QDirModel model;

    QTreeView tree;
    QListView list;
    QTableView table;

    tree.setModel (&model);
    list.setModel (&model);
    table.setModel (&model);

    tree.setSelectionMode (QAbstractItemView::MultiSelection);
    list.setSelectionModel (tree.selectionModel ());
    table.setSelectionModel (tree.selectionModel ());

    QObject::connect (&tree, SIGNAL(doubleClicked(QModelIndex)),
                      &list, SLOT(setRootIndex(QModelIndex)));

    QObject::connect (&tree, SIGNAL(doubleClicked(QModelIndex)),
                      &table, SLOT(setRootIndex(QModelIndex)));

    QSplitter *spliter = new QSplitter;

    spliter->addWidget (&tree);
    spliter->addWidget (&list);
    spliter->addWidget (&table);
    spliter->setWindowTitle (QObject::tr ("Model/View"));

    spliter->show();

    return a.exec();
}

8.2 模型(Model)
实现自定义模型可以通过QAbstractItemModel类继承,也可以通过QAbstractListModel和QAbstractTableModel类继承实现列表模型或表格模型。在数值代码保存,然后
通过外键关联操作来查找其真实的含义,这一方法为了避免冗余。

Item主要角色及其描述

常量
描述
Qt::DisplayRole
显示文字
Qt::DecorationRole
绘制装饰数据(通常是图标)
Qt::EditRole 在编辑器中编辑的数据
Qt::ToolTipRole
工具提示
 Qt::StatusTipRole
状态栏提示
Qt::WhatsThisRole
What's This文字
Qt::SizeHintRole
尺寸提示
Qt::FontRole
默认代理的绘制使用的字体
Qt::TextAlignmentRole 默认代理的对齐方式
Qt::BackgroundRole
默认代理的背景画刷
Qt::ForegroundRole 默认代理的前景画刷
Qt::CheckStateRole
默认代理的检查框状态
Qt::UserRole
用户自定义的数据的起始位置

运行画面

Qt中模型/视图结构_第3张图片

modelex.h

#ifndef MODELEX_H
#define MODELEX_H

#include 
#include 
#include 
#include 

/*
 * QMap提供了一个从类型为Key的键到类型为T的值的映射。
 * rowCount()、columnCount()、data()和返回表头数据的headerData()函数
 * 是QAbstractTableModel类的纯数函数
 *
 * 纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,
 * 而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用
*/

class ModelEx : public QAbstractTableModel
{
public:
    explicit ModelEx(QObject *parent = 0);

    virtual int rowCount (const QModelIndex &parent) const;
    virtual int columnCount (const QModelIndex &parent) const;

    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData (int section, Qt::Orientation orientation, int role) const;
signals:
public slots:
private:
    QVector<short> army;                //定义一个短型军种类型容器
    QVector<short> weaponType;          //定义一个短型武器类型容器

    QMap<short, QString> armyMap;       //定义一个从short类型的键值到类型QString的值得映射
    QMap<short, QString> weaponTypeMap; //定义一个从short类型的键值到类型QString的值得映射

    QStringList weapon;                 //定义一个字符串武器链表
    QStringList header;                 //定义一个头结点链表

    void populateModel();               //完成表格数据的初始化填充
};

#endif // MODELEX_H

modelex.cpp

#include "modelex.h"

ModelEx::ModelEx(QObject *parent) :
    QAbstractTableModel(parent)
{
    armyMap[1] = tr("空军");
    armyMap[2] = tr("海军");
    armyMap[3] = tr("陆军");
    armyMap[4] = tr("海军陆战队");

    weaponTypeMap[1] = tr("轰炸机");
    weaponTypeMap[2] = tr("航空母舰");
    weaponTypeMap[3] = tr("直升机");
    weaponTypeMap[4] = tr("两栖攻击舰");
    weaponTypeMap[5] = tr("驱逐舰");
    weaponTypeMap[6] = tr("两栖战舰");
    weaponTypeMap[7] = tr("坦克");
    weaponTypeMap[8] = tr("战斗机");

    populateModel ();
}

void ModelEx::populateModel ()
{
    header << tr("军种") << tr("种类") << tr("武器");
    army << 1 << 2 << 3 << 4 << 4 << 2 << 3 << 1;
    weaponType << 1 << 2 << 3 << 4 <<5 << 6 << 7 << 8;
    weapon << tr("B-2") << tr("尼米兹级") << tr("阿帕奇") << tr("黄蜂级")
           << tr("阿利伯克级") << tr("AAAV") << tr("MIAI") << ("F-22");
}

/*
 * 返回行
*/
int ModelEx::rowCount (const QModelIndex &parent) const
{
    return army.size ();
}

/*
 * 返回列
*/
int ModelEx::columnCount (const QModelIndex &parent) const
{
    return 3;
}

/*
 * QVariant类类似于C++的联合(union)数据类型,它不仅能够保存很多Qt类型的值,包括QColor、QBrush、QFont、
 * QPen、QRect、QString和QSize等,也能够存放容器类的值。
 * data()函数返回指定索引的数据,即将数值映射为文字
*/
QVariant ModelEx::data (const QModelIndex &index, int role) const
{
    if(!index.isValid ())
    {
        return QVariant();
    }
    /*Qt::DisplayRole用来存取视图中显示的文字*/
    if(role == Qt::DisplayRole)
    {
        switch(index.column ())
        {
        case 0:
            return armyMap[army[index.row ()]];
            break;
        case 1:
            return weaponTypeMap[weaponType[index.row ()]];
            break;
        case 2:
            return weapon[index.row ()];
            break;
        default:
            return QVariant();
            break;
        }
    }

    return QVariant();
}
/*
 * headerData()函数返回固定的表头数据,设置水平表头的标题
*/
QVariant ModelEx::headerData (int section, Qt::Orientation orientation, int role) const
{
    if(role == Qt::DisplayRole && orientation == Qt::Horizontal)
        return header[section];

    return QAbstractTableModel::headerData (section, orientation, role);
}

main.cpp

#include 
#include "modelex.h"
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ModelEx modelEx;
    QTableView view;
    view.setModel(&modelEx);
    view.setWindowTitle(QObject::tr("modelEx"));
    view.resize(400,400);
    view.show();

    return a.exec();
}

8.3 视图(View)
实现自定义的View,可继承自QAbstractItemView类,对所需的纯虚函数进行重定义与实现,对于QAbstractItemView类中的纯虚函数,在子类中必须进行重定义,
但不一定要实现,可根据需要选择实现。

显示结果

Qt中模型/视图结构_第4张图片

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "histogramview.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

    void createAction();
    void createMenu();
    void setupModel();
    void setupView();
    void openFile(QString);
private:
    QMenu *fileMenu;
    QAction *openAct;
    QStandardItemModel *model;
    QTableView *table;
    QSplitter *splitter;
    HistogramView *histogram;
public slots:
    void slotOpen();
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include 
#include 
#include 
#include 

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    createAction ();
    createMenu ();
    setupModel ();
    setupView ();

    setWindowTitle (tr("View Example"));
    resize(600, 660);
}

MainWindow::~MainWindow()
{

}

void MainWindow::createAction ()
{
    openAct = new QAction(tr("打开"), this);
    connect (openAct, SIGNAL(triggered(bool)), this, SLOT(slotOpen()));
}
/*
 * 创建一个文件菜单栏
 * 将openAct动作插入到这个菜单栏里
*/
void MainWindow::createMenu ()
{
    fileMenu = new QMenu(tr("文件"), this);
    fileMenu->addAction(openAct);
    menuBar ()->addMenu (fileMenu);
}

void MainWindow::setupModel ()
{
    model = new QStandardItemModel(4, 4, this);
    model->setHeaderData (0, Qt::Horizontal, tr("部门"));
    model->setHeaderData (1, Qt::Horizontal, tr("男"));
    model->setHeaderData (2, Qt::Horizontal, tr("女"));
    model->setHeaderData (3, Qt::Horizontal, tr("退休"));
}

void MainWindow::setupView ()
{
    splitter = new QSplitter;
    splitter->setOrientation (Qt::Vertical);

    histogram = new HistogramView(splitter);
    histogram->setModel (model);

    table = new QTableView;     //新建一个QTableView对象
    table->setModel (model);    //为QTableView对象设置相同的Model

    /*新建一个QItemSelectModel对象作为QTableView对象使用的选择模型*/
    QItemSelectionModel *selectionModel = new QItemSelectionModel(model);
    table->setSelectionModel (selectionModel);
    histogram->setSelectionModel (selectionModel);

    splitter->addWidget (table);
    splitter->addWidget (histogram);

    setCentralWidget (splitter);

    connect (selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), table,
             SLOT(selectionChanged(QItemSelection,QItemSelection)));
    connect (selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), histogram,
             SLOT(selectionChanged(QItemSelection,QItemSelection)));

}

/*
 * 打开一个*.txt文件
 * 如果文件非空读取文件信息
*/
void MainWindow::slotOpen ()
{
    QString name;
    name = QFileDialog::getOpenFileName (this, "打开", ".", "histogram files(*.txt)");

    if(!name.isEmpty ())
    {
        openFile (name);
    }
}

/*
 * 一行行读取*.txt文件内容,当遇到“,”分割数据
 * QFile类提供了一个接口,用于读取或写入文件
 * QTextStream类可以方便地读取单词、行和数字。
 * 在循环中一次读取文件中的一行数据,然后插入到表格对应的一行数据中
*/
void MainWindow::openFile (QString path)
{
    if(!path.isEmpty ())
    {
        QFile file(path);
        if(file.open (QFile::ReadOnly | QFile::Text))
        {
            QTextStream stream(&file);
            QString line;
            /*从文本中移除一行的数据*/
            model->removeRows (0, model->rowCount (QModelIndex()), QModelIndex());
            int row = 0;
            do
            {
                line = stream.readLine ();
                if(!line.isEmpty ())
                {
                    /*设置一行插入多个数据,一次插入一个数据*/
                    model->insertRows (row, 1, QModelIndex());
                    QStringList pieces = line.split (",", QString::SkipEmptyParts);
                    /*将数据设置到row行、0、1、2、3列*/
                    model->setData (model->index (row, 0, QModelIndex()), pieces.value (0));
                    model->setData (model->index (row, 1, QModelIndex()), pieces.value (1));
                    model->setData (model->index (row, 2, QModelIndex()), pieces.value (2));
                    model->setData (model->index (row, 3, QModelIndex()), pieces.value (3));
                    row++;
                }
            }while(!line.isEmpty ());
            file.close ();
        }
    }
}

histogramview.h

#ifndef HISTOGRAMVIEW_H
#define HISTOGRAMVIEW_H

#include 
#include 
#include 
#include 

/*
 * visualRect (),scrollTo (),indexAt (),moveCursor (),horizontalOffset (),verticalOffset ()
 * isIndexHidden (),setSelection ()和visualRegionForSelection ():QAbstractItemView类中的纯虚函数
 * 这些纯虚函数不一定要实现,可以根据需要选择性地实现,但一定要声明。
 *
 * QModelIndex indexAt(const QPoint &point) const:当鼠标在视图中单击或位置发生改变时被触动,它返回鼠标所
 * 在点的QModelIndex值。当鼠标处在某一个数据项的区域中,则返回此数据项的Index值,否则返回一个空的Index。
 *
 * void mousePressEvent (QMouseEvent *event):柱状统计图可以被鼠标单击选择,选中后以不同的方式显示。
 *
 * selectionChanged (const QItemSelection &selected, const QItemSelection &deselected):当数据项
 * 选择发生变化时,此槽函数将响应。
 *
 * void dataChanged (const QModelIndex &topLeft, const QModelIndex &bottomRight):当模型中的数据发生
 * 变更时,此槽函数将响应。
 *
 * void setSelection (const QRect &rect, QItemSelectionModel::SelectionFlags flags):将位于QRect内
 * 的数据项按照SelectionFlags(描述被选择的数据项以何种方式进行更新)指定的方式进行更新。QItemSelectModel类提供
 * 多种可用的SelecttionFlags,常用的有QItemSelectModel::Select、QItemSelectModel::Current类。
 *
 * QItemSelectionModel *selections:用于保存与视图选择项相关的内容。
 *
 * QList  MRegionList:用于保存其中某一类型柱状图的区域范围,而每个区域是QList中的一个值。
*/

class HistogramView : public QAbstractItemView
{
public:
    HistogramView(QWidget *parent = 0);


    QRect visualRect (const QModelIndex &index) const;
    void scrollTo (const QModelIndex &index, ScrollHint hint=EnsureVisible);
    QModelIndex indexAt(const QPoint &point) const;

    //为selecttions赋初值
    void setSelectionModel (QItemSelectionModel *selectionModel);
    QRegion itemRegion(QModelIndex index);

    void paintEvent (QPaintEvent *);
    void mousePressEvent (QMouseEvent *event);

protected slots:
    void selectionChanged (const QItemSelection &selected, const QItemSelection &deselected);
    void dataChanged (const QModelIndex &topLeft, const QModelIndex &bottomRight);

protected:
    QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction,Qt::KeyboardModifiers modifiers);
    int horizontalOffset () const;
    int verticalOffset () const;
    bool isIndexHidden (const QModelIndex &index) const;
    void setSelection (const QRect &rect, QItemSelectionModel::SelectionFlags flags);
    QRegion visualRegionForSelection (const QItemSelection &selection) const;

private:
    QItemSelectionModel *selections;
    QList <QRegion> MRegionList;
    QList <QRegion> FRegionList;
    QList <QRegion> SRegionList;

};

#endif // HISTOGRAMVIEW_H


histogramview.cpp

#include "histogramview.h"
#include 

HistogramView::HistogramView(QWidget *parent)
    :QAbstractItemView(parent)
{
}
//paintEvent()函数具体完成柱状统计图绘制的工作
void HistogramView::paintEvent (QPaintEvent *)
{
    QPainter painter(viewport ());  //将viewport()作为绘图设备新建一个QPainter对象
    painter.setPen (Qt::black);

    int x0 = 40;
    int y0 = 280;
    //绘制y轴坐标
    painter.drawLine (x0, y0, 40, 18);
    painter.drawLine (36, 22, 40, 18);
    painter.drawLine (44, 22, 40, 18);
    painter.drawText (27, 15, tr("人数"));

    for(int i = 0; i <= 5; i++)
    {
        painter.drawLine (40, 280 - (5 - i) * 50, 42, 280 - (5 - i) * 50);
        painter.drawText (25, 285 - (5 - i) * 50, tr("%1").arg ((5 - i) * 5));
    }

    //绘制x轴坐标
    painter.drawLine (x0, y0, 562, 280);
    painter.drawLine (558, 276, 562, 280);
    painter.drawLine (558, 284, 562, 280);
    painter.drawText (567, 285, tr("部门"));

//    for(int i = 0; i <= 9; i++)
//    {
//        painter.drawLine (490 - (8 - i) * 50, 280, 490 - (8 - i) * 50, 278);
//        painter.drawText (487 - (8 - i) * 50, 295, tr("%1").arg (i + 1));
//    }

    int posD = x0 + 20;
    int row;
    //读取文件中每一行的第一例数据
    for(row = 0; row < model()->rowCount (rootIndex ()); ++row)
    {
        QModelIndex index = model()->index (row, 0, rootIndex ());
        QString dep = model ()->data (index).toString ();

        painter.drawText (posD, y0 + 15, dep);
        posD += 50;
    }
    /*完成了表格第1列数据的柱状统计图的绘制*/
    //男
    int posM = x0 + 20;
    for(row = 0; row < model()->rowCount (rootIndex ()); row++)
    {
        QModelIndex index=model()->index(row,1,rootIndex());
        int male=model()->data(index).toDouble();

        int width = 10;
        //使用不同画刷颜色区别选中与未选中的数据项
        if(selections->isSelected(index))
            painter.setBrush(QBrush(Qt::blue,Qt::Dense3Pattern));
        else
            painter.setBrush(Qt::blue);
        //绘制长方形条x,y,w,h
        painter.drawRect(QRect(posM, y0 - male * 10, width, male * 10));
        QRegion regionM(posM, y0 - male * 10, width, male * 10);
        //将此数据所占据的区域保存到MRegionList列表中,为后面的数据项选择做准备。
        MRegionList << regionM;

        posM += 50;
    }

    /*完成了表格第2列数据的柱状统计图的绘制*/
    //女条目框
    int posF = x0 + 30;
    for(row = 0; row < model()->rowCount (rootIndex ()); ++row)
    {
        QModelIndex index = model()->index (row, 2, rootIndex ());
        int female = model()->data (index).toDouble ();
        int width = 10;
        if(selections->isSelected (index))
            painter.setBrush (QBrush(Qt::red, Qt::Dense3Pattern));
        else
            painter.setBrush (Qt::red);

        //绘制长方形x,y,w,h
        painter.drawRect (QRect(posF, y0 - female * 10, width, female * 10));
        QRegion regionF(posF, y0 - female * 10, width, female * 10);
        //将此数据所占据的区域保存到FRegionList列表中,为后面的数据项选择做准备。
        FRegionList << regionF;
        posF += 50;
    }
    /*完成了表格第3列数据的柱状统计图的绘制*/
    //退休条目框
    int posS = x0 + 40;
    for(row = 0; row < model()->rowCount (rootIndex ()); ++row)
    {
        QModelIndex index = model ()->index (row, 3, rootIndex ());
        int retire = model()->data (index).toDouble ();
        int width = 10;

        if(selections->isSelected (index))
            painter.setBrush (QBrush(Qt::green, Qt::Dense3Pattern));
        else
            painter.setBrush (Qt::green);

        //绘制长方形x,y,w,h
        painter.drawRect (QRect(posS, y0 - retire * 10, width, retire * 10));
        QRegion regionS(posS, y0 - retire * 10, width, retire * 10);
        SRegionList << regionS;
        posS += 50;
    }
}
/*
 * dataChanged()函数实现当Model中的数据改变时,调用绘图设备的update()函数进行更新,反应数据的变化
*/
void HistogramView::dataChanged (const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    QAbstractItemView::dataChanged (topLeft, bottomRight);
    viewport ()->update ();
}
/*
 * 设置选择模式,至此View已经能正确显示表格的统计数据,而且对表格中的某个数据项进行修改时能够及时显示将变化反应在柱状统计图中
*/
void HistogramView::setSelectionModel (QItemSelectionModel *selectionModel)
{
    selections = selectionModel;
}
/*
 * 完成当数据项发生变化时调用update()函数,重绘绘图设备即可工作。此函数是将其他View中的操作引起的数据项选择变化放映到自身
 * View的显示中。
*/
void HistogramView::selectionChanged (const QItemSelection &selected, const QItemSelection &deselected)
{
    viewport ()->update ();
}
/*
 * 在调用setSelection()函数确定鼠标单击点是否在某个数据项的区域内,并设置选择项
*/
void HistogramView::mousePressEvent (QMouseEvent *event)
{
    QAbstractItemView::mousePressEvent (event);
    setSelection (QRect(event->pos().x (), event->pos().y (), 1, 1), QItemSelectionModel::SelectCurrent);
}
/*
 * QModelIndex selectIndex:用于保存被选中的数据项的Index值。此处只实现用鼠标单击选择,而没有实现鼠标拖曳框选,因此,鼠标
 * 动作只可能选中一个数据项。
 *
 * for循环里面:确定在rect中是否含有数据项。此处采用遍历的方式将每个数据项的区域与rect区域进行intersected操作,获得两者之间的
 * 交集。若此交集不为空则说明此数据项被选中,将它的Index值赋给selectIndex。
 *
 * QRegion region = itemRegion (index):返回指定index的数据项所占用的区域
 *
 * 完成select()函数的调用,即完成最后对选择项的设置工作。select()函数时在实现setSelection()函数的调用,即完成最后对选择项的
 * 设置。select()函数是在实现setSelection()函数时必须调用的。
*/
void HistogramView::setSelection (const QRect &rect, QItemSelectionModel::SelectionFlags flags)
{
    int rows = model()->rowCount (rootIndex ());        //获得总行数
    int columns = model()->columnCount (rootIndex());   //获得总列数
    QModelIndex selectedIndex;

    for(int row = 0; row < rows; ++row)
    {
        for(int column = 1; column < columns; ++column)
        {
            QModelIndex index = model()->index (row, column, rootIndex ());
            QRegion region = itemRegion (index);

            if(!region.intersected (rect).isEmpty ())
                selectedIndex = index;
        }
    }

    if(selectedIndex.isValid ())
        selections->select (selectedIndex, flags);
    else
    {
        QModelIndex noIndex;
        selections->select (noIndex, flags);
    }
}

QModelIndex HistogramView::indexAt (const QPoint &point) const
{
    QPoint newPoint(point.x (), point.y ());
    QRegion region;

    //男列
    foreach(region, MRegionList)
    {
        if(region.contains (newPoint))
        {
            int row = MRegionList.indexOf (region);
            QModelIndex index = model()->index (row, 1, rootIndex ());
            return index;
        }
    }

    //女列
    foreach (region, FRegionList)
    {
        if(region.contains (newPoint))
        {
            int row = FRegionList.indexOf (region);
            QModelIndex index = model ()->index (row, 2, rootIndex ());
            return index;
        }
    }
    //合计列
    foreach(region, SRegionList)
    {
        if(region.contains (newPoint))
        {
            int row = SRegionList.indexOf (region);
            QModelIndex index = model()->index (row, 3, rootIndex ());
            return index;
        }
    }
    return QModelIndex();
}

QRegion HistogramView::itemRegion (QModelIndex index)
{
    QRegion region;
    if(index.column () == 1)    //男
        region = MRegionList[index.row ()];
    if(index.column () == 2)    //女
        region = FRegionList[index.row ()];
    if(index.column () == 3)
        region = SRegionList[index.row ()];
    return region;
}

QRect HistogramView::visualRect(const QModelIndex &index)const{}

void HistogramView::scrollTo(const QModelIndex &index,ScrollHint){}

QModelIndex HistogramView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers){}

int HistogramView::horizontalOffset()const{}

int HistogramView::verticalOffset()const{}

bool HistogramView::isIndexHidden(const QModelIndex &index)const{}

QRegion HistogramView::visualRegionForSelection(const QItemSelection &selection)const{}

8.4代理(Dlegate)
在表格中嵌入各种不同控件,通过表格中的控件对编辑的内容进行限定。通常情况下,这种在表格中插入空间的方式,空间始终显示。在表格中控件数目较多时,将
影响表格的美观。此时,可利用Delegate的方式实现同样的效果,控件只有在需要编辑数据项时才会显示,从而解决了上述所遇到的问题。
运行效果如下:
Qt中模型/视图结构_第5张图片 Qt中模型/视图结构_第6张图片

main.cpp

#include "mainwindow.h"
#include 
#include 
#include 
#include 
#include 
#include "datedelegate.h"
#include "combodelegate.h"
#include "spindelegate.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    /*定义一个4*4的表格对象*/
    QStandardItemModel model(4, 4);
    QTableView tableView;
    tableView.setModel (&model);

    DateDelegate dateDelegate;
    tableView.setItemDelegateForColumn (1, &dateDelegate);

    ComboDelegate comboDelegate;
    tableView.setItemDelegateForColumn (2, &comboDelegate);

    SpinDelegate spinDelegate;
    tableView.setItemDelegateForColumn (3, &spinDelegate);

    model.setHeaderData (0, Qt::Horizontal, QObject::tr("姓名"));
    model.setHeaderData (1, Qt::Horizontal, QObject::tr ("生日"));
    model.setHeaderData (2, Qt::Horizontal, QObject::tr("职业"));
    model.setHeaderData (3, Qt::Horizontal, QObject::tr("收入"));

    QFile file("test.txt");
    if(file.open (QFile::ReadOnly | QFile::Text))
    {
        QTextStream stream(&file);
        QString line;
        model.removeRows (0, model.rowCount (QModelIndex()), QModelIndex());
        int row = 0;

        do{
            line = stream.readLine ();

            if(!line.isEmpty ())
            {
                /*一次插入一行数据,每次插入一个数据*/
                model.insertRows (row, 1, QModelIndex());
                /*当遇到”,“跳过该项,将数据插入pieces链表中*/
                QStringList pieces = line.split (",", QString::SkipEmptyParts);
                model.setData (model.index (row, 0, QModelIndex()), pieces.value(0));
                model.setData (model.index (row, 1, QModelIndex()), pieces.value (1));
                model.setData (model.index (row, 2, QModelIndex()), pieces.value (2));
                model.setData (model.index (row, 3, QModelIndex()), pieces.value (3));
                row++;
            }
        }while(!line.isEmpty ());
        file.close ();
    }
    tableView.setWindowTitle (QObject::tr ("Delegate"));
    tableView.resize (450, 250);
    tableView.show();

    return a.exec();
}

combodelegate.h

#ifndef COMBODELEGATE_H
#define COMBODELEGATE_H


#include 
#include 

class ComboDelegate : public QItemDelegate
{
public:
    ComboDelegate(QObject *parent = 0);
    QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData (QWidget *editor, const QModelIndex &index) const;
    void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
    void updateEditorGeometry (QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};

#endif // COMBODELEGATE_H


combodelegate.cpp

#include "combodelegate.h"
#include 

ComboDelegate::ComboDelegate(QObject *parent) :
    QItemDelegate(parent)
{

}

/*
 * createEditor()函数中创建了一个QComboBox控件,并插入可现实的条目,安装事件过滤器。
*/
QWidget *ComboDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QComboBox *editor = new QComboBox(parent);
    editor->addItem (tr("工人"));
    editor->addItem (tr("农民"));
    editor->addItem (tr("医生"));
    editor->addItem (tr("律师"));
    editor->addItem (tr("军人"));
    editor->installEventFilter (const_cast<ComboDelegate*>(this));

    return editor;
}
/*
 * 更新代理控件中的数据显示
*/
void ComboDelegate::setEditorData (QWidget *editor, const QModelIndex &index) const
{
    QString str = index.model ()->data (index).toString ();
    QComboBox *box = static_cast<QComboBox*>(editor);
    int i = box->findText (str);
    box->setCurrentIndex (i);
}
/*
 * 更新model中的数据
*/
void ComboDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QComboBox *box = static_cast<QComboBox*>(editor);
    QString str = box->currentText ();
    model->setData (index, str);
}

void ComboDelegate::updateEditorGeometry (QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry (option.rect);
}

datedelegate.h

#ifndef DATEDELEGATE_H
#define DATEDELEGATE_H

#include 
#include 

class DateDelegate : public QItemDelegate
{
public:
    DateDelegate(QObject *parent = 0);
    QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData (QWidget *editor, const QModelIndex &index) const;
    void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
    void updateEditorGeometry (QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};

#endif // DATEDELEGATE_H


datedelegate.cpp

#include "datedelegate.h"
#include 

DateDelegate::DateDelegate(QObject *parent) :
    QItemDelegate(parent)
{

}
/*
 * 完成创建控件的工作,创建由参数中的QModelIndex对象指定的表项数据的编辑控件,并对控件的内容进行设定
 *
 * 新建一个QDateTimeEdit对象作为编辑时的输入控件
 * 设置此QDateTimeEdit对象的显示格式为yyyy-MM-dd,此为ISO标准显示方式
 * 设置日历选择的显示以Popup的方式,即下拉菜单的方式显示
 * 调用QObject类的installEventFilter()函数安装事件过滤器,使DateDelegate能够捕获QDateTimeEdit对象的事件
*/
QWidget *DateDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QDateTimeEdit *editor = new QDateTimeEdit(parent);
    editor->setDisplayFormat ("yyyy-MM-dd");
    editor->setCalendarPopup (true);
    editor->installEventFilter (const_cast<DateDelegate*>(this));

    return editor;
}
/*
 * QString dataStr = index.model ()->data (index).toString (): 获取指定index数据项的数据,调用QModelIndex的
 * model()函数可获得提供index的Model对象,data()函数返回的是一个QVariant对象,toString()函数将它转换为一个QString类型数据
 *
 * 通过QDate的fromString()函数将以QString类型表示的日期数据转换为QDate类型。Qt::ISODate表示QDate类型的日期是以ISO格式
 * 保存的,这样最终转换得的QDate数据也是ISO格式,使控件显示与表格显示保持一致。
 *
 * QDateTimeEdit *edit = static_cast(editor):将editor装换为QDateTimeEdit对象,以获得编辑控件的
 * 对象指针。
*/
void DateDelegate::setEditorData (QWidget *editor, const QModelIndex &index) const
{
    QString dataStr = index.model ()->data (index).toString ();
    QDate date = QDate::fromString (dataStr, Qt::ISODate);

    QDateTimeEdit *edit = static_cast<QDateTimeEdit*>(editor);
    edit->setDate (date);
}
/*
 * static_cast(editor):通过紧缩转换获得编辑控件的对象指针
 * QDate date = edit->date ():获得编辑控件中的数据更新
 * model->setData (index, QVariant(date.toString (Qt::ISODate))): 调用setData()函数
 * 将数据修改更新到Model中。
 *
*/
void DateDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QDateTimeEdit *edit = static_cast<QDateTimeEdit*>(editor);
    QDate date = edit->date ();
    model->setData (index, QVariant(date.toString (Qt::ISODate)));
}

void DateDelegate::updateEditorGeometry (QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry (option.rect);
}

spindelegate.h

#ifndef SPINDELEGATE_H
#define SPINDELEGATE_H


#include 

class SpinDelegate : public QItemDelegate
{
public:
    SpinDelegate(QObject *parent = 0);
    QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData (QWidget *editor, const QModelIndex &index) const;
    void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
    void updateEditorGeometry (QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;

};

#endif // SPINDELEGATE_H


spindelegate.cpp

#include "spindelegate.h"
#include 

SpinDelegate::SpinDelegate(QObject *parent) :
    QItemDelegate(parent)
{

}

QWidget *SpinDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setRange (0, 10000);
    editor->installEventFilter (const_cast<SpinDelegate*>(this));

    return editor;
}

void SpinDelegate::setEditorData (QWidget *editor, const QModelIndex &index) const
{
    int value = index.model ()->data (index).toInt ();
    QSpinBox *box = static_cast<QSpinBox*>(editor);
    box->setValue (value);
}

void SpinDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QSpinBox *box = static_cast<QSpinBox*>(editor);
    int value = box->value ();
    model->setData (index, value);
}

void SpinDelegate::updateEditorGeometry (QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry (option.rect);
}

你可能感兴趣的:(Qt5开发及实例)