/

/

Qt 学习之路 2(46):视图和委托

Qt 学习之路 2(46):视图和委托

 豆子  2013年3月11日  Qt 学习之路 2  63条评论

前面我们介绍了模型的概念。下面则是另外一个基本元素:视图。在 model/view 架构中,视图是数据从模型到最终用户的途径。数据通过视图向用户进行显示。此时,这种显示方式不必须同模型的存储结构相一致。实际上,很多情况下,数据的显示同底层数据的存储是完全不同的。

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

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

某些视图,例如QTableViewQTreeView,不仅显示数据,还会显示列头或者表头。这些是由QHeaderView视图类提供的。在《QFileSystemModel》一章的最后,我们曾经提到过这个问题。表头通常访问视图所包含的同一模型。它们使用QAbstractItemModel::headerData()函数从模型中获取数据,然后将其以标签 label 的形式显示出来。我们可以通过继承QHeaderView类,实现某些更特殊的功能。

正如前面的章节介绍的,我们通常会为视图提供一个模型。拿前面我们曾经见过的一个例子来看:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QStringList data;
data << "0" << "1" << "2";
model = new QStringListModel(this);
model->setStringList(data);
 
listView = new QListView(this);
listView->setModel(model);
 
QPushButton *btnShow = new QPushButton(tr("Show Model"), this);
connect(btnShow, SIGNAL(clicked()),
        this, SLOT(showModel()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(btnShow);
 
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(listView);
layout->addLayout(buttonLayout);
setLayout(layout);

运行一下程序,这个界面十分简单:

跟我们前面的演示几乎一模一样。现在我们有一个问题:如果我们双击某一行,列表会允许我们进行编辑。但是,我们没办法控制用户只能输入数字——当然,我们可以在提交数据时进行检测,这也是一种办法,不过,更友好的方法是,根本不允许用户输入非法字符。为了达到这一目的,我们使用了委托。下面,我们增加一个委托:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SpinBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    SpinBoxDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {}
 
    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;
};

正如前面所说,委托就是供视图实现某种高级的编辑功能。不同于经典的 Model-View-Controller(MVC)模式,model/view 没有将用户交互部分完全分离。一般地,视图将数据向用户进行展示并且处理通用的输入。但是,对于某些特殊要求(比如这里的要求必须输入数字),则交予委托完成。这些组件提供输入功能,同时也能渲染某些特殊数据项。委托的接口由QAbstractItemDelegate定义。在这个类中,委托通过paint()sizeHint()两个函数渲染用户内容(也就是说,你必须自己将渲染器绘制出来)。为使用方便,从 4.4 开始,Qt 提供了另外的基于组件的子类:QItemDelegateQStyledItemDelegate。默认的委托是QStyledItemDelegate。二者的区别在于绘制和向视图提供编辑器的方式。QStyledItemDelegate使用当前样式绘制,并且能够使用 Qt Style Sheet(我们会在后面的章节对 QSS 进行介绍),因此我们推荐在自定义委托时,使用QStyledItemDelegate作为基类。不过,除非自定义委托需要自己进行绘制,否则,二者的代码其实是一样的。

继承QStyledItemDelegate需要实现以下几个函数:

  • createEditor():返回一个组件。该组件会被作为用户编辑数据时所使用的编辑器,从模型中接受数据,返回用户修改的数据。
  • setEditorData():提供上述组件在显示时所需要的默认值。
  • updateEditorGeometry():确保上述组件作为编辑器时能够完整地显示出来。
  • setModelData():返回给模型用户修改过的数据。

下面依次看看各函数的实现:

 
 
1
2
3
4
5
6
7
8
9
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                       const QStyleOptionViewItem & /* option */,
                                       const QModelIndex & /* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setMinimum(0);
    editor->setMaximum(100);
    return editor;
}

createEditor()函数中,parent 参数会作为新的编辑器的父组件。

 
 
1
2
3
4
5
6
7
void SpinBoxDelegate::setEditorData(QWidget *editor,
                                    const QModelIndex &index) const
{
    int value = index.model()->data(index, Qt::EditRole).toInt();
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

setEditorData()函数从模型中获取需要编辑的数据(具有Qt::EditRole角色)。由于我们知道它就是一个整型,因此可以放心地调用toInt()函数。editor 就是所生成的编辑器实例,我们将其强制转换成QSpinBox实例,设置其数据作为默认值。

 
 
1
2
3
4
5
6
7
8
9
void SpinBoxDelegate::setModelData(QWidget *editor,
                                   QAbstractItemModel *model,
                                   const QModelIndex &index) const
{
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText();
    int value = spinBox->value();
    model->setData(index, value, Qt::EditRole);
}

在用户编辑完数据后,委托会调用setModelData()函数将新的数据保存到模型中。因此,在这里我们首先获取QSpinBox实例,得到用户输入值,然后设置到模型相应的位置。标准的QStyledItemDelegate类会在完成编辑时发出closeEditor()信号,视图会保证编辑器已经关闭,但是并不会销毁,因此需要另外对内存进行管理。由于我们的处理很简单,无需发出closeEditor()信号,但是在复杂的实现中,记得可以在这里发出这个信号。针对数据的任何操作都必须提交给QAbstractItemModel,这使得委托独立于特定的视图。当然,在真实应用中,我们需要检测用户的输入是否合法,是否能够存入模型。

 
 
1
2
3
4
5
6
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}

最后,由于我们的编辑器只有一个数字输入框,所以只是简单将这个输入框的大小设置为单元格的大小(由option.rect提供)。如果是复杂的编辑器,我们需要根据单元格参数(由option提供)、数据(由index提供)结合编辑器(由editor提供)计算编辑器的显示位置和大小。

现在,我们的委托已经编写完毕。接下来需要将这个委托设置为QListView所使用的委托:

 
 
1
listView->setItemDelegate(new SpinBoxDelegate(listView));

值得注意的是,new 操作符并不会真的创建编辑器实例。相反,只有在真正需要时,Qt 才会生成一个编辑器实例。这保证了程序运行时的性能。

然后我们运行下程序:

 

Qt 学习之路 2(46):视图和委托的更多相关文章

  1. Qt 学习之路 :动态视图

    Repeater适用于少量的静态数据集.但是在实际应用中,数据模型往往是非常复杂的,并且数量巨大.这种情况下,Repeater并不十分适合.于是,QtQuick 提供了两个专门的视图元素:ListVi ...

  2. Qt 学习之路:模型-视图高级技术

    PathView PathView是 QtQuick 中最强大的视图,同时也是最复杂的.PathView允许创建一种更灵活的视图.在这种视图中,数据项并不是方方正正,而是可以沿着任意路径布局.沿着同一 ...

  3. Qt 学习之路 2(47):视图选择

    Qt 学习之路 2(47):视图选择 豆子 2013年3月28日 Qt 学习之路 2 34条评论 选择是视图中常用的一个操作.在列表.树或者表格中,通过鼠标点击可以选中某一项,被选中项会变成高亮或者反 ...

  4. Qt 学习之路 2(51):布尔表达式树模型

    Qt 学习之路 2(51):布尔表达式树模型 豆子 2013年5月15日 Qt 学习之路 2 17条评论 本章将会是自定义模型的最后一部分.原本打算结束这部分内容,不过实在不忍心放弃这个示例.来自于 ...

  5. Qt 学习之路 2(67):访问网络(3)

    Qt 学习之路 2(67):访问网络(3) 豆子 2013年11月5日 Qt 学习之路 2 16条评论 上一章我们了解了如何使用我们设计的NetWorker类实现我们所需要的网络操作.本章我们将继续完 ...

  6. Qt 学习之路 2(66):访问网络(2)

    Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2)  豆子  2013年10月31日  Qt 学习之路 2  27条评论 上一 ...

  7. Qt 学习之路 2(57):可视化显示数据库数据

    Qt 学习之路 2(57):可视化显示数据库数据(skip) 豆子 2013年6月26日 Qt 学习之路 2 26条评论 前面我们用了两个章节介绍了 Qt 提供的两种操作数据库的方法.显然,使用QSq ...

  8. Qt 学习之路 2(53):自定义拖放数据

    Qt 学习之路 2(53):自定义拖放数据 豆子  2013年5月26日  Qt 学习之路 2  13条评论上一章中,我们的例子使用系统提供的拖放对象QMimeData进行拖放数据的存储.比如使用QM ...

  9. Qt 学习之路 2(50):自定义可编辑模型

    Home / Qt 学习之路 2 / Qt 学习之路 2(50):自定义可编辑模型 Qt 学习之路 2(50):自定义可编辑模型 豆子 2013年5月13日 Qt 学习之路 2 13条评论 上一章我们 ...

随机推荐

  1. PC 微信页面倒计时代码 safari不兼容date的问题

    PC: 1.html页面: <div class="aTime"> <em id="t_d"></em> <em id ...

  2. Java多线程-线程的交互

    一.线程交互的基础知识void notify():唤醒在此对象监视器上等待的单个线程. void notifyAll():唤醒在此对象监视器上等待的所有线程. void wait():导致当前的线程等 ...

  3. Python的Flask框架使用Redis做数据缓存的配置方法

    flask配置redis 首先得下载flask的缓存插件Flask-Cache,使用pip下载. sudo pip install flask_cache 为应用扩展flask_cache   app ...

  4. java Web servlet简介及其生命周期

    Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容. 我们可 ...

  5. Unity ios、android、pc一键打包(一)

    http://www.cnblogs.com/miaoshujiang/p/5289223.html http://blog.csdn.net/ynnmnm/article/details/36774 ...

  6. 501. Find Mode in Binary Search Tree查找BST中的众数

    [抄题]: Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently oc ...

  7. 数字图像处理实验(15):PROJECT 06-02,Pseudo-Color Image Processing 标签: 图像处理MATLAB 2017-05-27 20:53

    实验要求: 上面的实验要求中Objective(实验目的)部分是错误的. 然而在我拿到的大纲中就是这么写的,所以请忽视那部分,其余部分是没有问题的. 本实验是使用伪彩色强调突出我们感兴趣的灰度范围,在 ...

  8. 498B Name That Tune

    传送门 题目大意 n首音乐,第i首被听出来的概率为pi,刚开始听第一首,1s后如果听出来了则放第下一首,否则接着听这一首,第i首在连续听了ti s之后一定会被听出来,问Ts后听出来的歌的期望数量. 分 ...

  9. Laravel框架中的数据库CURD操作、连贯操作、链式操作的用法

    Laravel是一套简洁.优雅的PHP Web开发框架(PHP Web Framework).它可以让你从面条一样杂乱的代码中解脱出来:它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁.富于 ...

  10. poj3274 Gold Balanced Lineup(HASH)

    Description Farmer John's N cows (1 ≤ N ≤ 100,000) share many similarities. In fact, FJ has been abl ...