Qt Model/View框架详解1

本文详细介绍了Model/View框架的核心思想、组件间的交互机制,以及MVC框架的优势与局限。重点讲解了Qt中的QAbstractItemModel、QAbstractItemView和QAbstractItemDelegate等类的作用,以及如何使用自定义委托实现高级编辑功能。
摘要由CSDN通过智能技术生成

一.Model/View框架简介

   Model/View框架的核心思想是模型(数据)与视图(显示)相分离模型对外提供标准接口存取数据,不关心数据如何显示,视图自定义数据的显示方式,不关心数据如何组织存储,即数据存储和渲染隔离开

   Model/View框架中数据与显示的分离,可以允许使用不同界面显示同一数据,也能够在不改变数据的情况下添加新的显示界面。为了处理用户输入,引入了委托(delegate)。引入委托的好处是可以自定义数据项的渲染和编辑。

模型必须为每一个数据提供独一无二的索引,视图通过索引访问模型中的数据

模型与数据源进行交互,为框架中其它组件提供接口。交互的本质在于数据源的类型以及模型的实现方式。视图从模型获取模型索引,通过将模型索引反向传给模型,视图又可以从数据源获取数据。在标准视图中,委托渲染数据项在需要编辑数据时,委托使用直接模型索引直接与模型进行交互

Model/View架构分为三部分:模型、视图和委托每一个组件都由一个抽象类定义,抽象类提供了基本的公共接口以及一些默认实现

MVC框架主要有以下三点优势:

耦合性低:由于模型与控制器和视图相分离,业务规则改变后只需要改动MVC的模型层,同样,更改视图层代码而不用重新编译模型和控制器代码。
重用性高:由于多个视图能共享一个模型,所以允许使用不同样式的视图来访问同一个服务器端的代码。
操作简单:开发和维护用户接口的技术含量降低,后台程序员集中精力于业务逻辑,界面程序员集中精力于表现形式上。


MVC框架也不是万能的,主要有以下两方面缺点:

复杂度高:严格遵循MVC会增加代码结构的复杂性,降低运行效率。
调试复杂:因为模型和视图要严格的分离,每个构件在使用之前都需要经过彻底的测试,这会给调试应用程序带来了一定的困难。

MVC层级关系图

模型、视图、委托及其后台数据集之间的关系,如下图所示:

MVC类结构图

Qt模型类的层次结构,如下图所示:

Qt标准视图类的层次结构,如下图所示: 

Qt委托类的层次结构,如下图所示:


 

Model/View框架工作机制    

模型、视图和委托使用信号槽进行交互:

    A、底层维护的数据发生改变时,模型发出信号通知视图

    B、当用户与视图进行交互时,视图发出信号提供了有关用户与界面进行交互的信息

    C、当用户编辑数据项时,委托发出信号用于告知模型和视图编辑器的状态。

Model/View框架的类

QAbstractItemModel、QAbstractItemView、QAbstractItemDelegate

  • QAbstractItemModel

所有的模型都是QAbstractItemModel的子类。QAbstractItemModel类定义了供视图和委托访问数据的接口模型并不一定存储数据本身。QAbstractItemModel提供的接口足够灵活,足以应付以表格、列表和树的形式显示的数据[正常需要显示表格列表即可直接继承QAbstractListModel/QAbstractTableModel]。如果要为列表或者表格设计自定义的模型,直接继承QAbstractListModel和QAbstractTableModel(表格常用,应用较多BOM、看板显示等)类会更好,因为这两个类已经实现了很多通用函数。

 QT内置了多种标准模型:

    QStringListModel:存储简单的字符串列表

    QStandardItemModel:可以用于树结构的存储,提供了层次数据

    QFileSystemModel:本地系统的文件和目录信息

    QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:存取数据库数据

  • QAbstractItemView

QT提供了多个预定义好的视图类:

    QListView:用于显示列表

    QTableView:用于显示表格 (类似excel表格)

    QTreeView:用于显示层次数据 (文件目录树应用较多)

  • QAbstractItemDelegate

所有委托的抽象基类。自 Qt 4.4 以来,默认的委托实现是 QStyledItemDelegate。但是,QStyledItemDelegate 和QItemDelegate 都可以作为视图的编辑器,二者的区别在于,QStyledItemDelegate 使用当前样式进行绘制。在实现自定义委托时,推荐使用QStyledItemDelegate 作为基类,或者结合 Qt style sheets。

二.Model

1.模型简介    

Model/View框架中,Model提供一种标准接口,供视图和委托访问数据。 QT中,Model接口由QAbstractItemModel类进行定义。不管底层数据是如何存储的,只要是QAbstractItemModel的子类,都提供一种表格形式的层次结构。视图利用统一的转换来访问模型中的数据。模型内部数据的组织方式并不一定和视图中数据的显示相同。

 List Model虽然是线性的列表,有一个 Root Item(根节点),线性的一个个数据可以看作是一个只有一列的表格,但是它是有层次的,因为有一个根节点。Table Model就比较容易理解,只是也存在一个根节点。Tree Model主要面向层次数据,而每一层次都可以都很多列,因此也是一个带有层次的表格

2. 模型索引【QModelIndex

  为了使数据的显示同存储分离,引入了模型索引(model index)的概念。通过模型索引,可以访问模型的特定元素的特定部分视图和委托使用模型索引来请求所需要的数据。由此可以看出,只有模型自己需要知道如何获得数据,模型所管理的数据类型可以使用通用的方式进行定义。模型索引保存有创建的它的那个模型的指针,使同时操作多个模型成为可能。

   模型索引提供了所需要的信息的临时索引,用于通过模型取回或者修改数据。由于模型随时可能重新组织其内部的结构,因此模型索引很可能变成不可用的,此时,就不应该保存这些数据。如果你需要长期有效的数据片段,必须创建持久索引。持久索引保证其引用的数据及时更新。临时索引(也就是通常使用的索引)由QModelIndex类提供,持久索引则是 QPersistentModelIndex 类。

  为了定位模型中的数据,需要三个属性:行号、列号以及父索引

       在类表格的视图中,比如列表和表格,行号和列号足以定位一个数据项。但对于树型结构,由于树型结构是一个层次结构,而层次结构中每一个节点都有可能是一个表格。所以,每一个项需要指明其父节点。由于在模型外部只能用过索引访问内部数据,因此,index()函数还需要一个 parent 参数:

QModelIndex index = model->index(row, column, parent);

3、数据角色   

 模型可以针对不同的组件(或者组件的不同部分,比如按钮的提示以及显示的文本等)提供不同的数据。例如,Qt::DisplayRole用于视图的文本显示。通常来说,模型中的数据项包含一系列不同的数据角色,数据角色定义在 Qt::ItemDataRole 枚举中。

  •     Qt::DisplayRole:文本表格中要渲染的关键数据
  •     Qt::EditRole:编辑器中正在编辑的数据
  •     Qt::ToolTipRole:数据项的工具提示的显示数据
  •     Qt::WhatsThisRole:项为"What's This?"模式显示的数据

通过为每一个角色提供恰当的数据,模型可以告诉视图和委托如何向用户显示内容。不同类型的视图可以选择忽略自己不需要的数据,也可以添加所需要的额外数据。在每一个item中,每个数据角色会对应一个数据,通过setData()方法来为指定的数据角色设定数据。setItemData()方法 则是为所有的数据角色设定相同的数据。

三、View    

  Model/View 架构中,视图是数据从模型到最终用户的途径。数据通过视图向用户进行显示,但通常数据的显示同底层数据的存储是完全不同的。

  QAbstractItemModel提供标准的模型接口,使用QAbstractItemView提供标准的视图接口,可以将数据同显示层分离,在视图中利用前面所说的模型索引。视图管理来自模型的数据的布局:既可以直接渲染数据本身,也可以通过委托渲染和编辑数据。

  视图不仅用于显示数据,还用于在数据项之间的导航以及数据项的选择。另外,视图也需要支持很多基本的用户界面的特性,例如右键菜单以及拖放。视图可以提供数据编辑功能,也可以将编辑功能交由某个委托完成。视图可以脱离模型创建,但是在其进行显示之前,必须存在一个模型。对于用户的选择,多个视图可以相互独立,也可以进行共享。

    QT内置了QListView、QTreeView、QTableView视图类,QListView把model中的数据项以一个简单的列表的形式显示,或是以经典的图标视图的形式显示。QTreeView把model中的数据项作为具有层次结构的列表的形式显示,允许以紧凑的深度嵌套的结构进行显示。QTableView把model中的数据项以表格的形式展现。

四、Delegate   

  委托就是供视图实现某种高级的编辑功能。Model/View 没有将用户交互部分完全分离。一般地,视图将数据向用户进行展示并且处理通用的输入。但是,对于某些特殊要求(比如这里的要求必须输入数字),则交予委托完成。这些组件提供输入功能,同时也能渲染某些特殊数据项。委托的接口由 QAbstractItemDelegate定义。

  QAbstractItemDelegate 通过paint()和sizeHint()两个函数渲染用户内容(必须自己将渲染器绘制出来)。从QT4.4开始,QT提供了基于组件的子类:QItemDelegate和QStyledItemDelegate。默认的委托是QStyledItemDelegate。QItemDelegate与QStyledItemDelegate的区别在于绘制和向视图提供编辑器的方式。QStyledItemDelegate使用当前样式绘制,并且能够使用Qt Style Sheet,在自定义委托时推荐使用QStyledItemDelegate作为基类

  继承自QStyledItemDelegate或QItemDelegate的自定义委托类需要重写实现以下几个函数: 

  • 创建编辑器【createEditor】,作为用户编辑数据时所使用的编辑器,从模型中接受数据,返回用户修改的数据
[virtual] QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
  • 设置编辑器的数据【setEditorData】
[virtual] void setEditorData(QWidget *editor, const QModelIndex &index) const
  • 将数据写入model【setModelData】
[virtual] void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
  • 更新编辑器布局【updateEditorGeometry】
[virtual] void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const

代理应用示例源码

用代码说事,比较靠谱。

代码目录:三个自定义类,重实现QStyledItemDelegate类。main函数应用示例。

(1)ComboDelegate.h

#ifndef COMBODELEGATE_H
#define COMBODELEGATE_H

#include <QStyledItemDelegate>

class ComboDelegate : public QStyledItemDelegate
{
public:
    ComboDelegate(QObject *parent = NULL);

protected:
    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

(2)ComboDelegate.cpp

#include "ComboDelegate.h"
#include <QComboBox>

ComboDelegate::ComboDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{}

QWidget *ComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QStringList list;
    list << "工人" << "农民" << "军人" << "律师";

    QComboBox *pEditor = new QComboBox(parent);
    pEditor->addItems(list);
    pEditor->installEventFilter(const_cast<ComboDelegate*>(this));
    return pEditor;
}

void ComboDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QString strText = index.model()->data(index).toString();
    QComboBox *pCombox = NULL;
    pCombox = static_cast<QComboBox*>(editor);
    if (pCombox != NULL)
    {
        int nIndex = pCombox->findText(strText);
        pCombox->setCurrentIndex(nIndex);
    }
}

void ComboDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index)const
{
    QComboBox *pCombox = NULL;
    pCombox = static_cast<QComboBox*>(editor);
    if (pCombox != NULL)
    {
        QString strText = pCombox->currentText();
        model->setData(index, strText);
    }
}

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

(3)DateDelegate.h

#ifndef DATEDELEGATE_H
#define DATEDELEGATE_H

#include <QStyledItemDelegate>

class DateDelegate : public QStyledItemDelegate
{
public:
    DateDelegate(QObject *parent = NULL);

protected:
    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

(4)DateDelegate.cpp

#include "DateDelegate.h"
#include <QDateTimeEdit>

DateDelegate::DateDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{}

// 首先创建要进行代理的窗体
QWidget *DateDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QDateTimeEdit *pEditor = new QDateTimeEdit(parent);      // 一个日历的控件
    pEditor->setDisplayFormat("yyyy-MM-dd");   // 日期时间的显示格式
    pEditor->setCalendarPopup(true);   // 以下拉的方式显示
    pEditor->installEventFilter(const_cast<DateDelegate*>(this));  // 调用这个函数安装事件过滤器,使这个对象可以捕获QDateTimeEdit对象的事件
    return pEditor;
}

// 这个是初始化作用,初始化代理控件的数据
void DateDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
  // 先用这个index返回这个model然后用这个model得到index对应的数据
  QString strDate = index.model()->data(index).toString();
  QDate date = QDate::fromString(strDate, Qt::ISODate);     // 根据QString类型得到相应的时间类型
  QDateTimeEdit *pEditor = NULL;
  pEditor = static_cast<QDateTimeEdit*>(editor);    // 强转为QDateTimeEdit*类型
  if (pEditor != NULL)
  {
      pEditor->setDate(date);      // 设置代理控件的显示数据
  }
}

// 将代理控件里面的数据更新到视图控件中
// void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
void DateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
  QDateTimeEdit *pEditor = NULL;
  pEditor = static_cast<QDateTimeEdit*>(editor);    // 得到时间
  if (pEditor != NULL)
  {
      QDate date = pEditor->date();    // 得到时间
      model->setData(index, QVariant(date.toString(Qt::ISODate)));    // 把值放到相应的index里面
  }
}

// 代理中数据的改变放到model中
// void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void DateDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index);
    editor->setGeometry(option.rect);
}

(5)SpinDelegate.h

#ifndef SPINDELEGATE_H
#define SPINDELEGATE_H

#include <QStyledItemDelegate>

class SpinDelegate : public QStyledItemDelegate
{
public:
    SpinDelegate(QObject *parent = NULL);

protected:
    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

(6)SpinDelegate.cpp

#include "SpinDelegate.h"

#include <QSpinBox>

SpinDelegate::SpinDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

QWidget *SpinDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);

    QSpinBox *pEditor = new QSpinBox(parent);
    pEditor->setRange(0, 30000);
    pEditor->installEventFilter(const_cast<SpinDelegate*>(this));
    return pEditor;
}

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

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

void SpinDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index);

    editor->setGeometry(option.rect);
}

(7)main.cpp

#include <QApplication>
#include <QFile>
#include <QDebug>
#include <QWidget>
#include <QTableView>
#include <QTextStream>
#include <QStandardItemModel>
#include "DateDelegate.h"
#include "ComboDelegate.h"
#include "SpinDelegate.h"

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

    QStandardItemModel model(4, 4);
    model.setHeaderData(0, Qt::Horizontal, QLatin1String("Name"));
    model.setHeaderData(1, Qt::Horizontal, QLatin1String("Birthday"));
    model.setHeaderData(2, Qt::Horizontal, QLatin1String("Job"));
    model.setHeaderData(3, Qt::Horizontal, QLatin1String("Income"));

    QFile file(QLatin1String("/mnt/liuy/info"));
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug() << "open the file failed...";
        return -1;
    }

    QTextStream out(&file);
    QString line;
    model.removeRows(0, model.rowCount(QModelIndex()), QModelIndex());
    int row = 0;
    do
    {
        line = out.readLine();
        if (!line.isEmpty())
        {
            model.insertRows(row, 1, QModelIndex());
            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();

    QTableView tableView;
    tableView.setModel(&model); // 绑定Model
    tableView.setWindowTitle(QLatin1String("Delegate"));

    DateDelegate dateDelegate;
    tableView.setItemDelegateForColumn(1, &dateDelegate); // 第一列代理
    ComboDelegate comboDelegate;
    tableView.setItemDelegateForColumn(2, &comboDelegate);// 第二列代理
    SpinDelegate spinDelegate;
    tableView.setItemDelegateForColumn(3, &spinDelegate); // 第三列代理

    tableView.resize(500, 300); // 重置大小
    tableView.show();

    return a.exec();
}

备注:此示例运行环境为UBuntu + Qt5.3.2

【2】读取的信息文件

文件info 内容如下(注意:文件格式):

Liu,1977-01-05,工人,1500
Wang,1987-11-25,医生,2500
Sun,1967-10-05,军人,500
Zhang,1978-01-12,律师,4500

【3】运行效果图

运行效果图:

第二列编辑图(时间日期控件):

第三列编辑图(下拉框控件):

第四列编辑图(微调框控件):

  • 21
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高亚奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值