首页 Qt 学习之路 2 Qt 学习之路 2(30):Graphics View Framework

Qt 学习之路 2(30):Graphics View Framework

27 3.7K

Graphics View 提供了一种接口,用于管理大量自定义的 2D 图形元素,并与之进行交互;还提供了用于将这些元素进行可视化显示的观察组件,并支持缩放和旋转。我们通常所说的 Linux 的 KDE 桌面环境,就是建立在 Graphics View 基础之上的(尽管新版本的 KDE 有向 QML 迁移的趋势)。

Graphics View 框架包含了一套完整的事件体系,可以用于与场景中的元素进行双精度的交互。这些元素同样支持键盘事件、鼠标事件等。Graphics View 使用了 BSP 树(Binary Space Partitioning tree,这是一种被广泛应用于图形学方面的数据结构)来提供非常快速的元素发现,也正因为如此,才能够实现一种上百万数量级元素的实时显示机制。

Graphics View 最初在 Qt 4.2 引入,来取代 Qt 3 中的 QCanvas。当然,在最新的 Qt5 中,Qt3 的代码已经不能继续使用了(尽管在一定程度上, Qt4 还是可以使用这些遗留代码)。

Graphics View 是一个基于元素(item)的 MV 架构的框架。它可以分成三个部分:元素 item、场景 scene 和视图 view。

基于元素的意思是,它的每一个组件都是一个独立的元素。这是与我们之前讲到过的QPainter状态机机制不同。回忆一下,使用QPainter绘图,大多是采用一种面向过程的描述方式:首先使用drawLine()画一条直线,然后使用drawPolygon()画一个多边形。对于 Graphics View,相同的过程可以是,首先创建一个场景(scene),然后创建一个直线对象和一个多边形对象,再使用场景的add()函数,将直线和多边形添加到场景中,最后通过视图进行观察,就可以看到了。乍看起来,后者似乎更加复杂,但是,如果你的图像中包含了成千上万的直线、多边形之类,管理这些对象要比管理QPainter的绘制语句容易得多。并且,这些图形对象也更加符合面向对象的设计要求:一个很复杂的图形可以很方便的复用。

MV 架构的意思是,Graphics View 提供一个 model 和一个 view(正如 MVC 架构,只不过 MV 架构少了 C 这么一个组件)。所谓模型(model)就是我们添加的种种对象;所谓视图(view)就是我们观察这些对象的视口。同一个模型可以由很多视图从不同的角度进行观察,这是很常见的需求。使用 QPainter 很难实现这一点,这需要很复杂的计算,而 Graphics View 可以很容易的实现。

Graphics View 提供了QGraphicsScene作为场景,即是允许我们添加图形的空间,相当于整个世界;QGraphicsView作为视口,也就是我们的观察窗口,相当于照相机的取景框,这个取景框可以覆盖整个场景,也可以是场景的一部分;QGraphicsItem作为图形元件,以便添加到场景中去,Qt 内置了很多图形,比如直线、多边形等,它们都是继承自QGraphicsItem

下面我们通过一段代码看看 Graphics View 的使用。

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

    QGraphicsScene scene;
    scene.addLine(0, 0, 150, 150);

    QGraphicsView view(&scene);
    view.setWindowTitle("Graphics View");
    view.resize(500, 500);
    view.show();

    return app.exec();
}

这段代码很简单:首先创建一个场景,也就是QGraphicsScene对象。然后我们使用addLine()函数向场景中添加了一个直线,起始点和终点坐标分别是 (0, 0) 和 (150, 150)。可以想象,这是一个边长 150px 的正方形的对角线。通过这两步,我们已经有了场景和元素。之后,我们创建一个GraphicsView对象,绑定到一个场景上(也就是我们前面创建的 scene 对象)。注意,QGraphicsScene不是QWidget的子类,因此该构造函数并不是调用的QGraphicsView(QWidget *parent)。接下来,我们可以运行一下代码:

Graphics View 示例

我们看到,这个直线自动在视图居中显示。这并不需要我们进行任何额外的代码。如果不想这么做,我们可以给 scene 设置一下sceneRect()属性:

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 300, 300);
    scene.addLine(0, 0, 150, 150);

    QGraphicsView view(&scene);
    view.setWindowTitle("Graphics View");
    // view.resize(500, 500);
    view.show();

不仅如此,我们还去掉了view.resize()一行。QGraphicsScenesceneRect属性供QGraphicsView确定视图默认的滚动条区域,并且协助QGraphicsScene管理元素索引。之所以去掉view.resize()一行,是因为我们让系统去决定视图的最小尺寸(否则的话,我们需要手动将窗口标题栏等的大小同时考虑设置)。

27 评论

mogu 2012年12月12日 - 21:59


qDebug() << "谢谢!" << endl;

回复
rekols 2017年9月13日 - 19:28

不用endl

回复
safdfa 2012年12月13日 - 11:10

很不错,刚好过几天可能就要用上相关功能,受教了

回复
锁骨断了 2012年12月24日 - 09:09

Qt从最开始只有MainWindow到Qt3的canvas,到Qt4的GraphicsView,再到Qt5的SceneGraph,其实就是一个绘图系统的演化史,最开始只有纯软件的Framebuffer,速度上也很慢,就只能支持MainWindow和QDialog。
后来有了surface的概念,有了Blit加速/alpha混合的2d加速系统,再后来又有了SVG这个矢量加速系统,于是就有了Qt3、4那个时代的演进 😎 。
现在的低端机顶盒大多都还只支持Blit加速/alpha混合的2d加速系统,但是高端的已经支持opengles2了,其他的嵌入式平台也大多如此。
于是到了Qt5,就选用SenceGraph作为新一代的架构,SenceGraph现在已经是opengl上面很成熟的一套架构了,有一个叫OpenSenceGraph的开源项目很受瞩目,到它的官网上能看到许多惊艳的3d实现

回复
DevBean 2012年12月24日 - 09:30

感谢分享啊!很有深度的

回复
andmoe 2013年1月15日 - 16:19

帧缓冲不是只有linux和嵌入式linux才能用吗?

回复
路过 2013年4月1日 - 21:22

说实话,看到这,我几乎对Qt都没什么印象,那么多东西。

回复
枫亦 2013年7月10日 - 14:33

这么重要的内容,我认为博主应该要下大力气来讲解, 尤其是QWidget体系与QGraphicsWidget体系之间的区别,我写同样功能的程序明显感觉用后者内存开销要小很多。这一部分内容qt文档相当欠缺,包括《c++ GUI Qt4编程(第二版)》中感觉也是一笔带过,没有讲解清楚。

回复
豆子 2013年7月10日 - 22:13

这部分内容太复杂,各种应用都可以由此实现,也很难深入下去,只能具体问题具体分析。QWidget 体系比 GVF 更高级,QWidget 提供了很多窗口系统的功能,而这些功能在 GVF 中只能自己一个个实现。比如一个简单的关闭窗口,QWidget 几乎不用额外的代码,而 GVF 就必须首先判断是不是点击了关闭按钮,然后再将组件从场景中移除,这些代码都必须自己实现。本质上来说,GVF 只是一个绘图系统,所以效率肯定要比 QWidget 系统高。KDE 的窗口基本就是使用 GVF 实现的,只不过你需要把一个窗口系统中所有功能都自己实现一遍。这就是运行效率和开发效率的一个权衡。所以,如果没有一个合适的主题,这部分内容很难深入下去。

回复
枫亦 2014年7月23日 - 11:11

可以与你的29(绘图设备)进行比较讲解会清楚一点,把这套框架的绘图脉络给理清楚。比如官网上有Paint System的讲解:http://qt-project.org/doc/qt-5/paintsystem.html

回复
朦朦胧胧 2014年4月3日 - 10:52

QGraphicsScene的sceneRect属性供QGraphicsView确定视图默认的滚动条区域,
请问这里说的滚动条区域是什么意思?
另外,测试这段代码发现这条线确实并不是居中,当窗口放大时,线条的位置也跟着变动,请问是根据“什么”来确定当窗口大小被改变时,线条的位置是如何被计算的?

回复
豆子 2014年4月3日 - 16:35

滚动条区域就是下面用于显示的区域;至于根据哪些规则确定线条位置,这一点也不清楚。

回复
lidongen 2014年4月6日 - 13:06

豆子高手,您好,又要向您请教了。
PV_Topology 是 QGraphicsView 控件
QGraphicsScene *scene;
scene = new QGraphicsScene(0,0,800,480);
QPixmap pixmap(“./pv1.png”);
scene->addPixmap(pixmap);
ui->PV_Topology->setScene(scene);
这样图片在PV_Topology里就显示出来了。 但是 PV_Topology的槽函数里没有“click”或者“dbclick”槽函数,请问如何实现鼠标双击这个PV_Topology控件时的信号响应呢?

回复
豆子 2014年4月8日 - 10:51

鼠标双击是 mouseDoubleClickEvent 事件,不是信号。

回复
sherry 2014年5月5日 - 09:46

豆子,您好。请问QGraphicsScene的坐标原点在哪?它的y轴是向下的吗?

回复
豆子 2014年5月5日 - 15:41

场景的原点在场景的中心;y轴方向还真不清楚,因为与 item 和 view 都有一定的关系,这个坐标也是可以变换的。所以最好先尝试一下吧 ;-P

回复
delifue 2014年7月15日 - 10:46

请问如何将QGraphicsView中的坐标转换为qt窗口部件用的坐标?

回复
豆子 2014年7月16日 - 11:12

QGraphicsView 的坐标与组件坐标没有关系,不知道你说的组件是只什么?

回复
delifue 2014年7月21日 - 11:26

这里的组件就是指QLabel,QPushButton之类的QWidget,他们的坐标以左上角为原点,y值向下增大。

回复
豆子 2014年7月25日 - 23:27

使用 QGraphicsWidget 可以将 widgets 添加到 scene。因为 QGraphicsWidget 与 QGraphicsItem 是对应的,因此个人感觉应该按照 item 相似的坐标转换就可以了。不过没有测试,不知道实际是否如此。

回复
wuqi 2014年9月1日 - 15:44

实测,如果用QGraphicsItem的话,百万级估计做不到,内存占用太厉害了..四十万的线绘制占用大概2G内存,六万的面绘制占用大概四百兆内存...所以绘制矢量方式还是得画到QpixMap之类的东西上,贴上去,然后更新...

回复
happly 2014年10月19日 - 12:30

你真是奇怪,QpixMap是用于显示图像,GVF是管理图元,两种根本就不是同一个东西,试问你打算怎么用QpixMap来处理其中某条线?例如编缉你的第25685条line时?

回复
happly 2014年10月17日 - 03:10

请问如何给 QGraphicsLineItem * line; 设置反走样效果???

回复
豆子 2014年10月19日 - 22:56

可以直接通过 QGraphicsView::setRenderHints() 函数进行设置。

回复
happly 2014年10月26日 - 13:34

可是这么一来岂不是场景内所有item都反走样了吗?
看来也只能选择够承了

回复
豆子 2014年10月27日 - 10:08

如果需要只对某一种 item 反走样,只能选择继承,自己提供 paint 函数了。

回复
xie 2015年6月11日 - 12:14

豆哥,请教一个问题,现在要做一个类似手机主屏幕的窗口,最上面一栏显示一些电量图示,手机信号大小等一些小图标,最下面显示类似状态栏,中间是类似软件APP的一些图标,中间整个窗口可以滑动。怎么实现?指教以下
谢谢!

回复

回复 豆子 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2