图形视图框架提供了一个基于图形项的模型视图编程方法,主要由场景(QGraphicsScene
)、视图(QGraphicsView
)和项(QGraphicsItem
)三部分组成。多个视图可以查看一个场景,场景中包含各种各样几何形状的图形项。
场景
QGraphicsScene
提供了图形视图框架中的场景,场景拥有以下功能:
- 提供用于管理大量图形项的高速接口
- 传播事件到每一个图形项
- 管理图形项的状态,比如选择和处理焦点
- 提供无变换的渲染功能,主要用于打印
QGraphicsScene
的事件传播构架可以将场景事件传递给图形项,也可以管理图形项之间事件的传递。例如,如果场景在一个特定的点接收到了一个鼠标按下事件,那么场景就会把这个事件传递给该点的图形项。
一个场景分为3层:图形项层(ItemLayer
)、前景层(ForegroundLayer
)、背景层(BackgroundLayer
)。场景的绘制总是从背景层开始,然后是图形项层,最后是前景层。前景层和背景层都可以使用QBrush
进行填充,比如使用渐变和贴图等。
视图
QGraphicsView
提供了视图部件,它用来使场景中的内容可视化。可以连接多个视图到同一个场景来为相同的数据集提供多个视口。视图部件是一个可滚动的区域,它提供了一个滚动条来浏览大的场景。默认的QGraphicsView
提供了一个QWidget
作为视口部件,如果要使用OpenGL
进行渲染,则可调用QGraphicsView::setViewport()
设置QOpenGLWidget
作为视口。QGraphicsView
会获取视口部件的所有权。
视图从键盘或者鼠标接收输入事件,然后会在发送这些事件到可视化场景前将他们转换为场景事件(将坐标转换为合适的场景坐标)。另外,使用视图的变换矩阵函数QGraphicsView::transform()
时,可以通过视图来变换场景的坐标系统,这样便可以实现缩放和旋转等高级的导航功能。
视图的坐标系统有以下几种:
- 项坐标
- 场景坐标
- 视图坐标
- 坐标映射
项(图元)
QGraphicsItem
是场景中图形项的基类。典型的形状的标准图形项有矩形(QGraphicsRectItem
)、椭圆(QGraphicsEllipseItem
)和文本项(QGraphicsTextItem
)等。但只有编写自定义的图形项才能发挥QGraphicsItem
的强大功能。
QGraphicsItem
主要支持如下功能:
- 鼠标按下(
mousePressEvent
)、移动(mouseMoveEvent
)、释放(mouseReleaseEvent
)、双击(mouseDoubleClickEvent
)、悬停(hoverMoveEvent
)、滚轮(wheelEvent
)和右键菜单(contextMenuEvent
)事件 - 键盘输入焦点和键盘事件
- 拖放事件
- 碰撞检测(
shape()
)
除此之外,图形项还可以存储自定义的数据,可以使用setData()
进行数据存储,然后使用data()
获取其中的数据。
要实现自定义的图形项,那么首先要创建一个QGraphicsItem
的子类,然后重新实现它的两个纯虚公共函数:boundingRect()
和paint()
,前者用来返回要绘制图形项的矩形区域,后者用来执行实际的绘图操作。其中boundingRect
()函数将图形项的外部边界定义为一个矩形,所有的绘图操作都必须限制在图形项的边界矩形中。这个矩形对于剔除不可见图形项、确定绘制交叉项目时哪些区域需要重新构建、碰撞检测机制都很重要。一定要保证所有绘图都在boundingRect
()的边界之中,特别是当QPainter
使用了指定的QPen
来渲染图形的边界轮廓时,绘制的图形的边界线的一般会在外面,一半会在里面(例如使用了宽度为两个单位的画笔,就必须在boundingRect()
里绘制一个单位的边界线),这也是在boundingRect()
中要包含半个画笔宽度的原因。
注意:如果boundingRect()
设置的不合适,会导致缩放时候在一定比例下出现无法显示的情况。
示例
项有关部分
头文件部分
#ifndef OBJECTITEM_H
#define OBJECTITEM_H
#include <qmath.h>
#include <QObject>
#include <QGraphicsItem>
#include <QStyle>
#include <QPainter>
#include <QMenu>
#include <QAction>
#include <QMouseEvent>
#include "objecttype.h"
class ObjectItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
public:
enum{ Type = UserType + 1 }; // 注册类型,用来通过类型分辨是什么项
ObjectItem(QObject *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void wheelEvent(QGraphicsSceneWheelEvent *event) override;
void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
void delItem();
int type() const override
{
return Type;
}
QRectF boundingRect() const override;
QPainterPath shape() const Q_DECL_OVERRIDE;
private:
QPixmap m_pixMap;
QPoint m_qPointfOrg;
qreal m_qrealScaleValue; //缩放值
double m_dDirection; //弧度制角度
bool m_bRotating;
int m_iSize;
double m_dPixMapScale;
void setPixMap();
bool isInRotateArea(const QPointF &pos);
qreal GetDegreeAngle(QVector2D vector2d) const;
signals:
void sigdelTriggered();
public slots:
void on_deleteItem(bool bFlag);
};
#endif // OBJECTITEM_H
.cpp部分
#include <QDebug>
#include "objectitem.h"
#define TRAFFICCONE_PIXMAP ":/new/res/mainwindow/trafficcone_verticalview.png"
#define PEDESTRIAN_PIXMAP ":/new/res/mainwindow/trafficsign_verticalview.png"
#define ZEBRACROSSING_PIXMAP ":/new/res/mainwindow/zebracrossing@2x.png"
#define STR_DELETE "删除"
const double dROAD_WIDTH = 7.0;
const double dEACH_WHEEL_DISTANCE = 600.0;
const double dROAD_FIFTY_PART = 50.0;
const double dROAD_TWENTY_PART = 20.0;
const double dROAD_TEN_PART = 10.0;
const double dDEFAULT_MIN = 0.1;
const double dDEFAULT_MAX = 1;
const double dZOOM_IN_BASE = 1.1;
const double dZOOM_OUT_BASE = 1 / 1.1;
const double dPIXMAP_SCALE = 1 / 2.0;
const double dBOUNDING_BOX_SIZE = 20;
ObjectItem::ObjectItem(QObject *parent)
: QObject(parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true); // 设置可移动
setFlag(QGraphicsItem::ItemIsSelectable, true); // 设置可选中
setFlag(QGraphicsItem::ItemIsFocusable, true);
setAcceptHoverEvents(true);
setAcceptTouchEvents(true);
setPixMap();
}
// 重绘事件
void ObjectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setRenderHint(QPainter::Antialiasing);
painter->setRenderHint(QPainter::SmoothPixmapTransform);
if (option->state & QStyle::State_Selected)
{
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(QPen(Qt::red,0.1));
painter->drawRect(QRectF(-m_dWidth/2, -m_dHeight/2, m_dWidth, m_dHeight));
// rotate control point
painter->setPen(QPen(Qt::blue, 0.1));
painter->drawLine(0, 0, m_iSize / 2, 0);
painter->setPen(QPen(Qt::blue, 1));
painter->drawPoint(m_iSize / 2, 0);
}
painter->scale(4, 4);
painter->drawPixmap(-m_pixMap.width() / 2, -m_pixMap.height() / 2, m_pixMap.width(), m_pixMap.height(), m_pixMap);
}
// 设置右键菜单
void ObjectItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
QMenu menu;
QAction *pActionDel=new QAction(STR_DELETE);
menu.addAction(pActionDel);
connect(pActionDel, SIGNAL(triggered(bool)), this, SLOT(on_deleteItem(bool)));
menu.exec(event->screenPos());
}
// 按下事件
void ObjectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
m_qPointfOrg.setX(event->scenePos().x() - this->x());
m_qPointfOrg.setY(event->scenePos().y() - this->y());
}
QGraphicsItem::mousePressEvent(event);
}
// 移动事件(包含两种:旋转和移动)
void ObjectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (m_bRotating)
{
// 旋转
QPointF cursorPos = event->pos();
QVector2D vectorStart = QVector2D(QPointF(m_iSize / 2, 0.0) - QPointF(0.0, 0.0)); // 起始向量
QVector2D vectorEnd = QVector2D(cursorPos - QPointF(0.0, 0.0)); // 结束向量
// 计算起始向量和结束向量之间的角度
qreal angle = 0.0;
qreal angleEnd = GetDegreeAngle(vectorEnd);
qreal angleStart = GetDegreeAngle(vectorStart);
angle = angleEnd - angleStart + rotation();
while(angle > 360.0)
{
angle -= 360.0;
}
while(angle < 0.0)
{
angle += 360.0;
}
setRotation(angle);
m_dDirection = 2 * M_PI - angle / AnglePerPI;
}
else
{
// 移动
this->setPos(event->scenePos().x() - m_qPointfOrg.x(), event->scenePos().y() - m_qPointfOrg.y());
QGraphicsItem::mouseMoveEvent(event);
}
}
// 悬停事件
void ObjectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
if ((isInRotateArea(event->pos()) && isSelected()))
{
setCursor(Qt::SizeVerCursor);
m_bRotating = true;
}
else
{
setCursor(Qt::ArrowCursor);
m_bRotating = false;
}
QGraphicsItem::hoverMoveEvent(event);
}
// 鼠标释放事件
void ObjectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton && m_bRotating)
{
m_bRotating = false;
}
else
{
this->setPos(event->scenePos().x() - m_qPointfOrg.x(), event->scenePos().y() - m_qPointfOrg.y());
}
QGraphicsItem::mouseReleaseEvent(event);
}
// 滚轮事件,主要是放大和缩小
void ObjectItem::wheelEvent(QGraphicsSceneWheelEvent *event)
{
if (QStyle::State_Selected)
{
int iScaleValue = m_qrealScaleValue;
if(event->delta() > 0) //delta()为正,滚轮向上滚
{
iScaleValue++;
}
else
{
iScaleValue--;
}
if(iScaleValue > ZOOM_IN_TIMES || iScaleValue < ZOOM_OUT_TIMES)
{
return;
}
m_qrealScaleValue = iScaleValue;
qreal scale;
if(m_qrealScaleValue > 0)
{
// 向上滚
scale = pow(dZOOM_IN_BASE, m_qrealScaleValue); //放大 计算x的y方次 参数都是double类型
}
else
{
// 向下滚
scale = pow(dZOOM_OUT_BASE, -m_qrealScaleValue); //缩小
}
setScale(scale);
setTransformOriginPoint(event->pos().x(), event->pos().y());
}
QGraphicsItem::wheelEvent(event);
}
void ObjectItem::setPixMap()
{
// 初始化项(加载图片,设置方向等)
m_pixMap.load(TRAFFICCONE_PIXMAP);
m_dWidth = m_pixMap.width() * dOBSTACLE_SCALE;
m_dHeight = m_pixMap.height() * dOBSTACLE_SCALE;
m_dPixMapScale = m_dWidth / m_pixMap.width();
m_object.width = m_dWidth;
m_object.length = m_dHeight;
m_qrealScaleValue = 0.0;
m_dDirection = 0.0 ;
m_bRotating = false;
m_iSize = dBOUNDING_BOX_SIZE;
setRotation(m_dDirection);
}
void ObjectItem::on_deleteItem(bool /*bFlag*/)
{
emit sigdelTriggered();
}
QRectF ObjectItem::boundingRect() const
{
return QRectF(-m_iSize / 2 - 1, -m_iSize / 2, m_iSize + 2, m_iSize);
}
QPainterPath ObjectItem::shape() const
{
QPainterPath path;
if(this->isSelected())
{
path.addRect(-m_dWidth / 2, -m_dHeight / 2, m_dWidth, m_dHeight);
path.addEllipse(QPoint(m_iSize / 2, 0), 0.5, 0.5);
}
else
{
path.addRect(-m_dWidth / 2, -m_dHeight / 2, m_dWidth, m_dHeight);
}
return path;
}
bool ObjectItem::isInRotateArea(const QPointF &pos)
{
if (
m_iSize / 2 -0.5 <= pos.x() &&
pos.x() <= m_iSize / 2 + 0.5 &&
-0.5 <= pos.y() &&
pos.y() <= 0.5
)
{
return true;
}
else
{
return false;
}
}
qreal ObjectItem::GetDegreeAngle(QVector2D vector2d) const
{
return fmod((atan2((qreal)vector2d.y(), (qreal)vector2d.x()) * AnglePerPI + 360.0), 360.0 );
}
视图有关部分
QGraphicsView*pQWGraphicsView = new QGraphicsView();
m_pScene = new QGraphicsScene(-4, -4, 4, 4); // 创建QGraphicsScene
pQWGraphicsView->setScene(m_pScene); // 与view关联
// 场景滚动条部分设置样式
pQWGraphicsView ->verticalScrollBar()->setStyleSheet(STR_VERICAL_SCROLLBAR_STYLE);
pQWGraphicsView ->horizontalScrollBar()->setStyleSheet(STR_HORIZONTAL_SCROLLBAR_STYLE);
//暂时根据文件大小调整缩放度
pQWGraphicsView->resetTransform();
pQWGraphicsView->scale(4, 4); // 由于物体、交通灯等元素可能导致加载后有问题
pQWGraphicsView->centerOn(0, 0);
场景有关部分
m_pScene->addItem(m_pItem); // 添加项
m_pScene->clear(); // 清空项
m_pScene->removeItem(m_pItem); // 移除项
QGraphicsItem *item = m_pScene->itemAt(pointf, pQWGraphicsView->transform()); // 获取光标下的绘图项
QList<QGraphicsItem*> qlstpItem = m_pScene->selectedItems(); // 获取选中的项