Graphics View provides a surface for managing and interacting with a large number of custom-made 2D graphical items, and a view widget for visualizing the items, with support for zooming and rotation. Graphics View was introduced in Qt 4.2, replacing its predecessor, QCanvas. For more on Graphics View, see Graphics View Framework.
This document walks through the steps needed, class by class and function by function, to port a QCanvas application to Graphics View.
Qt 4.2 provides two complete examples of Q3Canvas applications ported to Graphics View:
Conceptually, the Graphics View classes from Qt 4 and the Canvas classes from Qt 3 provide similar functionality using a similar design. Instead of "canvas", we use the term "scene". Otherwise, the class names and functions are almost the same as in Qt 3. The easiest classes to port will be QCanvas and QCanvasView. Experience shows that most time is spent porting the item classes, depending on the complexity of the QCanvasItem classes you have been using before.
This porting guide will assume you have already ported your application to Qt 4, by making use of Q3Canvas. If you have not done so already, as a first step, run the qt3to4 tool on your project. This tool will automate the most tedious part of the porting effort.
Some additional steps are usually required before your application will compile and run. You can read more about the porting process in Porting to Qt 4.
QGraphicsScene is the closest equivalent to Q3Canvas. There are some noticable differences in this new API: Whereas the Q3Canvas classes use integer precision, QGraphicsScene is entirely based on double coordinates, with graphical primitives such as QPointF instead of QPoint, QRectF instead of QRect, and QPolygonF and QPainterPath. The canvas area is defined by a scene rectangle, allowing negative coordinates, as opposed to Q3Canvas, which only defines a size (QSize), and whose top-left corner is always (0, 0).
In addition, there is no explicit support for canvas tiles anymore; see Porting scenes with tiles for more information. The chunks-based indexing system has been replaced with an implicitly maintained internal BSP tree.
Q3Canvas | QGraphicsScene |
---|---|
There is no QPixmap based constructor, and the concept of tiles is gone. You can use QGraphicsScene::backgroundBrush to set a brush pattern for the background, or reimplement QGraphicsScene::drawBackground() in a QGraphicsScene subclass (see Porting scenes with tiles). In addition, the QGraphicsScene geometry is provided as a full QRectF. Instead of Q3Canvas(int width, int height), you can use QGraphicsScene(int top, int left, int width, int height). | |
QGraphicsScene::items() returns a list of all items on the scene. | |
You can assign a color for the background through the QGraphicsScene::backgroundBrush or QGraphicsView::backgroundBrush properties. | |
You can set a tiled pixmap for the background through QGraphicsScene::backgroundBrush or QGraphicsView::backgroundBrush. For more control on the pixmap positioning, you can reimplement QGraphicsScene::drawBackground() or QGraphicsView::drawBackground(). | |
The closest equivalent to the chunks size in Q3Canvas is the depth of QGraphicsScene's BSP tree. QGraphicsScene assigns a depth automatically, and the size of each scene segment depends on this depth, and QGraphicsScene::sceneRect(). See QGraphicsScene::itemIndexMethod. | |
QGraphicsScene provides several means to detect item collisions. The QGraphicsScene::items() overloads return items that collide with a point, a rectangle, a polygon, or an arbitrary vector path (QPainterPath). You can also call QGraphicsScene::collidingItems() to determine collision with an item. | |
The QGraphicsScene::render() function provides the original behavior Q3Canvas::drawArea(). In addition, you can pass a source rectangle for rendering only parts of the scene, and a destination rectangle for rendering onto designated area of the destination device. QGraphicsScene::render() can optionally transform the source rectangle to fit into the destination rectangle. See Printing | |
The is no equivalent to this function in Graphics View. However, you can combine QGraphicsScene::sceneRect() and QRectF::intersects(): item->scene().sceneRect().intersects(item->sceneBoundingRect()); | |
The equivalent, QGraphicsScene::sceneRect(), returns a QRectF (double precision coordinates). Its top-left corner can be an arbitrary coordinate (Q3Canvas::rect().topLeft() is always (0, 0)). | |
You can call QGraphicsScene::setSceneRect(0, 0, width, height) instead. | |
See QGraphicsScene::itemIndexMethod. You can tune the indexing by setting a suitable sceneRect(). The optimal depth of QGraphicsScene's BSP tree is determined automatically. | |
There is no concept of an advance period in the new API; instead, you can connect QTimer::timeout() to the QGraphicsScene::advance() slot to obtain similar functionality. This will cause all items' QGraphicsItem::advance() function to be called. See also QGraphicsItemAnimation. | |
You can call QGraphicsScene::update() with no arguments. | |
QGraphicsScene::update() will trigger a repaint of the whole scene, or parts of the scene. | |
Q3Canvas' double buffering enabled cacheing of the scene contents in device (i.e., viewport) coordinates. This cache layer has been moved to the view instead; you can cache QGraphicsScene's background through QGraphicsView::setCacheMode(). QGraphicsView::resetCachedContent() will reset the areas of the cache that has changed. | |
There is no equivalent in Graphics View. This call can usually be removed with no side effects. | |
There is no concept of an update period in the new API; instead, you can connect QTimer::timeout() to the QGraphicsScene::update() slot to obtain similar functionality. See also QGraphicsItemAnimation. | |
QGraphicsScene::sceneRect().size() returns a QSizeF, with double precision coordinates. | |
To determine if an area is inside the scene area or not, you can combine QRectF::intersects() with QGraphicsScene::sceneRect(). | |
QGraphicsScene emits sceneRectChanged() whenever the scene rect changes. | |
You can reimplement QGraphicsScene::drawBackground() to render the scene background. You can also reimplement QGraphicsView::drawBackground() to override this background if you need different backgrounds for different views. | |
You can reimplement QGraphicsScene::drawForeground() to render the scene foreground. You can also reimplement QGraphicsView::drawForeground() to override this foreground if you need different foregrounds for different views. |
QGraphicsScene does not provide an API for tiles. However, you can achieve similar behavior by drawing pixmaps in a reimplementation of QGraphicsScene::drawBackground().
Q3Canvas' tile support is based on providing one pixmap containing tiles of a fixed width and height, and then accessing them (reading and replacing tiles) by index. The tiles in the pixmap are arranged from the left to right, top to bottom.
0 | 1 | 2 | 3 |
4 | 5 | 6 | 7 |
With Graphics View, this pixmap can be stored as a member of a subclass of QGraphicsScene. The three main functions that make out the public tile API can then be declared as new members of this class. Here is one example of how to implement tile support:
class TileScene : public QGraphicsScene { public: ... void setTiles(const QPixmap &pixmap, int h, int v, int tileHeight, int tileWidth); void setTile(int x, int y, int tilenum); private: QRect tileRect(int x, int y) const; QRect tileRect(int tileNum) const; QVector<QVector<int> > tiles; QPixmap tilePixmap; int tileW, tileH; int hTiles, vTiles; };
Depending on how your scene uses tiles, you may be able to simplify this approach. In this example, we will try to mimic the behavior of the Q3Canvas functions.
We start by creating a subclass of QGraphicsScene ("TileScene"). In this class, we declare two of the tile functions from Q3Canvas, and we then add two helper function that returns the rectangle for a certain tile in our tile pixmap. We will use a two-dimensional vector of ints to keep track of what tiles should be used at what parts of the scene.
void TileScene::setTiles(const QPixmap &pixmap, int h, int v, int tileHeight, int tileWidth) { tilePixmap = pixmap; tileW = tileWidth; tileH = tileHeight; hTiles = h; vTiles = v; tiles.resize(v); for (int y = 0; y < v; ++y) tiles[y].resize(h); }
In setTiles(), we store the pixmap and tile properties as members of the class. Then we resize the tiles vector to match the width and height of our tile grid.
void TileScene::setTile(int x, int y, int tilenum) { tiles[y][x] = tilenum; update(tileRect(x, y)); }
The setTile() function updates the tiles index, and then updates the corresponding rect in the scene by calling tileRect().
QRect TileScene::tileRect(int x, int y) const { return QRect(x * tileW, y * tileH, tileW, tileH); }
The first tileRect() function returns a QRect for the tile at position (x, y).
QRect TileScene::tileRect(int tileNum) const { int numHTiles = tilePixmap.width() / tileW; int numVTiles = tilePixmap.height() / tileH; return tileRect(tileNum % numHTiles, tileNum / numHTiles); }
The second tileRect() function returns a QRect for a tile number. With these functions in place, we can implement the drawBackground() function.
void drawBackground(QPainter *painter, const QRectF &exposed) { for (int y = 0; y < vTiles; ++y) { for (int x = 0; x < hTiles; ++x) { QRect destRect = tileRect(x, y); if (exposed.intersects(destRect)) { painter->drawPixmap(destRect, tilePixmap, tileRect(tiles[y][x])); } } } }
In drawBackground(), we redraw all tiles that have been exposed by intersecting each tile rect with the exposed background area.
The closest equivalent to Q3CanvasView in Graphics View is called QGraphicsView. In most cases, this is the easiest class to port. In addition to providing all of Q3CanvasView's functionality, QGraphicsView includes some useful new features. You can read more about this in QGraphicsView's documentation.
Q3CanvasView | QGraphicsView |
---|---|
QGraphicsView provides the same constructors as Q3CanvasView, but without the name and flags arguments. You can set the name by calling setObjectName(), and the flags by calling setWindowFlags(). | |
QGraphicsView::scene() returns the scene that is currently associated with the view. QGraphicsScene also provides the opposite function, QGraphicsScene::views(), which returns a list of views observing the scene. | |
You can call QGraphicsView::matrix() and QMatrix::inverted(). QGraphicsView::mapToScene() and QGraphicsView::mapFromScene() allow transforming of viewport shapes to scene shapes, and vice versa. | |
QGraphicsView::setMatrix(), QGraphicsView::rotate(), QGraphicsView::scale(), QGraphicsView::shear() and QGraphicsView::translate(). | |
The QGraphicsView::drawBackground() function draws the background, QGraphicsView::drawItems() draws the items, and QGraphicsView::drawForeground() draws the foreground of the scene in scene coordinates. You can also reimplement these functions in QGraphicsScene. |
QGraphicsView can cache the visible contents of the scene, similar to how Q3Canvas::setDoubleBuffering() could cache the entire scene contents. You can call QGraphicsView::setCacheMode() to configure cacheing, and QGraphicsView::resetCachedContent() invalidates the cache.
For improved navigation support, you can set a resize or transformation anchor through QGraphicsView::resizeAnchor and QGraphicsView::transformationAnchor. This allows you to easily rotate and zoom the view while keeping the center fixed, or zooming towards the position under the mouse cursor. In addition, if you set the QGraphicsView::dragMode of the view, QGraphicsView will provide rubber band selection or click-and-pull navigation using the OpenHandCursor and ClosedHandCursor cursors.
The closest equivalent to Q3CanvasItem in Graphics View is called QGraphicsItem. Deriving from this class is very common, and because of that, porting from Q3CanvasItem often involves more work than Q3Canvas and Q3CanvasView.
Q3CanvasItem has become easier to use, easier to subclass, and more powerful with QGraphicsItem. The key difference from Q3CanvasItem lies in event propagation and item groups, but you will also find several convenient new features, such as support for tooltips, cursors, item transformation and drag and drop. You can read all about QGraphicsItem in its own class documentation.
This section starts with a table that shows how to port each function from Q3CanvasItem to QGraphicsItem. Immediately after that, each of Q3CanvasItem's standard subclasses have a section of their own.
Q3CanvasItem | QGraphicsItem |
---|---|
QGraphicsItem::advance() is provided for compatibility. QGraphicsScene::advance() calls QGraphicsItem::advance() for all items. See also QTimeLine and QGraphicsItemAnimation. | |
No equivalent; all items are advanced by QGraphicsScene::advance(). | |
No equivalent. You can translate QGraphicsItem::boundingRect() instead (see QRectF::translate()). | |
QGraphicsItem::collidesWithItem() and QGraphicsItem::collidesWithPath(). | |
QGraphicsItem::collidingItems() returns a list of all items that collide with an item. You can specify whether you want fast, rough estimate collision between bounding rectangles, or the slower, more accurate shapes. | |
QGraphicsItem::paint(). See also QStyleOptionGraphicsItem, QGraphicsScene::drawItems() and QGraphicsView::drawItems(). | |
QGraphicsItem::hide() or QGraphicsItem::setVisible(). QGraphicsItems are visible by default; Q3CanvasItems, however, are not. | |
No equivalent. To achieve similar behavior, you can add this property in a custom subclass of QGraphicsItem. | |
QGraphicsItem::isVisible(). QGraphicsItems are visible by default; Q3CanvasItems, however, are not. | |
You can call QGraphicsItem::setPos() to change the position of the item. | |
QGraphicsItem::type() and qgraphicsitem_cast(). | |
No equivalent. | |
No equivalent; all items are by default "animated" (i.e., QGraphicsScene::advance() advances all items on the scene). | |
You can call QGraphicsScene::addItem(), or pass a pointer to the canvas to QGraphicsItem's constructor. | |
No equivalent. You can add x and y velocity as member data of your class, and call QGraphicsItem::moveBy(x, y) from inside QGraphicsItem::advance(). See also QTimeLine and QGraphicsItemAnimation. | |
QGraphicsItem::setVisible(). QGraphicsItems are visible by default; Q3CanvasItems, however, are not. | |
No equivalent. | |
No equivalent. | |
QGraphicsItem::show() or QGraphicsItem::setVisible(). QGraphicsItems are visible by default; Q3CanvasItems, however, are not. | |
No equivalent. | |
No equivalent. |
Note that some virtual functions that have passed on to QGraphicsItem have lost their virtuality. An example is Q3CanvasItem::moveBy(), which was often used to track movement of items. In this case, the virtual QGraphicsItem::itemChange() has taken over as a substitute.
The closest equivalent to Q3CanvasPolygonalItem in Graphics View is called QAbstractGraphicsShapeItem. Unlike Q3CanvasPolygonalItem, it does not define area points (Q3CanvasPolygonalItem::areaPoints()); instead, each item's geometry is stored as a member of the subclasses.
The Q3CanvasPolygonalItem::drawShape() function is no longer available; instead, you can set the brush and pen from inside QGraphicsItem::paint().
Q3CanvasPolygonalItem | QAbstractGraphicsShapeItem |
---|---|
No equivalent; each item's geometry is stored in the respective subclass. | |
No equivalent; you can use QPolygonF::translate() or QPainterPath::translate() instead. | |
QGraphicsItem::paint(). You can set the pen and brush from inside this function. | |
Call QGraphicsItem::prepareGeometryChange() before changing the item's geometry. | |
No equivalent; items' geometry is always in a valid state. | |
This function is only useful for polygon items and path items; see QGraphicsPolygonItem::fillRule(), and QPainterPath::fillRule() for QGraphicsPathItem. |
The closest equivalent to Q3CanvasEllipse in Graphics View is called QGraphicsEllipseItem. The most noticable difference to QGraphicsEllipseItem is that the ellipse is not longer drawn centered around its position; rather, it is drawn using a bounding QRectF, just like QPainter::drawEllipse().
For compatibility, you may want to shift the ellipse up and to the left to keep the ellipse centered. Example:
// Before Q3CanvasEllipse ellipse(10, 10); // After QGraphicsEllipseItem ellipse(-5, -5, 10, 10);
Note: QGraphicsEllipseItem uses QAbstractGraphicsShapeItem::pen() for outlines, whereas Q3CanvasEllipse did not use Q3CanvasPolygonalItem::pen().
Q3CanvasEllipse | QGraphicsEllipseItem |
---|---|
QGraphicsEllipseItem::setStartAngle() and QGraphicsEllipseItem::setSpanAngle() | |
The closest equivalent to Q3CanvasLine in Graphics View is called QGraphicsLineItem.
Q3CanvasLine | QGraphicsLineItem |
---|---|
QGraphicsLineItem::line() and QLineF::p2() | |
QGraphicsLineItem::line() and QLineF::p1() |
The closest equivalent to Q3CanvasPolygon in Graphics View is called QGraphicsPolygonItem.
Q3CanvasPolygon | QGraphicsPolygonItem |
---|---|
QGraphicsPolygonItem::polygon() and QGraphicsItem::mapToParent() | |
The closest equivalent to Q3CanvasSpline in Graphics View is called QGraphicsPathItem. This item can be used to describe any type of path supported by QPainter.
Q3CanvasSpline takes its control points as a Q3PointArray, but QPainterPath operates on a sequence of calls to QPainterPath::moveTo() and QPainterPath::cubicTo(). Here is how you can convert a bezier curve Q3PointArray to a QPainterPath:
static QPainterPath fromControlPoints(const Q3PointArray &pa) { QPainterPath path; path.moveTo(pa[0]); for (int i = 1; i < pa.size(); i += 3) path.cubicTo(pa[i], pa[(i + 1) % pa.size()], pa[(i + 2) % pa.size()]); return path; }
Note: QGraphicsPathItem uses QAbstractGraphicsShapeItem::pen() for outlines, whereas Q3CanvasSpline did not use Q3CanvasPolygonalItem::pen().
Q3CanvasSpline | QGraphicsPathItem |
---|---|
No equivalent. You can call QPainterPath::closeSubPath() to close a subpath explicitly. |
The closest equivalent to Q3CanvasRectangle in Graphics View is called QGraphicsRectItem.
Q3CanvasRectangle | QGraphicsRectItem |
---|---|
QGraphicsRectItem::rect() and QRectF::size() | |
QGraphicsRectItem::rect() and QRectF::width() | |
No equivalent. |
Q3CanvasSprite is the item class that differs the most from its Q3Canvas predecessor. The closest resemblance of Q3CanvasSprite in Graphics View is QGraphicsPixmapItem.
Q3CanvasSprite supports animated pixmaps; QGraphicsPixmapItem, however, is a simple single-frame pixmap item. If all you need is a pixmap item, porting is straight-forward. If you do need the animation support, extra work is required; there is no direct porting approach.
For the Ported Asteroids Example, a subclass of QGraphicsPixmapItem is used to replace Q3CanvasSprite, storing a list of pixmaps and a frame counter. The animation is advanced in QGraphicsItem::advance().
These classes have been removed from the API. You can use QPixmap instead of Q3CanvasPixmap, and QList instead of Q3CanvasPixmapArray.
Q3CanvasPixmapArray included convenience for loading a sequence of pixmaps or masks using a path with a wildcard (see Q3CanvasPixmapArray::readPixmaps() and Q3CanvasPixmapArray::readCollisionMasks()). To achieve similar functionality using Graphics View, you can load the images by using QDir:
wildcardPath.replace("%1", "*"); QFileInfo fi(wildcardPath); QList<QPixmap> frames; foreach (QString entry, QDir(fi.path(), fi.fileName()).entryList()) frames << QPixmap(fi.path() + "/" + entry);
Q3CanvasText has been split into two classes in Graphics View: QGraphicsSimpleTextItem and QGraphicsTextItem. For porting, QGraphicsSimpleTextItem should be adequate. QGraphicsTextItem provides advanced document structuring features similar to that of QTextEdit, and it also allows interaction (e.g., editing and selection).
Q3CanvasText | QGraphicsSimpleTextItem |
---|---|
Use QGraphicsTextItem instead. |
Use QList instead.
The Porting to Qt 4.2's Graphics View article in Qt Quarterly 21 covered the process of porting the Qt 3 canvas example to Qt 4. The result of this is the Ported Canvas example.
[Previous: Porting UI Files to Qt 4] [Next: qt3to4 - The Qt 3 to 4 Porting Tool]