Qt图形视图框架
前言
为了应付大作业更好的整活,我需要学习以下Qt的图形视图框架。
本文内容主要来自这里
图形视图框架概述
如果要绘制成千上万的图形,并且对它们进行控制,比如拖动这些图形、检测它们的位置以及判断它们是否碰撞等,可以使用Qt提供的图形视图框架来进行设计。
图形视图框架提供了一个基于图形项的模型视图编程方法,主要由场景、视图和图形项三部分组成,这三部分分别由QGraphicsScene、QGraphicsView和QGraphicsItem这三个类来表示。多个视图可以查看一个场景,场景中包含各种各样几何形状的图形项。
图形视图框架可以管理数量庞大的自定义2D图形项,并且可以与它们进行交互。使用视图部件可以使这些图形项可视化,视图还支持缩放与旋转。框架中包含了一个事件传播架构,提供了和场景中的图形项进行精确的双精度交互的能力。图形视图框架使用一个BSP(Binary Space Partitioning)树来快速发现图形项。可通过Graphics View Framework关键字查看相关帮助。
场景
QGraphicsItem是场景中图形项的基类。典型的形状的标准图形项有矩形(QGraphicsRectItem)、椭圆(QGraphicsEllipseItem)和文本项(QGraphicsTextItem)等。但只有编写自定义的图形项才能发挥QGraphicsItem的强大功能。
QGraphicsItem主要支持如下功能:
-
鼠标按下、移动、释放、双击、悬停、滚轮和右键菜单事件
-
键盘输入焦点和键盘事件
-
拖放事件
-
碰撞检测
除此之外,图形项还可以存储自定义的数据,可以使用setData()进行数据存储,然后使用data()获取其中的数据。
要实现自定义的图形项,那么首先要创建一个QGraphicsItem的子类,然后重新实现它的两个纯虚公共函数:boundingRect()和paint(),前者用来返回要绘制图形项的矩形区域,后者用来执行实际的绘图操作。其中boundingRect()函数将图形项的外部边界定义为一个矩形,所有的绘图操作都必须限制在图形项的边界矩形中。这个矩形对于剔除不可见图形项、确定绘制交叉项目时哪些区域需要重新构建、碰撞检测机制都很重要。一定要保证所有绘图都在boundingRect()的边界之中,特别是当QPainter使用了指定的QPen来渲染图形的边界轮廓时,绘制的图形的边界线的一般会在外面,一半会在里面(例如使用了宽度为两个单位的画笔,就必须在boundingRect()里绘制一个单位的边界线),这也是在boundingRect()中要包含半个画笔宽度的原因。
纯虚函数实现示例:
QRectF MyItem::boundingRect() const
{
qreal penWidth = 1; //画笔宽度
return QRectF(0 - penWidth / 2, 0 - penWidth / 2,
20 + penWidth, 20 + penWidth);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->setBrush(Qt::red);
painter->drawRect(0, 0, 20, 20);
}
图形视图框架的坐标系统
图形视图框架基于笛卡尔坐标系统。图形视图框架中有3个有效的坐标系统:图形项坐标、场景坐标和视图坐标。进行绘图时,场景坐标对应QPainter的逻辑坐标,视图坐标对应设备坐标。
图形项坐标
图形项使用自己的本地坐标系统,坐标通常是以(0,0)为原点,而这也是所有变换的中心。当要创建一个自定义图形项时,只需要考虑图形项的坐标系统,而且一个图形项的边界矩形和图形形状都是在图形项坐标系统中的。
图形项的位置是指图形项的原点在其父图形项或者场景中的位置。可以使用setPos()函数来指定图形项的位置,如果没有指定,默认出现在父图形项或者场景的原点处。
子图形项的位置和坐标是相对于父图形项的,虽然父图形项的坐标变换会隐含地变换子图形项,但子图形项的坐标不会受到父图形项的变换的影响。例如,在没有坐标变换时,子图形项就在父图形项的(10,0)点,那么子图形项中的(0,10)点就对应了父图形项的(10,10)点。现在即使父图形项进行了旋转或者缩放,子图形项的(0,10)点仍对应着父图形项的(10,10)点。但是相对于场景,子图形项就会跟随父图形项的变换,例如,父图形项放大为(2x,2x),那么子图形项在场景中的位置就会变成(20,0),它的(10,0)点就会对应着场景中的(40,0)点。
所有的图形项都会使用确定的顺序来进行绘制,这个顺序也决定了单击场景时哪个图形项会先获得鼠标输入。一个子图形项会堆叠在父图形项的上面,而兄弟图形项会以插入顺序进行堆叠。所有图形项都包含一个Z值来设置它们的层叠顺序,一个图形项的Z值默认为0,可以使用QGraphicsItem::setZValue()来改变一个图形项的Z值,从而使它堆叠到其兄弟图形项的上面(使用较大的Z值)或者下面(使用较小的Z值)。
场景坐标
场景坐标是所有图形项的基础坐标系统。场景坐标的原点在场景的中心,x和y坐标分别向右和向下增大。
视图坐标
视图坐标的每一个单位对应一个像素,原点(0,0)总在QGraphicsView视口的左上角,而右下角是(宽,高)。所有的鼠标事件和拖放事件最初都是使用视图坐标接收的。
坐标映射
不仅可以在视图、场景和图形项之间使用坐标映射,还可以在子图形项、父图形项或者图形项、图形项之间进行坐标映射。所有的映射函数都可以映射点、矩形、多边形和路径。例如要获取在视图中的一个椭圆形中包含的图形项,则可以先传递一个QPainterPath对象作为参数给mapToScene()函数,然后传递映射后的路径给QGraphicsScene::items()函数。
图形视图框架的映射函数
| 映射函数 | 描述 |
|---|---|
| QGraphicsView::mapToScene( ) | 从视图坐标系统映射到场景坐标系统 |
| QGraphicsView::mapFromScene( ) | 从场景坐标系统映射到视图坐标系统 |
| QGraphicsItem::mapToScene( ) | 从图形项的坐标系统映射到场景的坐标系统 |
| QGraphicsItem::mapFromScene( ) | 从场景的坐标系统映射到图形项的坐标系统 |
| QGraphicsItem::mapToParent( ) | 从本图形项的坐标系统映射到其父图形项的坐标系统 |
| QGraphicsItem::mapFromParent( ) | 从父图形项的坐标系统映射到本图形项的坐标系统 |
| QGraphicsItem::mapToItem( ) | 从本图形项的坐标系统映射到另一个图形项的坐标系统 |
| QGraphicsItem::mapFromItem( ) | 从另一个图形项的坐标系统映射到本图形项的坐标系统 |
事件处理与传播
图形视图框架中的事件都是先由视图进行接收,然后传递给场景,再由场景传递给相应的图形项。而对于键盘事件,它会传递给获得焦点的图形项,可以使用QGraphicsScene类的setFocusItem()函数或者图形项自身调用setFocus()函数来设置焦点图形项。默认的,如果场景没有获得焦点,那么所有的键盘事件都会被丢弃。场景中的图形项获得了焦点,场景也会自动获得焦点。
示例
MyItem
- MyItem.h
#pragma once
#include <QGraphicsItem>
class MyTime : public QGraphicsItem
{
public:
MyTime();
~MyTime();
QRectF boundingRect() const override;
void paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget) override;
void setColor(const QColor& color);
private:
QColor brushColor;
protected:
void KeyPressEvent(QKeyEvent* event);
void mousePressEvent(QGraphicsSceneMouseEvent* event);
void hoverEnterEvent(QGraphicsSceneHoverEvent* event);
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event);
};
- MyItem.cpp
#include "MyTime.h"
#include <QPainter>
#include <QCursor>
#include <QKeyEvent>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
MyTime::MyTime()
{
brushColor = Qt::red;
setFlag(QGraphicsItem::ItemIsFocusable);
setFlag(QGraphicsItem::ItemIsMovable);
setAcceptHoverEvents(true);
}
MyTime::~MyTime()
{
}
QRectF MyTime::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(
-10 - adjust,
-10 - adjust,
20 + adjust,
20 + adjust
);
}
void MyTime::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
if (hasFocus())
{
painter->setPen(QPen(QColor(255, 255, 255, 200)));
}
else
{
painter->setPen(QPen(QColor(100, 100, 100, 100)));
}
painter->setBrush(brushColor);
painter->drawRect(-10, -10, 20, 20);
}
void MyTime::setColor(const QColor& color)
{
brushColor = color;
}
void MyTime::KeyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Down)
{
moveBy(0, 10);
}
}
void MyTime::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
setFocus();
setCursor(Qt::ClosedHandCursor);
}
void MyTime::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
setCursor(Qt::OpenHandCursor);
setToolTip("I am a item");
}
void MyTime::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
{
QMenu menu;
QAction* moveAction = menu.addAction("move back");
QAction* selectedAction = menu.exec(event->screenPos());
if (selectedAction == moveAction)
{
setPos(0, 0);
}
}
MyView
- MyView.h
#pragma once
#include <QGraphicsView>
class MyView : public QGraphicsView
{
public:
MyView();
~MyView();
protected:
void keyPressEvent(QKeyEvent* event);
};
- MyView.cpp
#include "MyView.h"
#include <QKeyEvent>
MyView::MyView()
{
}
MyView::~MyView()
{
}
void MyView::keyPressEvent(QKeyEvent* event)
{
switch (event->key())
{
case Qt::Key_Plus:
scale(1.2, 1.2);
break;
case Qt::Key_Minus:
scale(1 / 1.2, 1 / 1.2);
break;
case Qt::Key_Right:
rotate(30);
default:
break;
}
QGraphicsView::keyPressEvent(event);
}
main
- main.cpp
#include <QtWidgets/QApplication>
#include <QTime>
#include "MyTime.h"
#include "MyView.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
QGraphicsScene scene;
scene.setSceneRect(-200, -150, 400, 300);
for (int i = 0; i < 5; i++)
{
MyTime* item = new MyTime();
item->setColor(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
item->setPos(i * 50 - 90, -50);
scene.addItem(item);
}
MyView view;
view.setScene(&scene);
view.setBackgroundBrush(QBrush(Qt::red));
view.show();
return app.exec();
}

浙公网安备 33010602011771号