文章目录
- 前言
- [1. QGraphicsView视图类](#1. QGraphicsView视图类)
-
- [1.1 缩放与旋转](#1.1 缩放与旋转)
- [1.2 其他事件传递](#1.2 其他事件传递)
- [2. QGraphicsScene场景类](#2. QGraphicsScene场景类)
-
- [2.1 QPainterPath的使用](#2.1 QPainterPath的使用)
- [2.2 Scene中添加item](#2.2 Scene中添加item)
- [3. QGraphicsItem图元类](#3. QGraphicsItem图元类)
前言
在 Qt 框架内的许多模块,类和子框架下,有一块专门用于简化图形处理的工具,称为图形视图框架。 它包含许多类,几乎所有的类都以QGraphics
开头,并且所有这些类都可用于处理构建计算机视觉应用时可能遇到的大多数图形任务。 图形视图框架将所有可能的对象简单地分为三个主要类别,即场景类(QGraphicsScene
)、视图类(QGraphicsView
)和图元类(QGraphicsItem
),统称为"三元素"。随之而来的架构允许轻松地添加,删除,修改以及显示图形对象。
- 视图(
QGraphicsView
小部件):用于可视化和显示QGraphicsScene
的内容。 它还负责将事件传播到QGraphicsScene
。 这里要注意的重要一点是QGraphicsScene
和QGraphicsView
都具有不同的坐标系。 可以猜到,如果放大,缩小或进行不同的相似变换,则场景上的位置将不同。QGraphicsScene
和QGraphicsView
都提供了转换彼此适合的位置值的功能。 - 场景(
QGraphicsScene
类):管理项目(QGraphicsItem
)的实例(其子类),包含它们,并将事件(例如,鼠标单击、移动等)传播到项目中。一个场景可以通过多个视图表现,一个场景也可以包括多个几何图形。 - 图形项目(QGraphicsItem及其子类)这些项目(
QGraphicsItem
)子类的实例是QGraphicsScene
中包含的项目。 它们可以是线,矩形,图像,文本等。
这三个元素之间的关系如下:
场景包含了所有的图形项,负责管理它们的位置、显示状态和交互。
视图负责将场景中的图形项渲染到屏幕上,并处理用户的交互操作。
图形项是场景中的可视对象,可以添加到场景中,并通过视图进行显示和交互。
使用 Graphics View
框架,你可以创建交互式的图形界面,显示和管理大量的图形元素,并实现各种图形操作和动画效果。
简单来说
Items
就好比各种事物,是实实在在存在的;
scene
就好比现实中的场景,也是实实在在存在的,scene
本身没有边界,里面可以放置很多事物,即items。
view
即为视窗,你可以理解为一个摄像机,把摄像机放在scene
前,摄像机的取景框是有边界的,所以从view
看到的scene
只是scene
的一部分,我们转动摄像机的镜头就可以看到scene
的其他地方。
view
层可以做的事情就是缩放视窗,转动视窗,移动视窗。这些操作很好理解,还是摄像机,我们调整摄像机镜头的焦段,放大后看到的items
就变大了,但是场景中显示的内容就变少了,反之亦然。转动和移动视窗就是对摄像机的拍摄角度进行调整。其他的操作例如鼠标操作点击,拖动items
在这一层是没办法实现的,items
是实际存在的,你所能操作的只是摄像机里面的图像,实际物体并没有发生变化。所以要想对事物进行操作,就需要将鼠标信号向后传递。
scene
相当于一个大大的容器,里面存放着items
,收到前面传来的信号之后,添加items
就是向scene
中添加事物,其他的可以类比。能对scene
层进行的操作也可以类比了,就是场景的背景,布局等待,天空的颜色,地形地貌等等,简单点就是画板的背景。对场景scene
的变动跟items
是有很多关系的,一个个items
的变动组成了场景scene
的变动。具体到某一个事物的操作,就要转到item
了。
item
就是一个特定的事物,比如一张桌子,我要搬动他,在计算机里的操作就是通过鼠标选中图元,然后拖动图元,这里选中操作是在场景里找到这个桌子,而找到后要进行的操作就是针对桌子这个事物了。
1. QGraphicsView视图类
QGraphicsView
类是 Qt 窗口小部件类,可以将其放置在窗口上以显示QGraphicsScene
,该窗口本身包含许多QGraphicsItem
子类和/或窗口小部件。 与QGraphicsScene
类相似,该类还提供大量函数,方法和属性来处理图形的可视化部分。 我们将审核以下列表中的一些最重要的函数,然后我们将学习如何对QGraphicsView
进行子类化并将其扩展为在我们全面的计算机视觉应用中具有若干重要功能,例如放大,缩小, 项目选择等。
下面给出一些常用的方法和成员:
alignment
和setAlignment
函数可用于设置场景在视图中的对齐方式。 重要的是要注意,只有当视图可以完全显示场景,即视图不需要滚动条时,这才具有可见效果。一般当场景大于视图会出现滚动条,是不能通过setAlignment()
函数来设置场景在视图中的对齐方式的。
cpp
//左对齐Qt::AlignLeft ; 向上对齐Qt::AlignTop ; 中心对齐Qt::AlignCenter
eg:setAlignment(Qt::AlignLeft | Qt::AlignTop);
dragMode
和setDragMode
函数可用于获取和设置视图的拖动模式。 这是视图的最重要函数之一,它可以决定在视图上单击并拖动鼠标左键时会发生什么。我们将使用QGraphicsView::DragMode
枚举设置不同的拖动模式。
cpp
QGraphicsView::NoDrag:忽略鼠标事件,不可以拖动。
QGraphicsView::ScrollHandDrag:光标变为手型,可以拖动场景进行移动。
QGraphicsView::RubberBandDrag:使用橡皮筋效果,进行区域选择,可以选中一个区域内的所有图形项。
dragMode
和setDragMode
函数可用于获取和设置视图的拖动模式。 这是视图的最重要函数之一,它可以决定在视图上单击并拖动鼠标左键时会发生什么。我们将使用QGraphicsView::DragMode
枚举设置不同的拖动模式。
cpp
QGraphicsView::NoDrag:忽略鼠标事件,不可以拖动。
QGraphicsView::ScrollHandDrag:光标变为手型,可以拖动场景进行移动。
QGraphicsView::RubberBandDrag:使用橡皮筋效果,进行区域选择,可以选中一个区域内的所有图形项。
我们已经了解到场景中的每个项目和场景中的每个项目都有各自的坐标系 ,我们需要使用映射函数 将位置从一个位置转换到另一个位置,反之亦然。 视图也是如此。 视图还具有自己的坐标系,主要区别在于视图中的位置和矩形等实际上是根据像素进行测量的,因此它们是整数,但是场景和项目的位置使用实数,等等。 这是由于以下事实:场景和项目在视图上被查看之前都是逻辑实体,因此所有实数都将转换为整数,而整个场景(或部分场景)准备在屏幕上显示。下图可以帮助您更好地理解这一点:
mapFromScene
和mapToScene
函数可用于在场景坐标系之间转换位置。与前面提到的一致,mapFromScene
函数接受实数并返回整数值,而mapToScene
函数接受整数并返回实数。稍后我们将开发视图的缩放功能时,将使用这些函数。items
函数可用于获取场景中的项目列表。render
函数对于执行整个视图或其一部分的渲染很有用。该函数的用法与QGraphicsScene
中的render
完全相同,只是此函数在视图上执行相同的功能。rubberBandRect
函数可用于获取橡皮筋选择的矩形。如前所述,这仅在拖动模式设置为rubberBandSelectionMode
时才有意义。setScene
和scene
函数可用于设置和获取视图场景。setMatrix
,setTransform
,transform
,rotate
,scale
,shear
和translate
函数都可以用于修改或检索视图的几何特性。
1.1 缩放与旋转
cpp
QGraphicsView::scale(xScale, yScale);//在分别在x,y方向上缩放xScale,yScale倍。若为1.0倍,则不进行缩放。
QGraphicsView::rotate(90);//顺时针旋转90度
功能实现:
cpp
//重写QGraphicsView类中滑轮事件,完成缩放功能。MyGraphicsView为继承QGraphicsView的子类
void MyGraphicsView::wheelEvent(QWheelEvent *event)
{
// 获取当前鼠标相对于view的位置;
QPointF cursorPoint = event->pos();
// 获取当前鼠标相对于scene的位置;
QPointF scenePos = this->mapToScene(QPoint(cursorPoint.x(), cursorPoint.y()));
// 获取view的宽高;
qreal viewWidth = this->viewport()->width();
qreal viewHeight = this->viewport()->height();
// 获取当前鼠标位置相当于view大小的横纵比例;
qreal hScale = cursorPoint.x() / viewWidth;
qreal vScale = cursorPoint.y() / viewHeight;
// 当前放缩倍数;
//qreal scaleFactor = this->matrix().m11();
int wheelDeltaValue = event->delta();
// 向上滚动,放大
if (wheelDeltaValue > 0)
{
this->scale(1.4, 1.4);
}
// 向下滚动,缩小;
else
{
this->scale(1/ 1.4, 1 / 1.4);;
}
// 将scene坐标转换为放大缩小后的坐标;
QPointF viewPoint = this->matrix().map(scenePos);
// 通过滚动条控制view放大缩小后的展示scene的位置;
this->horizontalScrollBar()->setValue(int(viewPoint.x() - viewWidth * hScale));
this->verticalScrollBar()->setValue(int(viewPoint.y() - viewHeight * vScale));
QGraphicsView::wheelEvent(event);
}
1.2 其他事件传递
在上面我们看到必须在事件函数的最后将event
参数传递出去,才能执行默认的事件操作。其实不止上面那一种情况,在图形视图框架中,鼠标键盘等事件是从视图View
进入的,视图View
将它们传递给场景Scene
,场景Scene
再将事件传递给该点的图形项Item
,如果该点有多个图形项,那么就传给最上面的图形项。所以要想使这个事件能一直传播下去,我们就需要在重新实现事件处理函数时,在其最后将event
参数传给默认的事件处理函数 。比如我们重写了场景的键盘按下事件处理函数,那么我们就在该函数的最后写上QGraphicsView::keyPressEvent(event);
除了像上面那样通过直接重写QGraphicsView
的虚函数的方式来实现事件的传递之外,还可以通过事件过滤器eventFilter
的方式来实现:
cpp
MyGraphicsView::MyGraphicsView(QWidget *parent)
:QGraphicsView(parent)
{
this->viewport()->installEventFilter(this);
}
bool MyGraphicsView::eventFilter(QObject *obj, QEvent *event)
{
/*
实现方式
*/
return false; //返回false表示不过滤
}
2. QGraphicsScene场景类
场景分为3层:分别是背景层、图形项层、前景层。我们绘制的图形item都是放在图形项层的:
QGraphicsScene
类提供了处理多个图形项(GraphicsItem
)所需的几乎所有方法。其主要作用是作为一个容器放置图元Item
,本身是不可见的,必须关联到至少一个QGraphicsView
。这两者的关系就画布和画板的关系,View
画板,负责显示;Scene
是画布,负责存储图形数据。所以从这个角度出发,我们可以这样认为,一个Scene
可以关联到多个View
,就好比一份画布贴到不同的画板上来被外界所看到一样。
addEllipse
,addLine
,addRect
和addPolygon
函数可以从它们的名称中猜测出来,可以用来向场景添加通用的几何形状。 它们中的一些提供了重载函数,以便于输入参数。 创建并添加到场景时,上述每个函数都会返回其对应的QGraphicsItem
子类实例(如下所示)。返回的指针可以保留,以后可用于修改,删除或以其他方式使用该项目:
cpp
QGraphicsEllipseItem
QGraphicsLineItem
QGraphicsRectItem
QGraphicsPolygonItem
使用示例:
cpp
scene.addEllipse(-100.0, 100.0, 200.0, 100.0,
QPen(QBrush(Qt::SolidPattern), 2.0),
QBrush(Qt::Dense2Pattern));
scene.addLine(-200.0, 200, +200, 200,
QPen(QBrush(Qt::SolidPattern), 5.0));
scene.addRect(-150, 150, 300, 140);
QVector<QPoint> points;
points.append(QPoint(150, 250));
points.append(QPoint(250, 250));
points.append(QPoint(165, 280));
points.append(QPoint(150, 250));
scene.addPolygon(QPolygon(points));

2.1 QPainterPath的使用
QPainterPath
类(绘图路径)提供了一个容器,用于绘图操作,可以创建和重用图形形状。
绘图路径是由许多图形化的构建块组成的对象,例如:矩形、椭圆、直线和曲线。构建块可以加入在封闭的子路径中,例如:矩形或椭圆。封闭的路径的起点和终点是一致的,或者他们可以作为未封闭的子路径独立存在,如:直线和曲线。
QPainterPath
可以被填充、描绘轮廓、裁剪。要为一个指定的绘图路径生成可填充的轮廓,可以使用 QPainterPathStroker
类。与正常绘图相比,QPainterPath
的主要优点在于:复杂的图形只需创建一次,然后只需调用QPainter::drawPath()
函数即可绘制多次。
QPainterPath
提供了一组函数,可用于获取绘图路径及其元素的信息。除了可以使用toReversed()
函数来改变元素的顺序外,还有几个函数将 QPainterPath
对象转换成一个多边形表示。
QPainterPath
对象可以用指定的起点,或者另一个QPainterPath
对象的副本来构造一个空路径。
一旦创建,可以使用 lineTo()
、arcTo()
、cubicTo()
和quadTo()
函数将直线和曲线添加到路径中,直线和曲线从 currentPosition()
处伸展到其传递的参数的所在点的位置。
QPainterPath
对象的currentPosition()
始终是最后一个添加的子路径的最终位置(或初始起点),使用 moveTo()
函数可以在不增加组件的情况下移动 currentPositon()
,moveTo()
函数会隐式地启动一个新的子路径,并关闭前一个。启动新的子路径的另一种方式是调用 closeSubpath()
函数,该函数通过添加一条直线(从 currentPosition()
到起始位置)来关闭当前路径。**注意:**新路径将 (0, 0) 作为其初始 currentPosition()
。
QPainterPath
也提供了一些便利的函数来添加一个封闭的子路径 - addEllipse()
、addPath()
、 addRect()
、addRegion()
和 addText()
。addPolygon()
函数添加一个未封闭的子路径。事实上,这些函数都是 moveTo()
、lineTo()
、cubicTo()
操作的集合。
此外,使用 connectPath() 函数将路径添加至当前路径。但需要注意,该函数将通过添加一条直线,将当前路径的最后一个元素连接到给定的第一个元素。
addPath
函数可用于将QPainterPath
与给定的QPen
和QBrush
添加到场景中。QPainterPath
类可用于记录绘画操作,类似于我们在QPainter
中看到的操作,并在以后使用它们。另一方面,QPen
和QBrush
类具有不言自明的标题。addPath
函数返回一个指向新创建的QGraphicsPathItem
实例的指针。
addSimpleText
和addText
函数可用于将纯文本和带格式的文本添加到场景中。 它们分别返回指向QGraphicsSimpleTextItem
或QGraphicsTextItem
的指针。
cpp
QGraphicsScene *pScene = new QGraphicsScene();
QGraphicsView *pView = new QGraphicsView();
//1、将项目Item添加到场景Scene中,即构造一个QGraphicsScene的对象。
pScene->addText("Hello, world!");//添加文本图形项
//2、使用QGraphicsView::setScene()将Scene加入到View中,视图View的可视化需要场景Scene的加入来进行实现。
pView->setScene(pScene);
pView->show();
代码结果:
2.2 Scene中添加item
图形项Item
添加到场景Scene
中有两种方式,一是通过调用便利函数addEllipse()
、addLine()
、addPath()
、addPixmap()
、addPolygon()
、addRect()
或addText()
等,这些函数都返回指向新添加项的指针。二是调用addIitem()
添加现有的QGraphicsItem
对象(主要是自定义继承的QGraphicsItem
);使用这些函数添加的项目的尺寸是相对于项目的坐标系的,并且项目位置在场景中初始化为(0,0)。
那么,上面addText("Hello, world!")
则可以换转成方式二:通过调用addIitem()
添加现有的QGraphicsItem
对象:
cpp
QGraphicsTextItem *item = new QGraphicsTextItem("Hello, world!");
scene.addItem(item);
3. QGraphicsItem图元类
它是场景中各个图元的基类,在它的基础上可以继承出各种图元类,即使您可以将其子类化并创建自己的图形项(自己重新实现绘制函数),Qt也会提供一组子类,这些子类可用于大多数(如果不是全部)日常图形任务。 以下是这些子类:
cpp
QGraphicsEllipseItem //椭圆
QGraphicsLineItem //直线
QGraphicsPathItem
QGraphicsPixmapItem
QGraphicsPolygonItem //多边形
QGraphicsRectItem //矩形
QGraphicsSimpleTextItem
QGraphicsTextItem //文本图元
QGraphicsItem主要有以下功能:
- 处理鼠标按下、移动、释放、双击、悬停、滚轮和右键菜单事件。
- 处理键盘输入事件。
- 处理拖拽事件。
- 分组。
- 碰撞检测。
acceptDrops和setAcceptDrops函数可用于使项目接受拖放事件。 请注意,这与我们在前面的示例中已经看到的拖放事件非常相似,但是这里的主要区别是项目本身可以识别拖放事件。
acceptHoverEvents,setAcceptHoverEvents,acceptTouchEvents,setAcceptTouchEvents,acceptedMouseButtons和setAcceptedMouseButtons函数均处理项目交互及其对鼠标单击的响应等。 这里要注意的重要一点是,一个项目可以根据Qt::MouseButtons枚举设置来响应或忽略不同的鼠标按钮。 这是一个简单的例子:
cpp
QGraphicsRectItem *item = new QGraphicsRectItem(0, 0, 100, 100, this);
item->setAcceptDrops(true);
item->setAcceptHoverEvents(true);
item->setAcceptedMouseButtons( Qt::LeftButton |
Qt::RightButton |
Qt::MidButton);
boundingRegion函数可用于获取描述图形项区域的QRegion类。 这是一项非常重要的函数,因为它可用于获取需要绘制(或重绘)项目的确切区域,并且与项目的边界矩形不同,因为简单地说,该项目可能仅覆盖其边界矩形的一部分,如直线等。