图形视图框架

图形视图提供用于管理大量定制 2D 图形项并与之交互的表面,和用于可视化这些项的视图 Widget (支持缩放和旋转)。

框架包括事件传播体系结构,允许将精确双精度交互能力用于场景项。项可以处理按键事件,鼠标按下、移动、释放及双击事件,且它们还可以跟踪鼠标移动。

图形视图使用 BSP (二进制空间分区) 树以提供非常快速的项探索,因此,它可以实时可视化大型场景,甚至具有数百万的项。

图形视图在 Qt 4.2 引入,替换其前身 QCanvas。

话题:

图形视图体系结构

图形视图提供基于项的模型/视图编程途径,非常像 InterView 方便类 QTableView , QTreeView and QListView 。多个视图可以观测一个场景,且场景包含各种几何形状的项。

场景

QGraphicsScene 提供图形视图场景。场景有以下职责:

  • 为管理大量项提供快速接口
  • 把事件传播到各项
  • 管理项状态 (譬如:选定和聚焦处理)
  • 提供未变换渲染功能;主要用于印刷

场景充当容器为 QGraphicsItem 对象。项被添加到场景通过调用 QGraphicsScene::addItem (),然后通过调用许多项探索函数之一检索。 QGraphicsScene::items () 及其重载返回点、矩形、多边形或一般向量路径所包含或与之相交的所有项。 QGraphicsScene::itemAt () 返回特定点的最顶项。所有项探索函数按降序堆叠次序返回项 (即:第一返回项最顶,最后项最底)。

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

QGraphicsScene 的事件传播体系结构调度要交付给项的场景事件,并管理项之间的传播。若场景在某个位置收到鼠标按下事件,场景将事件传递给该位置的任何项。

QGraphicsScene 还管理某些项状态 (譬如:项选定和聚焦)。可以选择场景中的项通过调用 QGraphicsScene::setSelectionArea (),传递任意形状。此功能还用作选择橡皮筋的基础在 QGraphicsView 。要获取所有目前选中项的列表,调用 QGraphicsScene::selectedItems ()。另一状态的处理通过 QGraphicsScene 为项是否拥有键盘输入聚焦。可以设置项聚焦通过调用 QGraphicsScene::setFocusItem () 或 QGraphicsItem::setFocus (),或获取当前聚焦项通过调用 QGraphicsScene::focusItem ().

最后, QGraphicsScene 允许将场景部分渲染进绘图设备透过 QGraphicsScene::render () 函数。可以在此文档稍后的打印章节阅读更多有关这。

视图

QGraphicsView provides the view widget, which visualizes the contents of a scene. You can attach several views to the same scene, to provide several viewports into the same data set. The view widget is a scroll area, and provides scroll bars for navigating through large scenes. To enable OpenGL support, you can set a QGLWidget as the viewport by calling QGraphicsView::setViewport ().

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

视图从键盘和鼠标接收输入事件,并将这些事件转换为场景事件 (将所用坐标转换为合适场景坐标),在将事件发送给可视化场景之前。

使用其变换矩阵, QGraphicsView::transform (),视图可以 transform 场景的坐标系。这允许高级导航特征 (譬如:缩放和旋转)。为方便起见, QGraphicsView 还提供视图和场景坐标之间的翻译函数: QGraphicsView::mapToScene () 和 QGraphicsView::mapFromScene ().

QGraphicsItem 是场景图形项的基类。Graphics View 为典型形状提供了几种标准项,譬如矩形 ( QGraphicsRectItem ),椭圆 ( QGraphicsEllipseItem ) 和文本项 ( QGraphicsTextItem ),但最强大的 QGraphicsItem 特征是可用的,当编写自定义项时。除其它事情外, QGraphicsItem 支持下列特征:

  • 鼠标按下、移动、释放和双击事件,及鼠标悬停事件、滚轮事件和上下文菜单事件。
  • 键盘输入聚焦和键事件
  • 拖放
  • 分组,透过父级/子级关系和采用 QGraphicsItemGroup
  • 碰撞检测

项活在本地坐标系中,且像 QGraphicsView ,它还提供了用于在项和场景之间,从项到项映射坐标的许多函数。另外,像 QGraphicsView ,它可以使用矩阵变换其坐标系: QGraphicsItem::transform ()。这对旋转和比例缩放单个项很有用。

项可以包含其它项 (子级)。父级项的变换由其所有子级继承。虽然可以不管项的累计变换,它的所有函数 (如 QGraphicsItem::contains (), QGraphicsItem::boundingRect (),QGraphicsItem::collidesWith()) 仍运转在本地坐标。

QGraphicsItem 支持碰撞检测透过 QGraphicsItem::shape () 函数,和 QGraphicsItem::collidesWith(),两者都是虚函数。通过将项的形状返回作为本地坐标 QPainterPath from QGraphicsItem::shape (), QGraphicsItem 将为您处理所有碰撞检测。不管怎样,若想要提供自己的碰撞检测,可以重实现 QGraphicsItem::collidesWith()。

在图形视图框架中的类

这些类提供用于创建交互应用程序的框架。

QAbstractGraphicsShapeItem 用于所有路径项的公共基
QGraphicsAnchor 表示 QGraphicsAnchorLayout 中 2 项之间的锚点
QGraphicsAnchorLayout 可以在图形视图中将 Widget 锚定在一起的布局
QGraphicsEffect 用于所有图形效果的基类
QGraphicsEllipseItem 可以添加到 QGraphicsScene 的椭圆项
QGraphicsGridLayout 用于在图形视图中管理 Widget 的栅格布局
QGraphicsItem QGraphicsScene 中所有图形项的基类
QGraphicsItemGroup 将一组项视为单项的容器
QGraphicsLayout 基类为图形视图中的所有布局
QGraphicsLayoutItem 可以被继承以允许自定义项由布局进行管理
QGraphicsLineItem 可以添加到 QGraphicsScene 的线项
QGraphicsLinearLayout 用于在图形视图中管理 Widget 的水平或垂直布局
QGraphicsObject 基类用于所有要求信号、槽及特性的图形项
QGraphicsPathItem 可添加到 QGraphicsScene 的路径项
QGraphicsPixmapItem 可以添加到 QGraphicsScene 的像素图项
QGraphicsPolygonItem 可以添加到 QGraphicsScene 的多边形项
QGraphicsProxyWidget 用于将 QWidget 嵌入 QGraphicsScene 的代理层
QGraphicsRectItem 可以添加到 QGraphicsScene 的矩形项
QGraphicsScene 用于管理大量 2D 图形项的表面
QGraphicsSceneContextMenuEvent 在图形视图框架中的上下文菜单事件
QGraphicsSceneDragDropEvent 用于图形视图框架的拖放事件
QGraphicsSceneEvent 基类为所有图形视图相关事件
QGraphicsSceneHelpEvent 当请求工具提示时的事件
QGraphicsSceneHoverEvent 在图形视图框架中的悬停事件
QGraphicsSceneMouseEvent 在图形视图框架中的鼠标事件
QGraphicsSceneMoveEvent 用于在图形视图框架中移动 Widget 的事件
QGraphicsSceneResizeEvent 用于在图形视图框架中重置 Widget 大小的事件
QGraphicsSceneWheelEvent 在图形视图框架中的滚轮事件
QGraphicsSimpleTextItem 可添加到 QGraphicsScene 的简单文本路径项
QGraphicsSvgItem 用于渲染 SVG 文件内容的 QGraphicsItem
QGraphicsTextItem 可添加到 QGraphicsScene,以显示格式化文本的文本项
QGraphicsTransform 抽象基类用于在 QGraphicsItems 构建高级变换
QGraphicsView 用于显示 QGraphicsScene 内容的 Widget
QGraphicsWidget 基类用于 QGraphicsScene 中的所有 Widget 项
QStyleOptionGraphicsItem 用于绘制 QGraphicsItem 的所需描述参数

图形视图坐标系

图形视图基于笛卡尔坐标系;场景中项的位置和几何图形由 2 数字的集表示:X 坐标和 Y 坐标。当使用未转换视图观测场景时,一屏幕像素表示一场景单位。

注意: 倒置 Y 轴坐标系 (在哪里 y 向上增长) 不被支持,因为图形视图使用 Qt 的坐标系。

图形视图有 3 种有效坐标系:项坐标、场景坐标及视图坐标。为简化实现,图形视图提供了允许在 3 种坐标系之间映射的方便函数。

当渲染时,图形视图的场景坐标对应 QPainter 's logical 坐标,和视图坐标如同 device 坐标。在 坐标系 文档编制,可以了解有关逻辑坐标和设备坐标之间的关系。

项坐标

Items live in their own local coordinate system. Their coordinates are usually centered around its center point (0, 0), and this is also the center for all transformations. Geometric primitives in the item coordinate system are often referred to as item points, item lines, or item rectangles.

When creating a custom item, item coordinates are all you need to worry about; QGraphicsScene and QGraphicsView will perform all transformations for you. This makes it very easy to implement custom items. For example, if you receive a mouse press or a drag enter event, the event position is given in item coordinates. The QGraphicsItem::contains () virtual function, which returns true if a certain point is inside your item, and false otherwise, takes a point argument in item coordinates. Similarly, an item's bounding rect and shape are in item coordinates.

At item's position is the coordinate of the item's center point in its parent's coordinate system; sometimes referred to as parent coordinates. The scene is in this sense regarded as all parent-less items' "parent". Top level items' position are in scene coordinates.

Child coordinates are relative to the parent's coordinates. If the child is untransformed, the difference between a child coordinate and a parent coordinate is the same as the distance between the items in parent coordinates. For example: If an untransformed child item is positioned precisely in its parent's center point, then the two items' coordinate systems will be identical. If the child's position is (10, 0), however, the child's (0, 10) point will correspond to its parent's (10, 10) point.

Because items' position and transformation are relative to the parent, child items' coordinates are unaffected by the parent's transformation, although the parent's transformation implicitly transforms the child. In the above example, even if the parent is rotated and scaled, the child's (0, 10) point will still correspond to the parent's (10, 10) point. Relative to the scene, however, the child will follow the parent's transformation and position. If the parent is scaled (2x, 2x), the child's position will be at scene coordinate (20, 0), and its (10, 0) point will correspond to the point (40, 0) on the scene.

采用 QGraphicsItem::pos () being one of the few exceptions, QGraphicsItem 's functions operate in item coordinates, regardless of the item, or any of its parents' transformation. For example, an item's bounding rect (i.e. QGraphicsItem::boundingRect ()) is always given in item coordinates.

场景坐标

The scene represents the base coordinate system for all its items. The scene coordinate system describes the position of each top-level item, and also forms the basis for all scene events delivered to the scene from the view. Each item on the scene has a scene position and bounding rectangle ( QGraphicsItem::scenePos (), QGraphicsItem::sceneBoundingRect ()), in addition to its local item pos and bounding rectangle. The scene position describes the item's position in scene coordinates, and its scene bounding rect forms the basis for how QGraphicsScene determines what areas of the scene have changed. Changes in the scene are communicated through the QGraphicsScene::changed () signal, and the argument is a list of scene rectangles.

视图坐标

View coordinates are the coordinates of the widget. Each unit in view coordinates corresponds to one pixel. What's special about this coordinate system is that it is relative to the widget, or viewport, and unaffected by the observed scene. The top left corner of QGraphicsView 's viewport is always (0, 0), and the bottom right corner is always (viewport width, viewport height). All mouse events and drag and drop events are originally received as view coordinates, and you need to map these coordinates to the scene in order to interact with items.

坐标映射

Often when dealing with items in a scene, it can be useful to map coordinates and arbitrary shapes from the scene to an item, from item to item, or from the view to the scene. For example, when you click your mouse in QGraphicsView 's viewport, you can ask the scene what item is under the cursor by calling QGraphicsView::mapToScene (), followed by QGraphicsScene::itemAt (). If you want to know where in the viewport an item is located, you can call QGraphicsItem::mapToScene () on the item, then QGraphicsView::mapFromScene () on the view. Finally, if you use want to find what items are inside a view ellipse, you can pass a QPainterPath to mapToScene(), and then pass the mapped path to QGraphicsScene::items ().

可以将坐标和形状映射到/从项场景通过调用 QGraphicsItem::mapToScene () 和 QGraphicsItem::mapFromScene (). You can also map to an item's parent item by calling QGraphicsItem::mapToParent () 和 QGraphicsItem::mapFromParent (), or between items by calling QGraphicsItem::mapToItem () 和 QGraphicsItem::mapFromItem (). All mapping functions can map both points, rectangles, polygons and paths.

The same mapping functions are available in the view, for mapping to and from the scene. QGraphicsView::mapFromScene () 和 QGraphicsView::mapToScene (). To map from a view to an item, you first map to the scene, and then map from the scene to the item.

关键特征

缩放和旋转

QGraphicsView 支持相同仿射变换如 QPainter does through QGraphicsView::setMatrix (). By applying a transformation to the view, you can easily add support for common navigation features such as zooming and rotating.

Here is an example of how to implement zoom and rotate slots in a subclass of 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 with autoRepeat 被启用。

QGraphicsView keeps the center of the view aligned when you transform the view.

另请参阅 Elastic Nodes example for code that shows how to implement basic zooming features.

打印

Graphics View provides single-line printing through its rendering functions, QGraphicsScene::render () 和 QGraphicsView::render (). The functions provide the same API: You can have the scene or the view render all or parts of their contents into any paint device by passing a QPainter to either of the rendering functions. This example shows how to print the whole scene into a full page, using QPrinter.

QGraphicsScene scene;
scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));
QPrinter printer;
if (QPrintDialog(&printer).exec() == QDialog::Accepted) {
    QPainter painter(&printer);
    painter.setRenderHint(QPainter::Antialiasing);
    scene.render(&painter);
}
					

The difference between the scene and view rendering functions is that one operates in scene coordinates, and the other in view coordinates. QGraphicsScene::render () is often preferred for printing whole segments of a scene untransformed, such as for plotting geometrical data, or for printing a text document. QGraphicsView::render (), on the other hand, is suitable for taking screenshots; its default behavior is to render the exact contents of the viewport using the provided painter.

QGraphicsScene scene;
scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));
QPixmap pixmap;
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
scene.render(&painter);
painter.end();
pixmap.save("scene.png");
					

When the source and target areas' sizes do not match, the source contents are stretched to fit into the target area. By passing a Qt::AspectRatioMode to the rendering function you are using, you can choose to maintain or ignore the aspect ratio of the scene when the contents are stretched.

拖放

因为 QGraphicsView 继承 QWidget indirectly, it already provides the same drag and drop functionality that QWidget provides. In addition, as a convenience, the Graphics View framework provides drag and drop support for the scene, and for each and every item. As the view receives a drag, it translates the drag and drop events into a QGraphicsSceneDragDropEvent , which is then forwarded to the scene. The scene takes over scheduling of this event, and sends it to the first item under the mouse cursor that accepts drops.

To start a drag from an item, create a QDrag object, passing a pointer to the widget that starts the drag. Items can be observed by many views at the same time, but only one view can start the drag. Drags are in most cases started as a result of pressing or moving the mouse, so in mousePressEvent() or mouseMoveEvent(), you can get the originating widget pointer from the event. For example:

void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QMimeData *data = new QMimeData;
    data->setColor(Qt::green);
    QDrag *drag = new QDrag(event->widget());
    drag->setMimeData(data);
    drag->start();
}
					

To intercept drag and drop events for the scene, you reimplement QGraphicsScene::dragEnterEvent () and whichever event handlers your particular scene needs, in a QGraphicsItem subclass. You can read more about drag and drop in Graphics View in the documentation for each of QGraphicsScene 的事件处理程序。

Items can enable drag and drop support by calling QGraphicsItem::setAcceptDrops (). To handle the incoming drag, reimplement QGraphicsItem::dragEnterEvent (), QGraphicsItem::dragMoveEvent (), QGraphicsItem::dragLeaveEvent (),和 QGraphicsItem::dropEvent ().

另请参阅 拖放 Robot example for a demonstration of Graphics View's support for drag and drop operations.

光标和工具提示

QWidget , QGraphicsItem 还支持光标 ( QGraphicsItem::setCursor ()),和工具提示 ( QGraphicsItem::setToolTip ()). The cursors and tooltips are activated by QGraphicsView as the mouse cursor enters the item's area (detected by calling QGraphicsItem::contains ()).

还可以直接为视图设置默认光标通过调用 QGraphicsView::setCursor ().

另请参阅 拖放 Robot 范例,对于实现工具提示和光标形状处理的代码。

动画

Graphics View supports animation at several levels. You can easily assemble animation by using the Animation Framework. For that you'll need your items to inherit from QGraphicsObject and associate QPropertyAnimation with them. QPropertyAnimation allows to animate any QObject 特性。

Another option is to create a custom item that inherits from QObject and QGraphicsItem . The item can the set up its own timers, and control animations with incremental steps in QObject::timerEvent ().

A third option, which is mostly available for compatibility with QCanvas in Qt 3, is to advance the scene by calling QGraphicsScene::advance (), which in turn calls QGraphicsItem::advance ().

OpenGL 渲染

To enable OpenGL rendering, you simply set a new QGLWidget as the viewport of QGraphicsView 通过调用 QGraphicsView::setViewport (). If you want OpenGL with antialiasing, you need OpenGL sample buffer support (see QGLFormat::sampleBuffers()).

范例:

QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
					

项组

By making an item a child of another, you can achieve the most essential feature of item grouping: the items will move together, and all transformations are propagated from parent to child.

此外, QGraphicsItemGroup is a special item that combines child event handling with a useful interface for adding and removing items to and from a group. Adding an item to a QGraphicsItemGroup will keep the item's original position and transformation, whereas reparenting items in general will cause the child to reposition itself relative to its new parent. For convenience, you can create QGraphicsItemGroup s through the scene by calling QGraphicsScene::createItemGroup ().

Widget 和布局

Qt 4.4 introduced support for geometry and layout-aware items through QGraphicsWidget . This special base item is similar to QWidget ,但不像 QWidget , it doesn't inherit from QPaintDevice ; rather from QGraphicsItem instead. This allows you to write complete widgets with events, signals & slots, size hints and policies, and you can also manage your widgets geometries in layouts through QGraphicsLinearLayout and QGraphicsGridLayout .

QGraphicsWidget

Building on top of QGraphicsItem 's capabilities and lean footprint, QGraphicsWidget provides the best of both worlds: extra functionality from QWidget , such as the style, font, palette, layout direction, and its geometry, and resolution independence and transformation support from QGraphicsItem . Because Graphics View uses real coordinates instead of integers, QGraphicsWidget 's geometry functions also operate on QRectF and QPointF . This also applies to frame rects, margins and spacing. With QGraphicsWidget it's not uncommon to specify contents margins of (0.5, 0.5, 0.5, 0.5), for example. You can create both subwidgets and "top-level" windows; in some cases you can now use Graphics View for advanced MDI applications.

Some of QWidget 's properties are supported, including window flags and attributes, but not all. You should refer to QGraphicsWidget 's class documentation for a complete overview of what is and what is not supported. For example, you can create decorated windows by passing the Qt::Window window flag to QGraphicsWidget 's constructor, but Graphics View currently doesn't support the Qt::Sheet and Qt::Drawer flags that are common on macOS .

QGraphicsLayout

QGraphicsLayout is part of a second-generation layout framework designed specifically for QGraphicsWidget . Its API is very similar to that of QLayout . You can manage widgets and sublayouts inside either QGraphicsLinearLayout and QGraphicsGridLayout . You can also easily write your own layout by subclassing QGraphicsLayout yourself, or add your own QGraphicsItem items to the layout by writing an adaptor subclass of QGraphicsLayoutItem .

嵌入 Widget 支持

图形视图为将任何 Widget 嵌入场景提供无缝支持。可以嵌入简单 Widget,譬如 QLineEdit or QPushButton , complex widgets such as QTabWidget , and even complete main windows. To embed your widget to the scene, simply call QGraphicsScene::addWidget (), or create an instance of QGraphicsProxyWidget to embed your widget manually.

Through QGraphicsProxyWidget , Graphics View is able to deeply integrate the client widget features including its cursors, tooltips, mouse, tablet and keyboard events, child widgets, animations, pop-ups (e.g., QComboBox or QCompleter ), and the widget's input focus and activation. QGraphicsProxyWidget even integrates the embedded widget's tab order so that you can tab in and out of embedded widgets. You can even embed a new QGraphicsView into your scene to provide complex nested scenes.

When transforming an embedded widget, Graphics View makes sure that the widget is transformed resolution independently, allowing the fonts and style to stay crisp when zoomed in. (Note that the effect of resolution independence depends on the style.)

性能

浮点指令

In order to accurately and quickly apply transformations and effects to items, Graphics View is built with the assumption that the user's hardware is able to provide reasonable performance for floating point instructions.

Many workstations and desktop computers are equipped with suitable hardware to accelerate this kind of computation, but some embedded devices may only provide libraries to handle mathematical operations or emulate floating point instructions in software.

As a result, certain kinds of effects may be slower than expected on certain devices. It may be possible to compensate for this performance hit by making optimizations in other areas; for example, by using OpenGL to render a scene. However, any such optimizations may themselves cause a reduction in performance if they also rely on the presence of floating point hardware.