当前位置: 首页 > article >正文

PySide (PyQt)的视图(QGraphicsView)和场景(QGraphicsScene)

         在 PySide 或 PyQt 中,QGraphicsView 和 QGraphicsScene 是两个重要的类,它们一起构成了一个强大的框架,用于显示和操作二维图形项(如图像、线条、矩形、文本等)。

1. 场景(QGraphicsScene)的作用

  • 场景是一个无限大的二维平面,可以理解为一个“画布”,用于管理和存储所有的图形项(QGraphicsItem)。
  • 场景负责管理图形项的生命周期、事件处理、碰撞检测等底层逻辑。
  • 场景不直接负责显示图形项,而是将这些任务交给视图(QGraphicsView)。
场景的特点:
  • 无限大:场景的大小没有物理限制,因此可以容纳任意多的图形项。
  • 事件处理:场景会处理底层的事件(如鼠标点击、键盘输入等),并将这些事件传递给相应的图形项。
  • 管理图形项:场景负责添加、删除、查找和操作图形项。
场景的常见用法:
  • 添加图形项:scene.addItem(item)
  • 获取图形项:scene.items()
  • 处理事件:scene.mousePressEvent()

2. 视图(QGraphicsView)的作用

  • 视图是一个窗口或控件,用于显示场景的内容。它可以理解为一个“窗口”,用户通过视图来查看和操作场景中的图形项。
  • 视图提供了一些高级功能,比如滚动、缩放、旋转、剪裁等,以便用户更好地查看和操作场景中的内容。
  • 视图与场景的关系是“一对多”的,即一个场景可以被多个视图同时显示。
  • 视图中的场景是唯一的,可以通过setScene() 方法将另一个场景分配给视图,但这将替换已有的场景。
视图的特点:
  • 有限的显示区域:视图有一个可见的矩形区域(viewport),它决定了场景的哪些部分会被显示。
  • 坐标转换:视图负责将视图坐标和场景坐标之间进行转换(例如,mapToScene() 和 mapFromScene())。
  • 交互功能:视图可以处理用户的交互(如鼠标滚动、缩放等),并将其反映到场景中。
视图的常见用法:
  • 设置场景:view.setScene(scene)
  • 缩放视图:view.scale(x, y)
  • 获取当前视图的中心点:view.mapToScene(view.viewport().rect().center())

3. 视图与场景的关系

  • 场景是内容的容器:场景负责存储和管理所有的图形项,但它不直接负责显示这些图形项。
  • 视图是显示的窗口:视图负责将场景中的内容渲染到屏幕上,并提供用户交互功能。
  • 场景和视图是分离的:场景和视图的设计是分离的,这样可以实现更灵活的布局和显示方式。例如,一个场景可以同时被多个视图显示,每个视图可以有不同的缩放比例或视角。

4. 坐标系统的区别

  • 场景坐标:场景坐标是全局坐标系统,图形项的位置和大小都是相对于场景的左上角((0, 0))来定义的。
  • 视图坐标:视图坐标是相对于视图的可见区域(viewport)的坐标系统。视图坐标通常会受到视图的缩放、平移等操作的影响。
  • 像素坐标:像素坐标是物理屏幕上的坐标系统,通常用于处理用户输入事件。
坐标转换:
  • mapToScene():将视图坐标转换为场景坐标。
  • mapFromScene():将场景坐标转换为视图坐标。
  • mapToGlobal():将视图坐标转换为屏幕上的像素坐标。

5. 视图的显示

        可以将视图单独显示,也可以将其放置到QT用于显示的容器小部件中,如QMainWindow、QWidget。

单独显示的例子:

from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PySide6.QtCore import QPointF, QPoint

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()

# 创建一个视图,并设置场景
view = QGraphicsView(scene)

# 在视图中添加一个矩形
scene.addRect(0, 0, 100, 100)

view.show()
app.exec_()

在窗口部件中显示的例子:

from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout
from PySide6.QtCore import QPointF, QPoint

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()

# 创建一个视图,并设置场景
view = QGraphicsView(scene)

# 在视图中添加一个矩形
scene.addRect(0, 0, 100, 100)

# 创建主窗口
window = QWidget()
window.setGeometry(100, 100, 800, 600)
window.verticalLayout = QVBoxLayout(window)
window.verticalLayout.addWidget(view)
window.show()


app.exec_()

6. 一些demo和用法

•  两个视图同时显示一个场景
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout
from PySide6.QtCore import QPointF, QPoint, QRectF

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()
# 在场景中添加一个矩形
scene.addRect(0, 0, 100, 100)

# 创建两个视图
view1 = QGraphicsView()
view2 = QGraphicsView()

# 设置视图的场景
view1.setScene(scene)
view2.setScene(scene)
view2.scale(2.0, 2.0)

# 创建主窗口
window = QWidget()
window.setGeometry(100, 100, 800, 600)
window.verticalLayout = QVBoxLayout(window)
window.verticalLayout.addWidget(view1)
window.verticalLayout.addWidget(view2)
window.show()

app.exec_()

这里的两个视图用不同的比例显示了同一个场景: 


•  应用一系列QTransform(变换)的方法
from PySide6.QtGui import QTransform
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()
# 在场景中添加一个矩形
scene.addRect(0, 0, 100, 100)

# 创建视图
view = QGraphicsView()

# 设置视图的场景
view.setScene(scene)

# #####定义一个QTransform,并应用一系列变换#####
# transform = QTransform()
# transform.rotate(45)
# transform.scale(0.5, 0.5)
# ########################################


# 动态地应用上面的一系列变换
transformations = [
    lambda t: t.rotate(45),
    lambda t: t.scale(0.5, 0.5)
]

# 创建一个QTransform 对象用于测试动态应用变换
transform = QTransform()
for func in transformations:
    func(transform)

# 将 QTransform 对象应用到视图
view.setTransform(transform)

view.show()

app.exec()

上面的代码显示了如何单独应用变换,以及将其集成到列表中后统一应用。


• 动态显示的一个范例
from PySide6.QtCore import QRect, QTimer, Slot
from PySide6.QtGui import QTransform
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QGraphicsRectItem

# from mpl_toolkits.mplot3d.proj3d import transform

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()
rect = QRect(0, 0, 100, 100)   # 定义一个矩形
rect_item = QGraphicsRectItem(rect)   # 创建一个矩形图形项
scene.addItem(rect_item)   # 将矩形图形项添加到场景中

# 创建视图
view = QGraphicsView()
view.setSceneRect(0, 0, 800, 600)

# 设置视图的场景
view.setScene(scene)

# 创建一个定时器,每隔300ms触发一次move_rect槽函数
@Slot()
def move_rect():
    x = rect_item.pos().x() + 10
    y = rect_item.pos().y() + 10
    s = rect_item.scale() * 0.9
    r = rect_item.rotation() + 10
    rect_item.setPos(x, y)
    rect_item.setScale(s)
    rect_item.setRotation(r)

timer = QTimer()   # 创建一个定时器
timer.start(300)  # 启动定时器
timer.timeout.connect(move_rect)  # 将定时器的timeout信号连接到槽函数

view.show()

app.exec()

这段代码显示了如何实时动态地改变一个图形项的位置、比例和角度。 


• 场景与视窗之间坐标的转换

 下面的代码重写了视图的鼠标移动事件,运行一下,你就能搞清楚坐标的转换关系了。

from PySide6.QtCore import QRect, QTimer, Slot
from PySide6.QtGui import QTransform
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QGraphicsRectItem

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()
rect = QRect(0, 0, 100, 100)   # 定义一个矩形
rect_item = QGraphicsRectItem(rect)   # 创建一个矩形图形项
scene.addItem(rect_item)   # 将矩形图形项添加到场景中

# 自定义一个视图类
class MyView(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMouseTracking(True)  # 设置鼠标跟踪
    # 重写鼠标移动事件
    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        view_pos = event.position().toPoint()
        view_x = view_pos.x()
        view_y = view_pos.y()
        scene_pos = self.mapToScene(view_pos)
        scene_x = scene_pos.x()
        scene_y = scene_pos.y()

        print(f"视窗坐标为:{view_x}, {view_y}"
              f"场景坐标为:{scene_x}, {scene_y}")

view = MyView()
# 设置视图的场景
view.setScene(scene)
view.show()

app.exec()

结论:鼠标事件捕获到的坐标是相对于视图的坐标,其零点(0,0)在视图的左上角。

下面是我将鼠标移至矩形的中心附近的运行截图:

在这个范例中,场景的尺寸是100*100,所以场景坐标是50,50左右。 


• 判断坐标是否位于场景内以及判断坐标位置的物体
from PySide6.QtCore import QRect, QTimer, Slot
from PySide6.QtGui import QTransform, QImage, QPixmap, QPalette, QBrush, QColor
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QGraphicsRectItem, \
    QGraphicsPixmapItem

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()
scene.setSceneRect(0, 0, 700, 700)
image = QPixmap("demo.PNG")   # 600*300像素
image_item = QGraphicsPixmapItem(image)   # 创建一个图片图形项
image_item.setScale(0.5)   # 缩放图片
scene.addItem(image_item)   # 将矩形图形项添加到场景中


# 自定义一个视图类
class MyView(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.resize(800, 800)

    # 重写鼠标按下事件
    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        view_pos = event.position().toPoint()  # 将鼠标点击位置转换为视图坐标
        scene_pos = self.mapToScene(view_pos)  # 将视图坐标转换为场景坐标
        scene_rec = self.sceneRect()           # 场景的矩形在视图坐标系中的位置
        if scene_rec.contains(scene_pos):      # 判断鼠标点击位置是否在场景内
            print(f"鼠标点击在场景内, 场景坐标为: {scene_pos.x()}, {scene_pos.y()}")
            item = self.itemAt(view_pos)   # 获取鼠标点击处的图形项
            if item:    # 判断鼠标点击处是否有图形项
                print("鼠标点击在图形项内")
                print(item)
        else:
            print("鼠标点击在场景外")


view = MyView()
# 设置视图的场景
view.setScene(scene)
view.show()

app.exec()


• 获取坐标处的多个物体

        上面的代码使用itemAt()函数,获取到了坐标处的图像项物体,如果该处由多个物体,则返回最上层的那个。如果需要获取坐标处的所有的图像项物体,使用scene().items(scene_pos):

from PySide6.QtCore import QRect, QTimer, Slot
from PySide6.QtGui import QTransform, QImage, QPixmap, QPalette, QBrush, QColor
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QGraphicsRectItem, \
    QGraphicsPixmapItem

app = QApplication([])

# 创建一个场景
scene = QGraphicsScene()
scene.setSceneRect(0, 0, 700, 700)
image = QPixmap("demo.PNG")   # 600*300像素
image_item = QGraphicsPixmapItem(image)   # 创建一个图片图形项
image_item.setScale(0.5)   # 缩放图片
scene.addItem(image_item)   # 将矩形图形项添加到场景中
scene.addRect(100, 100, 100, 100)
scene.addRect(100, 100, 200, 200)



# 自定义一个视图类
class MyView(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.resize(800, 800)

    # 重写鼠标按下事件
    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        view_pos = event.position().toPoint()  # 将鼠标点击位置转换为视图坐标
        scene_pos = self.mapToScene(view_pos)  # 将视图坐标转换为场景坐标
        scene_rec = self.sceneRect()           # 场景的矩形在视图坐标系中的位置
        if scene_rec.contains(scene_pos):      # 判断鼠标点击位置是否在场景内
            print(f"鼠标点击在场景内, 场景坐标为: {scene_pos.x()}, {scene_pos.y()}")
            items = self.scene().items(scene_pos)   # 获取鼠标点击处的图形项
            if items:    # 判断鼠标点击处是否有图形项
                print("鼠标点击在图形项内")
                for item in items:
                    print(item, end=',')
        else:
            print("鼠标点击在场景外")


view = MyView()
# 设置视图的场景
view.setScene(scene)
view.show()

app.exec()


http://www.kler.cn/a/543384.html

相关文章:

  • LabVIEW商业软件开发
  • DevOps的个人学习
  • vue 主子表加校验问题
  • Jenkins+gitee 搭建自动化部署
  • VMware Workstation创建虚拟机
  • 不小心删除服务[null]后,git bash出现错误
  • 深入理解现代前端框架:Vue.js 的进阶探秘
  • bash shell笔记——循环结构
  • 网络初识-
  • 基于微信小程序的超市售货管理平台
  • HttpServletRequest 作用
  • 从洗牌算法到前端工程化:如何用JavaScript实现真正的随机排序并应用到实际项目中
  • 2025常用的SEO工具有哪些?
  • 文档格式转换引擎开发:支持PDF与OFD的技术实现
  • 快速搭建支持图片实时处理的文件服务
  • pytest测试专题 - 1.1 运行pytest
  • SpringBoot3.2新武器RestClient如何替代Feign?
  • 42页精品PPT | 数字政府智慧政务大数据平台建设方案
  • STM 32 IO模拟两线串行接口
  • 使用EVE-NG-锐捷实现ACL访问控制
  • 深度剖析观察者模式:从理论到实战的Java实现
  • Prompt 工程优化方
  • 如何本地部署DeepSeek集成Word办公软件
  • Java开发实战:使用IntelliJ IDEA 开发Spring Boot + MyBatis + MySQL的详细实现步骤
  • 【大数据安全分析】安全告警关联相关安全分析场景
  • 国际版宝塔面板安装教程(Centos7)