Qt 之 QGraphicsView 案例

参考: QGraphicsView¶

QGraphicsView 在可滚动视口中可视化 QGraphicsScene 的内容。要创建包含几何项目的场景,请参见 QGraphicsScene 的文档。QGraphicsView 是 Graphics View Framework 的一部分。为了使场景可视化,首先要构造一个 QGraphicsView 对象,然后将要可视化的场景的地址传递给 QGraphicsView 的构造函数。另外,您可以调用 setScene() 在实例化后设置场景。调用 show() 后,默认情况下,视图将滚动到场景的中心并显示此时可见的所有项目。例如:

添加文本图元

scene = QGraphicsScene()
scene.addText("Hello, world!")
view = QGraphicsView(scene)
view.show()

完整代码:

from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets, Signal
from xinet.run_qt import run
class MainWindow(QtWidgets.QWidget):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.resize(500, 400)
        scene = QtWidgets.QGraphicsScene(self)  # 创建场景
        scene.addText("Hello, world!")  # 在场景中添加文本
        view = QtWidgets.QGraphicsView(scene, self)  # 创建视图窗口
        view.move(10, 10)
        view.show()
if __name__ == '__main__':
    run(MainWindow)

也可以设定文本字体:

font = QtGui.QFont("华文琥珀", 20, QtGui.QFont.Bold)
scene.addText("Hello, world!", font)  # 在场景中添加文本

您可以使用滚动条或调用 centerOn() 显式滚动到场景中的任何位置。通过将一个点传递给 centerOn(),QGraphicsView 将滚动其视口以确保该点在视图中居中。同时提供了用于滚动到 QGraphicsItem 的重载,在这种情况下,QGraphicsView 将看到项目的中心在视图中居中。如果只需要确保某个区域可见(但不必居中),则可以调用 sureVisible()

QGraphicsView 可用于可视化整个场景或其中的一部分。默认情况下,第一次显示视图时,会自动检测到可视化区域(通过调用 itemsBoundingRect())。要自己设置可视化区域矩形,可以调用 setSceneRect()。这将适当地调整滚动条的范围。请注意,尽管场景支持几乎不受限制的大小,但滚动条的范围永远不会超出整数 (INT_MIN, INT_MAX) 的范围。

QGraphicsView 通过调用 render() 可视化场景。默认情况下,使用常规 QPainter 并使用默认渲染提示将项目绘制到视口上。若要更改绘画项目时 QGraphicsView 传递给 QPainter 的默认渲染提示,可以调用setRenderHints()

默认情况下,QGraphicsView 为视口窗口小部件提供常规 QWidget。您可以通过调用 viewport() 来访问此小部件,也可以通过调用 setViewport() 来替换它。

要使用 OpenGL 进行渲染,只需调用 setViewport(新的 QOpenGLWidget)。QGraphicsView 拥有视口小部件的所有权。

QGraphicsView 使用 QTransform 支持仿射变换。最常见的两种转换是 scaling,用于实现缩放(zooming)和旋转(rotation)。QGraphicsView 在转换过程中保持视图中心不变。由于场景对齐(setAligment()),平移视图将不会产生视觉影响。您可以将矩阵传递给 setTransform(),也可以调用便捷函数之一 rotate()scale()translate()shear()

您可以使用鼠标和键盘与场景中的项目进行交互。QGraphicsView 将鼠标和键事件转换为场景事件(继承 QGraphicsSceneEvent 的事件),并将其转发到可视化场景。最后,处理事件并对事件做出反应的是独立项目。例如,如果单击一个可选择的项目,则该项目通常会让场景知道它已被选中,并且它还将重绘自身以显示选择矩形。类似地,如果您单击并拖动鼠标以移动可移动项,则它是处理鼠标移动并自行移动的项。默认情况下,图形项目互动处于启用状态,您可以触发调用。

您还可以通过创建 QGraphicsView 的子类并重新实现鼠标和键事件处理程序来提供自己的自定义场景交互。
为了简化您如何以编程方式与视图中的项目进行交互,QGraphicsView 提供了映射函数 mapToScene()mapFromScene() 以及项目访问器 items()itemAt()。这些功能允许您在视图坐标和场景坐标之间映射点,矩形,多边形和路径,并使用视图坐标在场景中查找项目。

from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets, Signal
from xinet.run_qt import run
class MainWindow(QtWidgets.QWidget):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.resize(500, 400)
        scene = QtWidgets.QGraphicsScene(self)  # 创建场景
        font = QtGui.QFont("华文琥珀", 20, QtGui.QFont.Bold)
        scene.addText("Hello, world!", font)  # 在场景中添加文本
        # 添加矩形
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor('blue'))
        pen.setWidth(3)
        brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
        brush.setColor(QtGui.QColor('yellow'))
        rect = QtCore.QRectF(130, 130, 100, 150)
        x, y = 30, 30
        w, h = 100, 100
        juxin = scene.addRect(x, y, w, h, pen, brush)  # 添加矩形图元--方式一
        juxin = scene.addRect(rect, pen, brush)  # 添加矩形图元--方式二
        rect = QtCore.QRectF(10, 10, 100, 100)
        item1 = QtWidgets.QGraphicsRectItem(rect)  # 创建矩形---以场景为坐标
        scene.addItem(item1)
        view = QtWidgets.QGraphicsView(scene, self)  # 创建视图窗口
        view.move(10, 10)
        view.show()
if __name__ == '__main__':
    run(MainWindow)
line = scene.addLine(10, 10, 50, 50, pen)  # 添加直线--方式一
lf = QtCore.QLineF(50, 50, 10, 50)
line = scene.addLine(lf, pen)  # 添加直线--方式二
item = QtWidgets.QGraphicsEllipseItem(10, 10, 100, 100)  # 创建椭圆--场景坐标
# 参数1 参数2  矩形左上角坐标
# 参数3 参数4 矩形的宽和高
scene.addItem(item)
item = scene.addEllipse(10, 10, 100, 100, pen, brush) # 方式1
rect = QtCore.QRectF(20, 20, 200, 200)
item = scene.addEllipse(rect, pen, brush) # 方式2

图元的操作(缩放,平移,旋转)

想要旋转,平移,缩放窗口中的图元,一般有两种思路:

  • 操作 View,概念类似于旋转摄像头,从而获取对场景 Scene 的不同观察结果,这样操作的好处是,场景中的图元本质上没有发生任何的变化,仅仅改变了 View 的计算矩阵,效率非常高,并且,如果该 Scene 被多个 View 绑定观察,对单独一个 view 的操作,将不会影响到别的 view 的观察结果
  • 操作 Scene 中所有的图元 Item 或者直接操作单个图元 Item,概念类似于真实改变了场景中的各个物体的摆放方式,该方法使用的场合:当一个场景,被多个 view 观察的时候,场景中的某一个变化操作,需要被这多个 view 同时观察到。
  • 图元的缩放

    rect = QRectF(100, 100, 200, 100)
    item = scene.addEllipse(rect, pen, brush)
    item.setScale(0.5)  # 对图元进行缩放
    # 参数为图元大小的缩放比
    # 需要注意的是,该方法的缩放基准是固定的,也就是说,
    # 如果连续两次调用该方法,但是参数的数字一样的话,第二次的调用将不会改变图元的大小
    print(item.scale())  # 返回图元的缩放比
    

    图元的平移

    item = scene.addRect(200, 200, 200, 100, pen, brush)
    item.setPos(100, 50)  # 移动图元--方式一
    # 参数 移动的偏移量
    # 其传参的x与y坐标值,属于场景Scene的坐标,
    # 如果调用的时候,坐标来源于view的鼠标点击事件的坐标,需要调用mapToScene()将view坐标值转换到场景坐标
    item.moveBy(100, 50)  # 移动图元--方式二
    

    图元的旋转

    item1 = scene.addRect(0, 0, 250, 25, pen, brush)
    item = scene.addRect(100, 100, 200, 100, pen, brush)
    item.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable |
                  QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsMovable)
    item.setPos(-100, -100)
    item.setTransformOriginPoint(0, 0)  # 设置旋转中心----???不按这点旋转啊
    # 默认 图元的左上角
    # 该方法传参的坐标值为图元自身的坐标系
    item.setRotation(30)  # 旋转图元
    # 参数 角度
    # 正数 逆时针
    # 设置的值的基准也是不变的,也就是说,连续两次调用该方法,都传入相同的参数值,图元只会旋转一次
    print(item.rotation())  # 返回旋转角度
    
    from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets, Signal
    from xinet.run_qt import run
    class QMyGraphicsView(QtWidgets.QGraphicsView):
        sigMouseMovePoint = Signal(QtCore.QPoint)
        # 自定义信号sigMouseMovePoint,当鼠标移动时,在mouseMoveEvent事件中,将当前的鼠标位置发送出去
        # QPoint--传递的是view坐标
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
        def mouseMoveEvent(self, evt):
            pt = evt.pos()  # 获取鼠标坐标--view坐标
            self.sigMouseMovePoint.emit(pt)  # 发送鼠标位置
            super().mouseMoveEvent(evt)
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.init_Ui()
            rect = QtCore.QRectF(-200, -100, 400, 200)
            self.scene = QtWidgets.QGraphicsScene(rect)  # 创建场景
            # 参数:场景区域
            # 场景坐标原点默认在场景中心---场景中心位于界面中心
            self.view.setScene(self.scene)  # 给视图窗口设置场景
            item1 = QtWidgets.QGraphicsRectItem(rect)  # 创建矩形---以场景为坐标
            item1.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable |
                           QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsMovable)  # 给图元设置标志
            # QGraphicsItem.ItemIsSelectable---可选择
            # QGraphicsItem.ItemIsFocusable---可设置焦点
            # QGraphicsItem.ItemIsMovable---可移动
            # QGraphicsItem.ItemIsPanel---
            self.scene.addItem(item1)  # 给场景添加图元
            for pos, color in zip([rect.left(), 0, rect.right()], [QtCore.Qt.red, QtCore.Qt.yellow, QtCore.Qt.blue]):
                # 创建椭圆--场景坐标
                item = QtWidgets.QGraphicsEllipseItem(-50, -50, 100, 100)
                # 参数1 参数2  矩形左上角坐标
                # 参数3 参数4 矩形的宽和高
                item.setPos(pos, 0)  # 给图元设置在场景中的坐标(移动图元)--图元中心坐标
                item.setBrush(color)  # 设置画刷
                item.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable |
                              QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsMovable)
                self.scene.addItem(item)
            self.scene.clearSelection()  # 【清除选择】
            self.view.sigMouseMovePoint.connect(self.slotMouseMovePoint)
        def init_Ui(self):
            self.resize(600, 400)
            self.view = QMyGraphicsView()  # 创建视图窗口
            self.setCentralWidget(self.view)  # 设置中央控件
            self.statusbar = self.statusBar()  # 添加状态栏
            self.labviewcorrd = QtWidgets.QLabel('view坐标:')
            self.labviewcorrd.setMinimumWidth(150)
            self.statusbar.addWidget(self.labviewcorrd)
            self.labscenecorrd = QtWidgets.QLabel('scene坐标:')
            self.labscenecorrd.setMinimumWidth(150)
            self.statusbar.addWidget(self.labscenecorrd)
            self.labitemcorrd = QtWidgets.QLabel('item坐标:')
            self.labitemcorrd.setMinimumWidth(150)
            self.statusbar.addWidget(self.labitemcorrd)
        def slotMouseMovePoint(self, pt):
            self.labviewcorrd.setText(f'view坐标:{pt.x()},{pt.y()}')
            ptscene = self.view.mapToScene(pt)  # 把view坐标转换为场景坐标
            self.labscenecorrd.setText(
                f'scene坐标:{ptscene.x():.0f},{ptscene.y():.0f}')
            item = self.scene.itemAt(
                ptscene, self.view.transform())  # 在场景某点寻找图元--最上面的图元
            # 返回值:图元地址
            if item != None:
                ptitem = item.mapFromScene(ptscene)  # 把场景坐标转换为图元坐标
                self.labitemcorrd.setText(
                    f'item坐标:{ptitem.x():.0f},{ptitem.y():.0f}')
    if __name__ == '__main__':
        run(MainWindow)