QGraphicsView图形视图框架使用(一)坐标变换

 

文章目录
图形视图框架的组成
图形视图框架的坐标系
Item坐标系
Scene坐标系
View坐标系
图形视图框架的坐标原点
使用组合对象
图元坐标变换

在设计静态用户交互界面的时候常见的QWidget控件已经够用了。但是如果用户想同时使用多个自绘控件并与其进行交互,常见的静态控件实现起来就比较困难了。比如在一些平面的2D游戏里面,使用者需要同时在界面里面对多个对象进行操作,显然静态控件系统是无法实现的。针对这种场景,QT提供了图形视图框架(Graphics View)来进行处理,这里就简单的介绍一下图形视图框架的用法。
图形视图框架的组成
Graphics View图形视图框架主要由三部分组成分别是:
视图对象(View) 对应着QGraphicsView类
场景对象(Scene) 对应着QGraphicsScene类
元素对象(Item) 对应着QGraphicsItem类
三个元素之间的关系如下图所示:


Items就像屋子里面的东西,我们可以搬动屋子里面的东西,损坏屋子里面东西,将屋子里面的东西放到一起等等。QT提供了很多继承自QGraphicsItem的对象,我们可以自由的创建和销毁,同时我们也可以根据需要创建自定义的Item对象。自定义Item对象必须实现paint()接口和boundingRect()接口,paint()负责对元素进行绘制,boundingRect()会返回绘制的图形的边界。

Scene就像一个屋子,屋子里面放满了各种各样的物品(Item)。在屋子里面我们可以自由的移动和处理物品。屋子负责展示和记录各个物品的位置和现状。同时屋子还能通知物品发生变化,比如地震了,屋子会通知物品发生位移变化等等。

View就像屋子的窗户,通过窗户我们既可以看到屋子的全景,也可以聚焦到屋子里面的一部分场景。通过窗户,我们可以对屋子里面的物品进行操作处理。

QGraphicsView是一个标准控件,我们可以将它和别的控件组合到一起使用。

图形视图框架的坐标系
为了正确使用GraphicsView,我们需要理解图像框架的坐标系。这对我们在图像系统中对元素进行定位和移动是非常重要的。
图形系统的坐标系包括三类:Item坐标系、Scene坐标系、View坐标系。下面分别介绍这个三个坐标系。

Item坐标系
图形框架中的每个Item都有自己的坐标系,坐标系的原点在左上定点。每个对象内部的元素都是通过相对于坐标原点的相对位置来进行定位的。不管我们如何移动旋转Item,Item自身的坐标系保持不变,Item内部元素之间的相对关系保持不变。在Item内部的paint()操作、鼠标事件操作、以及boundingRect()边界操作,使用的都是Item自身的坐标系坐标。

Scene坐标系
Scene坐标系的原点在场景的中心,我们可以通过setPos函数来修改每个Item在场景中的绝对坐标。如果我们在场景中对Item进行相对移动,我们可以通过调用moveBy()函数对Item进行移动。移动的时候,我们只需要指定x方向和y方向上的位移就可以了。

我们还可以通过setRotation()来对Item进行旋转,setRotation(90)就是顺时针旋转90度,setRotation(-90)就是逆时针旋转90度。默认旋转中心是Item的坐标原点,我们还可以通过setTransformOriginPoint()来指定新的坐标变换中心。

如果想对Item实行更加复杂的位移变换,我们可以使用QTransform对象来进行配置,配置完成的位移变化矩阵可以通过setTransform设置生效。

View坐标系
View视图由视窗窗口和两个滚动条组成,我们可以通过滚动条和视图窗口查看Scene的一部分图像。scene.SceneRect()是整个场景的显示范围,当增删Item的时候这个显示范围会发生变化。我们可以通过setSceneRect()来调整这个范围。

Scene与View的关系可以是一对一的,也可以是一对多的,也就是说一个Scene可能有多个View来进行查看。这是候Scene场景setSceneRect()是调整整个地图显显示范围的大小,会影响每一个View的显示。而View视图setScenenRect()是修改视图查看的范围,只会影响当前的View。使用scene.setSceneRect(0,50,100,100)设置之后,如果缩小程序的窗口大小,上端的视图会隐藏,如果放大程序的窗口,下端的视图会显示出来。其实setSceneRect()并没有修改视图的实际内容,只是修改了各个view能通过滚动条查看的范围。如果程序窗口足够大对应的内容还是能显示出来的。

如果Scene足够小的话,有时候为了查看Item方便,我们可以使用view的setAlignment,将视图的某个点和场景的某个位置进行对齐。view.setAlignment(Qt::AlignTop |Qt::AlignLeft) 就是设置Scene和View左上角对齐。如果Scene的显示范围很大,View里面无法完全显示的话,这时候滚动条就会出现,我们可以通过调整滚动条来查看Scene的全景。同时也可以通过horizontalScrollBarPolicy和verticalScrollBarPolicy属性来配置滚动条。

我们还可以通过view直接对整个scene进行调整。view.scale(5,5)可以将所有元素放大5倍,view.rotate(20)可以将整个scene旋转20度。还可以通过QTransform将整个场景进行更复杂的位移变化。

图形视图框架的坐标原点
如果直接在Scene中加入一个Item的话,默认位移变换的原点(坐标系原点)和Scene的坐标系原点是重合的,此时针对Item的位移变化都是针对Scene坐标原点的,对应的操作如下:

QGraphicsScene scene;
//在Scene中心添加一个十字坐标
scene.addLine(-200, 0, 200, 0);
scene.addLine(0, -200, 0, 200);
//原始位置的矩形
QGraphicsRectItem *rectItem = new QGraphicsRectItem(QRectF(50, 50, 50, 50));
//旋转之后的矩形
QGraphicsRectItem *rotate_rectItem = new QGraphicsRectItem(QRectF(50, 50, 50, 50));
rotate_rectItem->setRotation(45);
scene.addItem(rectItem);
scene.addItem(rotate_rectItem);

QGraphicsView view(&scene);
view.show();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
显示效果如下所示:

如果想改变位移变化的中心点,有两种方法:
方法1通过setTransformOriginPoint()修改位移变化的中心点

QGraphicsScene scene;
//在Scene中心添加一个十字坐标
scene.addLine(-200, 0, 200, 0);
scene.addLine(0, -200, 0, 200);
//原始位置的矩形
QGraphicsRectItem *rectItem = new QGraphicsRectItem(QRectF(50, 50, 50, 50));
//旋转之后的矩形
QGraphicsRectItem *rotate_rectItem = new QGraphicsRectItem(QRectF(50, 50, 50, 50));
rotate_rectItem->setTransformOriginPoint(75,75);
rotate_rectItem->setRotation(45);
scene.addItem(rectItem);
scene.addItem(rotate_rectItem);

QGraphicsView view(&scene);
view.show();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
显示效果如下:

这种做法,将Scene坐标系中的(75,75)设置成了位移变化的中心,只对setRotation()和setScale()两个函数产生了影响,对setTransform()不起作用。

方法二通过setPos()修改图元的位置
通过setPos()函数相当于修改了item坐标系的坐标原点,相应的图元的位移变化的中心点也就发生了变化。对应的操作如下:

QGraphicsRectItem* rectItem = scene.addRect(-25, -25, 50, 50);
//Scene坐标系下的(75,75)变成了Item坐标系的坐标原点
rectItem->setPos(75, 75);
//setRatation()或者setTransform()的位移变化的中心都变成了(75,75)
rectItem->setRotation(45);
1
2
3
4
5
使用组合对象
上面介绍的都是标准图形的坐标体系。在实际的开发和使用过程中,我们使用的一般都是复合图元,也就是包含childItem的Item。下面就介绍一下复合图元创建使用方法:

QGraphicsRectItem *createCombineItem(qreal width, qreal height, qreal radius)
{
//创建矩形父对象
QRectF rect(-width / 2, -height / 2, width, height);
QGraphicsRectItem *parent = new QGraphicsRectItem(rect);
QRectF circleBoundary(-radius, -radius, 2 * radius, 2 * radius);

//创建四个角上的Item
for(int i = 0; i < 4; i++)
{
QGraphicsEllipseItem *child = new QGraphicsEllipseItem(circleBoundary, parent);
child->setBrush(QBrush(QColor("#4D9CF8")));
QPointF pos;
switch(i) {
case 0:
pos = rect.topLeft();
break;
case 1:
pos = rect.bottomLeft();
break;
case 2:
pos = rect.topRight();
break;
case 3:
pos = rect.bottomRight();
break;
}
child->setPos(pos);
}

//创建中心的Item
QGraphicsEllipseItem *child = new QGraphicsEllipseItem(circleBoundary, parent);
child->setBrush(QBrush(QColor("#4D9CF8")));
child->setPos(rect.center());
return parent;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsRectItem * combine_rect = createCombineItem(100,100,10);
scene.addItem(combine_rect);
QGraphicsView view(&scene);
view.show();

return a.exec();

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
显示效果如下所示:

在创建组合对象的时候有几点需要注意:
1.创建顶层对象的时候不需要指定它的父对象,它的父对象是Scene本身
2.创建子对象的时候需要指定它的直接父对象,整个层级关系可能是多层嵌套。
3.子对象通过setPos()指定的是该对象在父对象坐标系中的位置,顶层对象setPos()指定的是该对象在scene坐标体系中的位置。
4.添加了对象间的父子关系之后,对父对象的操作都会影响到子对象,比如删除父对象子对象也会被删除。添加父对象的时候,子对象也会被添加进来。

图元坐标变换
图形视图框架体系里面包含Item坐标系、View坐标系、Scene坐标系,同时同一个组合Item里面还包含嵌套对象的父坐标系和子坐标系。这么多坐标系之间的坐标变换,也就成了一个让人头疼的问题。为了解决这个问题,QGraphicsItem提供了各个坐标系之间转换的函数,对应的介绍如下:

函数 描述
mapToScene(QPointF &point) 将当前Item坐标系中点坐标转换成Scene坐标系中对应的坐标。
mapFromScene(QPointF&point) 将Scene坐标系中的点坐标转换成Item坐标系中对应的点坐标
scenePos() 返回图元在Scene坐标系中的坐标,等价于mapToScene(0,0)
sceneBoundingRect() 返回Item在Scene坐标系统中的边界范围
mapToParent(QPointF &point) 将当前Item坐标系中的点坐标转换成父对象Item坐标系中的对应的坐标。如果没有父对象的话,该函数等价于mapToScene()
mapFromParent(QPointF &point) 将父对象坐标系中的点坐标转换成当前对象Item坐标系中对应的坐标。
mapToItem(const QGraphicsItem *item, const QPointF &point) 将当前Item坐标系中的点坐标转换成指定对象坐标系中的坐标。
mapFromItem(const QGraphicsItem *item, const QPointF &point) 将指定对象坐标系中的坐标,转换成当前Item坐标系中的坐标。
上面的函数接口,不仅有针对QPointF的版本,还有针对QRectF、QPolygonF、QPainterPath的重载版本,可以说是非常强大了。有了这些函数,我们可以轻松的实现各个坐标系之间的坐标转换了。

QGrahicsView也支持mapToScene()函数和mapFromScene()函数,可以实现Scene坐标系和View坐标系之间的相互转换。
————————————————
版权声明:本文为CSDN博主「码农飞飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yang1fei2/article/details/125665552

from:  https://blog.csdn.net/yang1fei2/article/details/125665552

posted @ 2023-09-07 23:38  imxiangzi  阅读(567)  评论(0编辑  收藏  举报