欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Qt之图形视图框架

程序员文章站 2024-03-26 09:48:59
...

简介

图形视图提供了基于项的模型—视图编程方式。用于管理大量的2D图形项。

图形视图使用BSP树来提供对图形项的快速查找,使用这种技术,它可以实时地绘制大规模场景,甚至以百万图形项计。

图形视图在Qt 4.2中被引用,它替代了它的前辈QCanvas。


体系结构

体系结构主要由以下三部分组成
- 场景(Scene)
- 视图(View)
- 项(Item)

(1)场景

QGraphicsScene提供了图形视图的场景,场景承担下列的责任:

  1. 提供一个快速的接口用来管理大量的项。
  2. 向每个项传递事件。
  3. 管理项的状态,如选中、焦点处理。
  4. 提供无变形的展示功能,主要为了打印。

场景作为QGraphicsItem对象的容器,项可以调用QGraphicsScene::addItem()加入场景。

QGraphicsScene::items()和它的重载可以返回所有的项,包括点、长方形、多边形、通用矢量路径。

QGraphicsScene::itemAt()返回在特定点上最上边的项。(如下例子所示)

QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));
QGraphicsItem *item = scene.itemAt(50, 50);

QGraphicsScene的事件传递架构,给场景事件确定时间表,用于管理项之间的传递。如果场景在某个位置收到一个鼠标按下事件,场景将把该事件传递给在那个位置的项。

QGraphicsScene也管理某些项状态,如项选择和聚焦。

你可以在场景上通过QGraphics::setSelectionArea()选择项,传递任意的形状。这个功能也被在QGraphicsView中用做橡皮擦选项。

另外一个由QGraphicsScene处理的状态是一个项是否有键盘输入聚焦。你可以通过调用QGraphics::setFocusItem()或QGraphicsItem::setFocus()来聚焦一个项,或者通过调用QGraphicsScene::focusItem()来获得当前的聚焦。

最后,QGraphicsScene允许通过QGraphicsScene::render()函数将场景的某一部分展示到一个绘画设备中。你可以在以后的打印段落中看到更多的相关内容。

(2)视图

QGraphicsView提供了视图窗口部件,它使场景的内容可视化。

你可以给一个场景多个视图,从而针对同样的数据集提供几个视图端口。视图窗口部件是滚动区域,对大型的场景图提供滚动的浏览方式。

为了支持OpenGL,你可以通过调用QGraphicsView::setViewport()l来设置一个QGLWidget作为视图端口。(如下所示)

QGraphicsScene scene;
myPopulateScene(&scene);
QGraphicsView view(&scene);
view.show();

视图接收来自键盘和鼠标的输入事件,并在发送事件给可视化的场景之前,将它们转化为场景事件(将坐标转化为适当的场景坐标)。

使用变换矩阵,QGraphicsView::matrix(),视图可以变换场景的坐标系统,以便处理高级的浏览特性,如缩放和旋转。为了方便,QGraphicsView也提供视图和场景坐标之间转换的函数:

QGraphicsView::mapToScene()
QGraphicsView::mapFromScene()

(3)项

QGraphicsItem是场景中图形项的基类。

图形视图提供了几个典型形状的标准项,如长方形QGraphicsRectItem,椭圆形QGraphicsEllipseItem和文本项QGraphicsTextItem。

当你撰写客户化项时,QGraphicsItem的强大特性就体现出来了,除此之外,QGraphicsItem还支持下面的特性:

  • 鼠标按压,移动,释放,双击事件,以及鼠标钩子事件(hover events),滚轮事件(wheel events),上下文事件(context menu events)
  • 键盘输入聚焦,特定键事件
  • 拖放
  • 成组(Grouping),通过父子关系或者用QGraphicsItemGroup
  • 碰撞侦测

QGraphicsItem像QGraphicsView一样,存在于局部坐标系统(local coordinate system)中,它提供了很多函数用于在项和场景之间、项与项之间进行坐标映射。

另外,和QGraphicsView一样,它通过QGraphicsItem::matrix()函数变换它的坐标系统。这在旋转和缩放单个项时非常有用。

QGraphicsItem通过QGRaphicsItem::shape()函数和QGraphicsItem::collidesWith()函数支持碰撞侦测,这两个都是虚函数。
通过返回项形状的局部坐标,QGRaphicsItem::shape()函数和QGraphicsItem::collidesWith()函数的QPainterPath将处理所有的碰撞侦测。如果你想提供自己的碰撞侦测,则可以重新实现QGraphicsItem::collidesWith()函数。


坐标系统

图形视图基于笛卡儿坐标系统(Cartesian coordinate system),项的位置和在场景中的几何形状由两个数字组合代表:X坐标和Y坐标。当使用变换的视图观察一个场景时,场景中的一个单元会由屏幕上的一个点表示。

有3个有效的坐标系统来演绎图形视图:项坐标、场景坐标和视图坐标。为了简化你的实现,图形视图提供了方便的函数允许你在三个坐标系统之间映射。

当图形展示时,图形视图的场景坐标对应了QPainter的逻辑坐标,视图坐标对应了设备的坐标。在坐标系统(The Coordinate System)文章中,介绍了逻辑坐标(logical coordinate)和设备坐标(device coordinate)的关系。

坐标系统主要有以下四部分:
- 项坐标
- 场景坐标
- 视图坐标
- 坐标映射

(1)项坐标

项总是在它们自己的局部坐标中。它们的坐标一般是围绕它们的中心点(0,0),并且这个中心也是左右变换的中心。项坐标系统中的简单几何件一般是指项点、项线、项长方形。

当创建客户化项时,项坐标是你要考虑的全部。

QGraphicsScene和QGraphicsView会为你实现所有的变换,这让实现客户化项变得相当容易。
例如,如果你收到了鼠标按下或拖动的事件,该事件的位置点是由项坐标系统给出的,QGraphicsItem::contains()得到一个项坐标的点参数,若这个点在项中,则返回真,否则,返回假。同样的,项绑定的矩形或形状区域也是项坐标系统的。

项的位置是指在它的父坐标系统中,项的中心点的坐标,有时候称父坐标。场景从这个意义上说是所有无父项的“parent”,顶层项的位置是在场景坐标中。

子坐标是相对于父坐标而言的,如果子坐标没有变换,那么子坐标和父坐标的差异是和项在父坐标中的位置一样的。如:如果一个没有变换的子项正好位于其父项的中心点位置,那么,这两个项的坐标系统是完全一样的。如果子项的位置是(10,0),那么,子项的(0,10)点对应了父项的(10,10)点。

因为项的位置和变换是相对于父项的,因此,虽然父项的坐标变换隐含了子项的变换,但是,子项的坐标不受父项坐标变换的影响。

在上述例子中,即使父项旋转或缩放了,子项的(0,10)点始终对应父项的(10,10)点。相对于场景,子项将跟随场景的变换和定位。如果父项放大2倍,(2X,2X),子项在场景坐标中的位置将是(20,0),它的(10,0)点将对应场景中的(40,0)点。

除了QGraphicsItem::pos()作为很少的例外,QGraphicsItem的函数操作都是在项坐标是操作,而不论项或者它的父项是否已经做了坐标变换。如:一个项的绑定矩形(即QGraphicsItem::boundingRect())总是给出项坐标。

(2)场景坐标

场景为所有的项提供了基本的坐标系统。

场景坐标系统描述了顶层项的位置,并给通过视图传递给场景的所有事件提供了基本的坐标体系。
场景中的每一个项,除了它自己的局部项位置和绑定矩形外,都有一个场景位置和绑定矩形(QGraphicsItem::scenePos(),QGraphicsItem::sceneBoundingRect())。

场景位置描述了项在场景坐标中的位置,场景绑定矩形使得QGraphicsScene确定场景中需要变化的区域。场景的变化通过QGraphicsScene::changed()信号来通讯,参数就是场景的绑定矩形。

(3)视图坐标

视图坐标是窗口部件的坐标,视图坐标中的每个坐标对应了一个象素。

有关这个坐标系统特殊的是它是相对于窗口部件的,或视图端口(viewport),而不被所观察的场景所影响。

QGraphicsView视图端口的左顶角总是(0,0),右底角总是(视宽,视高)。所有鼠标事件和拖放事件最初始总是以视图坐标收到的,你需要将它影射成场景坐标以方便和项交互。

(4)坐标映射

在处理场景中的项时,经常使用从场景到项,从项到项,从视图到场景的坐标和任意形状的映射。

例如:当你在视图端口按压鼠标时,可以通过调用QGraphicsView::mapToScene()函数,再跟上QGraphicsScene::itemAt()函数告诉场景那个项在当前光标下。也可以通过在项上调用QGraphicsItem::mapToScene()函数,然后是QGraphicsView::mapToScene()函数,获知该项在视图端口的什么位置。最后,若要知道项是否在视图椭圆中,你可以向mapToScene()传递QPainterPath,向QGraphicsScene::items()函数传递mapped path来实现。

通过调用QGraphicsItem::mapToScene()和QGraphicsItem::mapFromScene()函数,可以映射任意的坐标和形状。也可以调用QGraphicsItem::mapToParent()和QGraphicsItem::mapFromParent ()函数映射到父项。所有的映射函数可以映射点、长方形、多边形和路径。

同样的,在视图中有同样的映射函数,将坐标和形状映射到场景。QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。要从视图映射到项,你需要先映射到场景,再从场景映射到项。


主要特性

  • 缩放和旋转

QGraphicsView通过QGraphicsView::setMatrix(),象QPainter一样,支持仿射变换。通过给视图增加变换应用,可以很容易地给普通的浏览增加如缩放和旋转的特性。

下面是一个例子,说明在QGraphicsView子类中如何实现缩放和旋转的槽。

class View : public QGraphicsView
 {
 Q_OBJECT
     ...
 public slots:
     void zoomIn() { scale(1.2, 1.2); }
     void zoomOut() { scale(1 / 1.2, 1 / 1.2); }
     void rotateLeft() { rotate(-10); }
     void rotateRight() { rotate(10); }
     ...
};

槽需要被联系到QToolButtons,并使它的autoRepeat使能。
当你变换视图时,QGraphicsView保持视图的中心成直线。
可以参见“Elastic Node”例子,学习如何实现基本的缩放特性。

  • 打印

图形视图通过它的展示函数:QGraphicsScene::render()和QGraphicsView::render()提供单线(single-line)打印。
函数提供相同的API,通过将QPainter传递给展示函数,你可以打印场景、视图的全部或部分内容。
例子显示了如何使用QPainter将场景的全部内容打印到整页纸上。

QGraphicsScene scene;
 scene.addRect(QRectF(0, 0, 100, 200), Qt::black, QBrush(Qt::green));

 QPrinter printer;
 if (QPrintDialog(&printer).exec() == QDialog::Accepted) {
     QPainter painter(&printer);
     painter.setRenderHint(QPainter::Antialiasing);
     scene.render(&painter);
 }

场景和视图函数展示函数的差异是一个在场景坐标,另一个在视图坐标。

QGraphicsScene::render()常用于打印无变换的场景的全部内容,如画几何数据文档等。QGraphicsView::render()适合于打印屏幕快照(screenshots),缺省情况下,它展示视图端口中的当前内容。

QGraphicsScene scene;
 scene.addRect(QRectF(0, 0, 100, 200), Qt::black, QBrush(Qt::green));

 QPixmap pixmap;
 QPainter painter(&pixmap);
 painter.setRenderHint(QPainter::Antialiasing);
 scene.render(&painter);
 painter.end();

 pixmap.save("scene.png");

当源区域和目标区域的大小不匹配时,源内容进行伸展以适合目标区域。通过传递Qt::AspectRatioMode给你正调用的展示函数,你可以在源内容伸缩时,保持或忽略纵横比。

  • 拖放

因为QGraphicsView间接继承了QWidget,它也同样提供QWidget提供的拖放功能。

另外,为了方便,图形视图架构给场景、每一个项提供了拖放支持。当视图收到一个拖动作,它将拖放事件发给QGraphicsSceneDragDropEvent,它再发给场景,场景对事件按时序排列,并发给光标下的第一个项来接受放置。

为了从一个项上开始拖动作,要产生一个QDrag对象,将指针传给开始拖的那个窗口部件。在同一时刻,项可以被很多视图观察到,但是,只有一个视图可以开始拖动作。

拖动作在大多数情况下是因为鼠标按下和提供而触发开始的,因此,在mousePressEvent()和mouseMoveEvent()函数中,你可以从事件中得到起始窗口部件的指针。如下面的例子:

void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
 {
     QMimeData *data = new QMimeData;
     data->setColor(Qt::green);

     QDrag *drag = new QDrag(event->widget());
     drag->setMimeData(data);
     drag->start();
 }

为了给场景截取拖放事件,在QGraphicsItem子类中,需要重实现QGraphicsScene::dragEnteEvent()函数,哪个事件处理你特殊的场景需要。

你可以在图形视图拖放操作文档中,查看每一个QGraphicsScene的事件句柄来学习更多的知识。
参见“Drag and Drop”例子,它是一个在图形视图中支持拖放操作的演示。

  • 光标和提示

象QWidget一样,QGraphicsItem也提供光标支持(QGraphicsItem::setCursor()),提示支持(QGraphicsItem::setToolTip())。当鼠标的光标进入项的区域时(调用QGraphicsItem::contains()检测),光标和提示由QGraphicsView**。
你可以调用QGraphicsView::setCursor()直接在视图上设置一个缺省光标。
参见“Drag and Drop” 例子中,提示和光标形状处理的实现。

  • 动画

视图在几个层面支持动画。你可以通过将QGraphicsItemAnimation和项关联,来简单地组装动画路径,这种方式允许以时间线方式动画在所有平台上都可以以一个稳定的速度操作(帧速会因平台的性能而改变)。

QGraphicsItemAnimation允许为项的位置、旋转、尺寸缩放、裁减、变换创建路径。动画可以被QSlider控制,或者普遍的是被QTimeLine控制。

另外一个方式是创建一个项,它从QObject和QGraphicsItem继承而来。这个项可以建立它自己的定时器,在QObject::timeEvent()中控制动画的进阶步骤。

第三种方式,这种方式大部分是为了和Qt3中的QCanvas保持兼容,调用QGraphicsScene::advance()来推进(advance)场景,进而调用QGraphicsItem::advance()推进项。
参见“Drag and Drop”例子中关于基于时间线技术动画的演示。

  • OpenGL展示
    为了使用OpenGL展示,你只要简单地调用QGraphicsView::setViewport()来设置一个新的QGLWidget作为QGraphicsView的视图端口。
    如果你想要OpenGL具有无锯齿特性,你需要OpenGL采样缓冲支持(参见QGLFormat::sampleBuffers())。
    例子:
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
  • 项的成组

通过使一个项成为另外一个项的子项,你可以得到有关项成组的关键特性:这些项会一起移动,所有的变换都从父项传递给子项。

QGraphicsItem能够为它的子项处理所有的事件(参见QGraphicsItem::setHandlesChildEvents())。这允许父项代表子项动作,可以有效地将所有子项当作一个整体。

另外,QGraphicsItemGroup是一个特殊的项,它聚合了子项事件处理,并有一个有用的接口用来在组中增加和移除项。向QGraphicsItemGroup将保持项的原始位置和变换,而重父化项会引起子项的重定位,因为新的父项的关系。

未来方便,你可以通过场景调用QGraphicsScene::createItemGroup()来创建QGraphicsItemGroups。

  • 窗口部件和布局

Qt4.4通过QGraphicsWidget引入了对几何和布局感应的支持。这个特殊的基类与QWidget相似,但又不同。它不从QPaintDevice继承,而是从QGraphicsItem继承。

这允许你撰写具有事件、信号和槽、尺寸线索和策略的完整窗口部件,你也能通过QGraphicsLinearLayout和QGraphicsGridLayout管理布局中的窗口部件的几何特性。

QGraphicsWidget创建在QGraphicsItem之上,它提供了几个方面的结合:相对于QWidget的格外功能,如风格、字体、调色板、布局方向、几何表现和从QGraphicsItem继承的分辨率无关性和变换支持。

因为GraphicsView使用实数坐标而不是整数坐标,QGraphicsWidget的几何特性功能也是在QRectF和QPointF上操作。这个也应用到框架矩形、空白边缘和间隙。对于QGraphicsWidget,规定内容边缘为(0.5,0.5,0.5,0.5)并不是不平常的(很平常)。

例如,你可以创建顶层窗口和子窗口部件,在一些情况下,你现在可以用Graphics View写高级的多文档应用。

一些QWidget的属性被支持了,包括窗口标志和属性,但不是全部。

你可以参见QGraphicsWidget的类文件以全面了解它支持或不支持什么。

例如,你可以通过Qt::Window窗口标志给QGraphicsWidget的构造函数创建装饰过的窗口,但是,当前的Graphics View不支持在Mac OS X中和普通的Qt::Sheet和Qt::Drawer标志。

QGraphicsWidget的性能将以来社区的反馈不断增强。

QGraphicsLayout是专为QGraphicsWidget设计的第二代布局框架。它的API与QLayout很相似。

你可以在QGraphicsLinearLayout和QGraphicsGridLayout内管理窗口部件和布局。

你可以通过QGraphicsLayout的子类很容易写你的布局,或者通过改编QGraphicsLayoutItem子类给布局增加你自己的QGraphicsItems项。

  • 嵌入式窗口部件支持

Graphics View为在场景中嵌入任何窗口部件提供了无缝的支持。

你可以嵌入简单窗口部件如QLineEdit或QPushButton,到复杂的窗口部件如QTabWidget,或是完整的主窗口。

为了将你的窗口部件嵌入场景,可以简单地调用QGraphicsScene::addWidget(),或创建一个QGraphicsProxyWidget实例来手动嵌入你的窗口部件。

通过QGraphicsProxyWidget,Graphics View将能够深入地集成客户窗口部件特性,包括它的光标、提示、鼠标、tablet、键盘事件、子窗口部件、动画、弹出式部件(即QComboBox或QCompleter)、窗口部件的输入聚焦和**。

QGraphicsProxyWidget也能集成嵌入窗口部件的tab顺序。你甚至可以嵌入一个新的QGrasphicsView到场景中形成复杂的嵌套场景。

当对一个嵌入式窗口部件变换坐标时,Graphics View需要确信窗口部件被分辨率无关地变换了,而允许字体和风格在放大时保持脆弱的。(注意:分辨率无关的影响依赖于风格。)