Qt中的模型-视图

  做GUI应用程序开发的同学想必都知道MVC设计模式,MVC即Model-View-Controller,模型-视图-控制器。现我试图将Qt中的“MVC”讲清楚,先从简单的模型-视图说起。

1. 模型-视图基本概念

  模型-视图中,模型用于数据的存放/管理,视图用于数据的显示。模型-视图的核心思想是将模型和视图解耦,即将二者分离:模型对外提供标准接口,通过接口外界可以存取数据,模型不需要关心数据的显示;视图负责定义数据的显示方式,它不需要知道数据的组织存储。模型-视图从概念图上可以直观的理解为:

Qt中的模型-视图_第1张图片

  Qt中提供了几个现成的模型用于处理数据项,当然了,所谓模型其实就是c++中的类。
  (1) QStringListModel:用于存储简单的QString类型数据的列表项
  (2) QStandardItemModel:用于管理组成树形结构存储的数据项,其中每一个数据项可以是任意类型的数据
  (3) QFileSystemModel:用于管理有关本地文件系统的文件和目录信息

  这些模型类都继承自QAbstractTableModel:

Qt中的模型-视图_第2张图片

  同理,Qt也提供了几个现成的视图用于数据显示:
  (1) QListView:列表形式显示模型中的数据项
  (2) QTableView:表格形式显示模型中的数据项
  (3) QTreeView:树形结构显示模型中的数据项

  这些类都继承自QAbstractItemView:

Qt中的模型-视图_第3张图片

2. 模型-视图的工作机制

  模型-视图的工作,主要依赖于信号与槽机制,
  (1) 当数据发生改变时,模型发出信号以通知视图
  (2) 当用户和视图进行交互时,视图发出信号通知模型以提供交互信息

3. 模型定义的索引

  问:视图如何找到模型中的数据?
  前面讲到模型“对外提供标准接口,通过接口外界可以存取”,这个标准的接口就是索引,视图通过模型提供的索引访问模型中的具体数据,也就是说Qt模型中必须为模型中的每一个数据项提供一个独一无二的索引,这是模型-视图中的关键技术。这个索引接口定义在QAbstractItemModel类中,它是一个虚函数:

virtual QModelIndex index(int row,int column, const QModelIndex &parent = QModelIndex()) const = 0;

  该函数的返回的是一个QModelIndex的对象,QModelIndex是Qt模型中的索引类,它其中包含了具体数据访问的途径和一个指向模型的指针:

QVariant data(int role = Qt::DisplayRole) const;    //获取该索引指向的数据,返回值类型QVariant这个后面说
const QAbstractItemModel* model() const;            //获取该索引所在的模型

Qt中的模型-视图_第4张图片

  该函数的返回值不难理解,难以理解的是3个形参。这个得从Qt模型的索引机制讲起。

  Qt中要获取模型中的某个数据项,需要知道3个量:行号、列号、父节点,这3量合称为三元组。索引在需要时由模型实时创建。
视图的分类主要有:行(List)、列表(Table)和树形(Tree)。对应到模型中的数据项的摆放也主要是这3种结构。下面分别对这3种数据组织模型的存取举例。需要说明,对数据的存取不单单是下面所举例的方法,详细内容可参阅Qt Create提供的帮助文档。

  (1) 行结构数据项模型
Qt中的模型-视图_第5张图片
  行结构的数据项存放于QStringListModel模型中,

//Widget.h
class Widget : public QWidget
{
    Q_OBJECT

public:
    QStringListModel m_model;   //列表模型
    QListView m_listview;       //列表视图

    void initListMode();        //列表模型初始化函数
    void initListView();        //列表视图初始化函数

    QPushButton testBtn;        //按钮,用于测试索引数据

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

public slots:
    void onTestBtnClick();      //按钮槽函数
};

//Widget.cpp
Widget::Widget(QWidget *parent) : QWidget(parent), testBtn(this)
{
    //初始化按钮并连接信号与槽
    testBtn.move(200, 140);
    testBtn.setText("inedx");
    connect(&testBtn, SIGNAL(clicked()), this, SLOT(onTestBtnClick()));

    //初始化模型和视图
    initListMode();
    initListView();

    //将视图和模型挂接
    m_listview.setModel(&m_model);
}

void Widget::initListMode()
{
     m_model.setParent(this);
     QStringList data;
     data << "Hello" << "World" << "Qt";
     m_model.setStringList(data);
}

void Widget::initListView()
{
    m_listview.setParent(this);

    m_listview.move(10, 10);
    m_listview.resize(240, 120);
}

void Widget::onTestBtnClick()
{
    //通过三元组索引数据
    for (int i = 0; i < m_model.rowCount(); i++)
    {
        //虚拟根节点,由QModelIndex()获得
        QModelIndex index = m_model.index(i, 0, QModelIndex());
        QVariant v = index.data();

        qDebug() << v;
    }
}

  编译运行:
Qt中的模型-视图_第6张图片

  (2) 表格结构数据项模型
Qt中的模型-视图_第7张图片

  标准模型QStandardItemModel模型支持列表结构的数据项:

//Widget.h
class Widget : public QWidget
{
    Q_OBJECT

    QStandardItemModel m_model;         //标准模型
    QTableView m_tableview;             //表格视图

    void initStandardItemModel();
    void initTableView();

    QPushButton testBtn;

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

public slots:
    void onTestBtnClick();
};

//Widget.cpp
Widget::Widget(QWidget *parent) : QWidget(parent), testBtn(this)
{
    testBtn.move(200, 140);
    testBtn.setText("inedx");
    connect(&testBtn, SIGNAL(clicked()), this, SLOT(onTestBtnClick()));

    initStandardItemModel();
    initTableView();

    m_tableview.setModel(&m_model);
}

void Widget::initStandardItemModel()
{
    //QStandardItemModel的每一个数据项为QStandardItem类型
    QStandardItem* root = m_model.invisibleRootItem();  //定义虚拟节点的数据项
    QStandardItem* item0 = new QStandardItem();         //定义数据项0
    QStandardItem* item1 = new QStandardItem();         //数据项1
    QStandardItem* item2 = new QStandardItem();

    if((root != NULL) && (item0 != NULL) && (item1 != NULL) && (item2 != NULL) )
    {
        //为行的每一列增加数据
        item0->setData(1, Qt::DisplayRole);     //Qt::DisplayRole为该数据的角色。模型中需要为数据设置具体角色才能在视图中显示
        item1->setData("Qt", Qt::DisplayRole);
        item2->setData(139, Qt::DisplayRole);

        item0->setEditable(false);
        item1->setEditable(false);
        item2->setEditable(false);

        //将数据项增加到虚拟根节点中
        root->setChild(0, 1, item1);
        root->setChild(0, 2, item2);
        root->setChild(0, 0, item0);
    }
}

void Widget::onTestBtnClick()
{
    //通过三元组获取数据项
    for (int i = 0; i < m_model.rowCount(); i++)
    {
        for (int j = 0; j < m_model.columnCount(); j++)
        {
            //QModelIndex()为虚拟根节点
            QModelIndex index = m_model.index(i, j, QModelIndex());
            QVariant v = index.data();

            qDebug() << v;
        }
    }
}

  运行:
Qt中的模型-视图_第8张图片

  (3) 树形结构数据项模型
Qt中的模型-视图_第9张图片

  标准模型QStandardItemModel模型还支持树形结构的数据项,对上面代码稍作更改:

class Widget : public QWidget
{
    Q_OBJECT

    QStandardItemModel m_model;     //标准模型
    QTreeView m_treeview;           //树形视图

    void initStandardItemModel();   
    void initTreeView();

    QPushButton testBtn;

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

public slots:
    void onTestBtnClick();
};

Widget::Widget(QWidget *parent) : QWidget(parent), testBtn(this)
{
    testBtn.move(200, 140);
    testBtn.setText("inedx");
    connect(&testBtn, SIGNAL(clicked()), this, SLOT(onTestBtnClick()));

    initStandardItemModel();
    initTreeView();

    m_treeview.setModel(&m_model);
}

//为QStandardItem增加数据项的操作并不修改
void Widget::initStandardItemModel()
{
    QStandardItem* root = m_model.invisibleRootItem();
    QStandardItem* item0 = new QStandardItem();
    QStandardItem* item1 = new QStandardItem();
    QStandardItem* item2 = new QStandardItem();

    if((item0 != NULL) && (item1 != NULL) && (item2 != NULL) )
    {
        item0->setData(6, Qt::DisplayRole);
        item1->setData("Qt", Qt::DisplayRole);
        item2->setData(139, Qt::DisplayRole);

        item0->setEditable(false);
        item1->setEditable(false);
        item2->setEditable(false);

        root->setChild(0, 1, item1);
        root->setChild(0, 2, item2);
        root->setChild(0, 0, item0);
    }
}

void Widget::initTreeView()
{
    m_treeview.setParent(this);
    m_treeview.move(10, 10);
    m_treeview.resize(321, 120);
}

void Widget::onTestBtnClick()
{
    for (int i = 0; i < m_model.rowCount(); i++)
    {
        for (int j = 0; j < m_model.columnCount(); j++)
        {
            QModelIndex index = m_model.index(i, j, QModelIndex());
            QVariant v = index.data();

            qDebug() << v;

        }
    }
}

  运行:
Qt中的模型-视图_第10张图片

  运行结果似乎与列表没多大不同。那是因为我们没有增加子节点。下面增加子节点尝试:

void Widget::initStandardItemModel()
{
    QStandardItem* root = m_model.invisibleRootItem();
    QStandardItem* item0 = new QStandardItem();
    QStandardItem* item1 = new QStandardItem();
    QStandardItem* item2 = new QStandardItem();
    QStandardItem* ch = new QStandardItem();

    if((ch != NULL) && (item0 != NULL) && (item1 != NULL) && (item2 != NULL) )
    {
        item0->setData(6, Qt::DisplayRole);
        item1->setData("Qt", Qt::DisplayRole);
        item2->setData(139, Qt::DisplayRole);

        item0->setEditable(false);
        item1->setEditable(false);
        item2->setEditable(false);

        ch->setData("ch", Qt::DisplayRole);

        item0->setChild(0, 0, ch);

        root->setChild(0, 1, item1);
        root->setChild(0, 2, item2);
        root->setChild(0, 0, item0);
    }
}

  运行:
Qt中的模型-视图_第11张图片

  用于存放树形结构的数据项还有QFileSystemModel,它用于组织文件系统的目录文件。目录文件的组织方式本来就是树形的:

class Widget : public QWidget
{
    Q_OBJECT

    QFileSystemModel m_model;   //文件系统模型
    QTreeView m_treeview;

    void initFileSystemModel();
    void initTreeView();

    QPushButton testBtn;   
public:
    Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void onTestBtnClick();
};

Widget::Widget(QWidget *parent) : QWidget(parent), testBtn(this)
{
    testBtn.move(200, 140);
    testBtn.setText("inedx");
    connect(&testBtn, SIGNAL(clicked()), this, SLOT(onTestBtnClick()));

    //初始化文件系统模型
    initFileSystemModel();

    //为视图连接模型
    m_treeview.setModel(&m_model);

    //初始化树形视图
    initTreeView();     //注意这3个步骤的顺序
}

void Widget::initFileSystemModel()
{
    //设置模型的根目录
    m_model.setRootPath(QDir::currentPath());

}

void Widget::initTreeView()
{
    m_treeview.setParent(this);
    m_treeview.move(10, 10);
    m_treeview.resize(600, 200);

    //设置树形结构的根目录
    m_treeview.setRootIndex(m_model.index(QDir::currentPath()));
}

void Widget::onTestBtnClick()
{
    //文件系统模型可通过路径获取根节点
    QString path = QDir::currentPath();
    QModelIndex root = m_model.index(path);

    qDebug() << m_model.rowCount(root);

    //通过三元组获取数据项
    for (int i = 0; i < m_model.rowCount(root); i++)
    {
        for (int j = 0; j < m_model.columnCount(); j++)
        {
            QModelIndex ci = m_model.index(i, j, root);
            qDebug() << ci.data().toString();

        }
    }
}

Qt中的模型-视图_第12张图片

4. 模型中的数据项的数据角色

  模型中的数据在试图中的显示方式(用途)可能不同,通过索引得到的存放数据的位置可以有多个,假设为2个,一个是用于常规显示,另一个用于鼠标在视图中对应位置停留时提示用的显示,要如何实现?
  这就需要模型为数据设置数据角色(数据属性)。数据角色在不同的视图中以统一的风格显示对应数据。常见的的数据数据角色定义为(带带有Role单词):

Qt::DisplayRole     直接可见的提示信息(QString)
Qt::DecorationRole  以图表的方式显示(QIcon QPixmap)
Qt::EditRole        可编辑的数据信息(QString)
Qt::ToolTipRole     悬浮框中的补充提示信息(QString)
Qt::StatusTipRole   在状态指片内个显示的提示信息(QString)
Qt::WhatsThisRole   悬浮框中的详细帮助信息(QString)
Qt::SizeHintRole    数据大小信息(QSize)

  以前面的标准模型QStandardItemModel模型的列表结构的数据项为例,初始化模型操作的代码为:

void Widget::initStandardItemModel()
{
    QStandardItem* root = m_model.invisibleRootItem();
    QStandardItem* item0 = new QStandardItem();
    QStandardItem* item1 = new QStandardItem();
    QStandardItem* item2 = new QStandardItem();
    QStandardItem* ch = new QStandardItem();

    if((ch != NULL) && (item0 != NULL) && (item1 != NULL) && (item2 != NULL) )
    {
        //为数据项增加两个数据,分别用于直接可见的显示信息和悬浮的提示信息
        item0->setData(6, Qt::DisplayRole);
        item0->setData("ID", Qt::ToolTipRole);

        item1->setData("Qt", Qt::DisplayRole);
        item1->setData("Object", Qt::ToolTipRole);

        item2->setData(139, Qt::DisplayRole);
        item2->setData("score", Qt::ToolTipRole);


        item0->setEditable(false);
        item1->setEditable(false);
        item2->setEditable(false);

        ch->setData("ch", Qt::DisplayRole);

        root->setChild(0, 0, item0);
        root->setChild(0, 1, item1);
        root->setChild(0, 2, item2);

    }
}

  运行:
Qt中的模型-视图_第13张图片

  数据角色是模型为数据项中的数据附加的一个属性,这个属性代表Qt平台推荐的数据显示方式。不同的的视图完全可以自由解析或者忽略数据的角色信息。

  Qt中的视图-模型基本操作就是这些,MVC中的控制器部分将在下来的写在文章。

你可能感兴趣的:(Qt编程,mvc,模型视图索引,数据角色)