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

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()

相关文章:

  • 【鸿蒙Next】优秀鸿蒙博客集锦
  • 简单了解低代码Low Code
  • repo学习使用
  • HTTP/2 由来及特性
  • 探寻氧化铈:催化剂领域的璀璨明珠-京煌科技
  • 第39周:猫狗识别 2(Tensorflow实战第九周)
  • 上课啦 | 2月17日软考高项【5月备考班】
  • DeepSeek神经网络:技术架构与实现原理探析
  • VSCode选择编译工具(CMake)
  • HarmonyOS 5.0应用开发——Canvas制作个人签名
  • Linux开源生态与开发工具链的探索之旅
  • 通过openresty和lua实现随机壁纸
  • 基于SSM的农产品供销小程序+LW示例参考
  • 布隆过滤器详解及使用:解决缓存穿透问题
  • GDB 使用心得
  • MySQL技术公开课:Mysql-Server-8.4.4 Innodb 集群搭建与维护
  • Spring Boot 的约定优于配置:简化开发的艺术
  • mapbox V3 新特性,添加下雪效果
  • 科技查新测试有多重要?如何选择合适的第三方测试机构服务?
  • 深入浅出Java反射:掌握动态编程的艺术
  • 目前做哪个网站能致富/深圳整合营销
  • 东莞建网站公司/目前最流行的拓客方法
  • 政府网站建设以什么为宗旨/百度竞价收费标准
  • 服务公司取名/郑州seo使用教程
  • 医学专业网站/百度贴吧首页
  • 北京建筑人才网/seo网络营销推广公司