QT图形视图框架(Graphics View)

本文大多转载自《快速掌握PyQt5》第三十四章 图形视图框架

一、Graphicsview 是什么

1、Graphics View提供了一个平面,用于管理和交互大量自定义的2D图形图元,以及一个用于可视化图元的视图窗口小部件,支持缩放和旋转,包含了一套完整的事件体系,包括鼠标(Hover等)、键盘事件。

2、Graphics View使用BSP(二进制空间分区)树来提供非常快速的图元发现,因此,即使有数百万个图元,它也可以实时显示大型场景。

3、创建简单的基本流程

from PySide2 import QtCore, QtGui
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import sys

class SingleNote(QGraphicsTextItem):
    def __init__(self, parent=None, scene=None):
        super(SingleNote, self).__init__(parent, scene)
        self.setHtml("<strong>Hello</strong>")
        

    #鼠标进入时,设置大小为2.0
    def hoverEnterEvent(self, QGraphicsSceneHoverEvent):
        self.setScale(2.0)

    #鼠标离开时,设置大小为0.5
    def hoverLeaveEvent(self, QGraphicsSceneHoverEvent):
        self.setScale(0.5)
        
class NoteScene(QGraphicsScene):
    def __init__(self, parent=None):
        super(NoteScene, self).__init__(parent)

class MainView(QGraphicsView):
    def __init__(self, parent=None):
        super(MainView, self).__init__(parent)

if __name__ == "__main__":

    app = QApplication(sys.argv)

    # 创建一个QGraphicsView对象
    mv = MainView()

    #创建一个QGraphicsScence对象
    scene = NoteScene()

    #创建一个QGraphicsTextItem对象
    item = SingleNote()

    #为场景添加item
    scene.addItem(item)
    
    #为view设置场景
    mv.setScene(scene)

    mv.show()
    app.exec_()

二、三个主要类

1、QGraphicsItem图元类

图元可以是文本、图片,规则几何图形或者任意自定义图形。该类已经提供了一些标准的图元,比如:

  • 直线图元QGraphicsLineItem
  • 矩形图元QGraphicsRectItem
  • 椭圆图元QGraphicsEllipseItem
  • 图片图元QGraphicsPixmapItem
  • 文本图元QGraphicsTextItem
  • 路径图元QGraphicsPathItem
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QColor, QPainterPath
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, QGraphicsEllipseItem, \
                            QGraphicsPixmapItem, QGraphicsTextItem, QGraphicsPathItem, QGraphicsScene, QGraphicsView
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()

        self.resize(300, 300)
 
        # 设置场景
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)
 
        # 直线图元
        self.line = QGraphicsLineItem()
        self.line.setLine(100, 10, 200, 10)
        # self.line.setLine(QLineF(100, 10, 200, 10))
 
        # 矩形图元
        self.rect = QGraphicsRectItem()
        self.rect.setRect(100, 30, 100, 30)
        # self.rect.setRect(QRectF(100, 30, 100, 30))
 
        # 椭圆图元
        self.ellipse = QGraphicsEllipseItem()
        self.ellipse.setRect(100, 80, 100, 20)
        # self.ellipse.setRect(QRectF(100, 80, 100, 20))
 
        # 图片图元
        self.pic = QGraphicsPixmapItem()
        self.pic.setPixmap(QPixmap('img1.jpg').scaled(60, 60))
        self.pic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable) #可选择,可移动、
        self.pic.setOffset(100, 120)
        # self.pic.setOffset(QPointF(100, 120))
 
        # 文本图元
        self.text1 = QGraphicsTextItem()
        self.text1.setPlainText('Hello PyQt5')
        self.text1.setDefaultTextColor(QColor(66, 222, 88))
        self.text1.setPos(100, 180)
 
        self.text2 = QGraphicsTextItem()
        self.text2.setPlainText('Hello World')
        self.text2.setTextInteractionFlags(Qt.TextEditorInteraction)    #可编辑
        self.text2.setPos(100, 200)
 
        self.text3 = QGraphicsTextItem()
        self.text3.setHtml('<a href="https://baidu.com">百度</a>')
        self.text3.setOpenExternalLinks(True)
        self.text3.setTextInteractionFlags(Qt.TextBrowserInteraction)   #可跳转到其他网页
        self.text3.setPos(100, 220)
 
        # 路径图元
        self.path = QGraphicsPathItem()
 
        self.tri_path = QPainterPath()
        self.tri_path.moveTo(100, 250)
        self.tri_path.lineTo(130, 290)
        self.tri_path.lineTo(100, 290)
        self.tri_path.lineTo(100, 250)
        self.tri_path.closeSubpath()
 
        self.path.setPath(self.tri_path)
 

        # 添加图元到场景中
        self.scene.addItem(self.line)
        self.scene.addItem(self.rect)
        self.scene.addItem(self.ellipse)
        self.scene.addItem(self.pic)
        self.scene.addItem(self.text1)
        self.scene.addItem(self.text2)
        self.scene.addItem(self.text3)
        self.scene.addItem(self.path)
 
        # 设置场景
        self.setScene(self.scene)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

运行结果:
在这里插入图片描述

QGraphicsItem支持以下特性:

  • 鼠标按下、移动、释放和双击事件,以及鼠标悬浮事件、滚轮事件和右键
  • 菜单事件
  • 键盘输入事件
  • 拖放事件
  • 分组
  • 碰撞检测

事件的传递顺序
视图->场景->图元

import sys
from PyQt5.QtWidgets import QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView
 
 
class CustomItem(QGraphicsRectItem):
    def __init__(self):
        super(CustomItem, self).__init__()
        self.setRect(100, 30, 100, 30)
 
    def mousePressEvent(self, event):
        print('event from QGraphicsItem')
        super().mousePressEvent(event)
 
 
class CustomScene(QGraphicsScene):
    def __init__(self):
        super(CustomScene, self).__init__()
        self.setSceneRect(0, 0, 300, 300)
 
    def mousePressEvent(self, event):
        print('event from QGraphicsScene')
        super().mousePressEvent(event)
 
 
class CustomView(QGraphicsView):
    def __init__(self):
        super(CustomView, self).__init__()
        self.resize(300, 300)
 
    def mousePressEvent(self, event):
        print('event from QGraphicsView')
        super().mousePressEvent(event)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    view = CustomView()
    scene = CustomScene()
    item = CustomItem()
 
    scene.addItem(item)
    view.setScene(scene)
 
    view.show()
    sys.exit(app.exec_())

输出结果:
event from QGraphicsView
event from QGraphicsScene
event from QGraphicsItem

子图元传递到父图元

import sys
from PyQt5.QtWidgets import QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView
 
 
class CustomItem(QGraphicsRectItem):
    def __init__(self, num):
        super(CustomItem, self).__init__()
        self.setRect(100, 30, 100, 30)
        self.num = num
 
    def mousePressEvent(self, event):
        print('event from QGraphicsItem{}'.format(self.num))
        super().mousePressEvent(event)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    view = QGraphicsView()
    scene = QGraphicsScene()
    item1 = CustomItem(1)
    item2 = CustomItem(2)
    item2.setParentItem(item1)
 
    scene.addItem(item1)
    view.setScene(scene)
 
    view.show()
    sys.exit(app.exec_())

输出结果:
event from QGraphicsItem2
event from QGraphicsItem1

图元分组

所谓分组也就是将各个图元进行分类,分到一起的图元就会共同行动(选中、移动以及复制等)。我们通过下面的代码来演示下:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPen, QBrush
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsScene, \
                            QGraphicsView, QGraphicsItemGroup
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)
 
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)
 
        # 1
        self.rect1 = QGraphicsRectItem()
        self.rect2 = QGraphicsRectItem()
        self.ellipse1 = QGraphicsEllipseItem()
        self.ellipse2 = QGraphicsEllipseItem()
 
        self.rect1.setRect(100, 30, 100, 30)
        self.rect2.setRect(100, 80, 100, 30)
        self.ellipse1.setRect(100, 140, 100, 20)
        self.ellipse2.setRect(100, 180, 100, 50)
 
        # 2
        pen1 = QPen(Qt.SolidLine)
        pen1.setColor(Qt.blue)
        pen1.setWidth(3)
        pen2 = QPen(Qt.DashLine)
        pen2.setColor(Qt.red)
        pen2.setWidth(2)
 
        brush1 = QBrush(Qt.SolidPattern)
        brush1.setColor(Qt.blue)
        brush2 = QBrush(Qt.SolidPattern)
        brush2.setColor(Qt.red)
 
        self.rect1.setPen(pen1)
        self.rect1.setBrush(brush1)
        self.rect2.setPen(pen2)
        self.rect2.setBrush(brush2)
        self.ellipse1.setPen(pen1)
        self.ellipse1.setBrush(brush1)
        self.ellipse2.setPen(pen2)
        self.ellipse2.setBrush(brush2)
 
        # 3
        self.group1 = QGraphicsItemGroup()
        self.group2 = QGraphicsItemGroup()
        self.group1.addToGroup(self.rect1)
        self.group1.addToGroup(self.ellipse1)
        self.group2.addToGroup(self.rect2)
        self.group2.addToGroup(self.ellipse2)
        self.group1.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
        self.group2.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
        print(self.group1.boundingRect())
        print(self.group2.boundingRect())
 
        # 4
        self.scene.addItem(self.group1)
        self.scene.addItem(self.group2)
 
        self.setScene(self.scene)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

输出结果:
在这里插入图片描述

碰撞检测

碰撞检测可以以边界为范围或者以形状为范围,如图,一个椭圆图元,边界为虚线,形状为实线
在这里插入图片描述

Qt.ContainsItemBoundingRect

0x2

以边界为范围,当前图元被其他图元完全包含住

Qt.IntersectsItemBoundingRect

0x3

以边界为范围,当前图元被完全包含或者与其他图元有交集

几种具体的检测方式:

常量 触发条件
Qt.ContainsItemShape 0x0 以形状为范围,当前图元被其他图元完全包含住
Qt.IntersectsItemShape 0x1 以形状为范围,当前图元被完全包含或者与其他图元有交集
Qt.ContainsItemBoundingRect 0x2 以边界为范围,当前图元被其他图元完全包含住
Qt.IntersectsItemBoundingRect 0x3 以边界为范围,当前图元被完全包含或者与其他图元有交集
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsScene, \
                            QGraphicsView
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)
 
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)
 
        self.rect = QGraphicsRectItem()
        self.ellipse = QGraphicsEllipseItem()
        self.rect.setRect(120, 30, 50, 30)
        self.ellipse.setRect(100, 180, 100, 50)
        self.rect.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
        self.ellipse.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
 
        self.scene.addItem(self.rect)
        self.scene.addItem(self.ellipse)
 
        self.setScene(self.scene)
 
    def mouseMoveEvent(self, event):
        if self.ellipse.collidesWithItem(self.rect, Qt.IntersectsItemBoundingRect):
            print(self.ellipse.collidingItems(Qt.IntersectsItemShape))
        super().mouseMoveEvent(event)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

信号与槽
QGraphicsItem不继承自QObject,所以本身并不能使用信号和槽机制,我们也无法给它添加动画。不过我们可以自定义一个类,并让该类继承自QGraphicsObject。

import sys
from PyQt5.QtCore import QPropertyAnimation, QPointF, QRectF, pyqtSignal
from PyQt5.QtWidgets import QApplication, QGraphicsScene, QGraphicsView,  QGraphicsObject
 
 
class CustomRect(QGraphicsObject):
    # 1
    my_signal = pyqtSignal()
 
    def __init__(self):
        super(CustomRect, self).__init__()
 
    # 2
    def boundingRect(self):
        return QRectF(0, 0, 100, 30)
 
    # 3
    def paint(self, painter, styles, widget=None):
        painter.drawRect(self.boundingRect())
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)
 
        # 4
        self.rect = CustomRect()
        self.rect.my_signal.connect(lambda: print('signal and slot'))
        self.rect.my_signal.emit()
 
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)
        self.scene.addItem(self.rect)
 
        self.setScene(self.scene)
 
        # 5
        self.animation = QPropertyAnimation(self.rect, b'pos')
        self.animation.setDuration(3000)
        self.animation.setStartValue(QPointF(100, 30))
        self.animation.setEndValue(QPointF(100, 200))
        self.animation.setLoopCount(-1)
        self.animation.start()
        
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

2、QGraphicsScene场景类

场景提供了很多用于管理图元的方法

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QTransform
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)
 
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)
 
        # 1
        self.rect = self.scene.addRect(100, 30, 100, 30)
        self.ellipse = self.scene.addEllipse(100, 80, 50, 40)
        self.pic = self.scene.addPixmap(QPixmap('pic.png').scaled(60, 60))
        self.pic.setOffset(100, 130)
 
        self.rect.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable)
        self.ellipse.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable)
        self.pic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable)
 
        self.setScene(self.scene)
 
        # 2
        print(self.scene.items())
        print(self.scene.items(order=Qt.AscendingOrder))
        print(self.scene.itemsBoundingRect())
        print(self.scene.itemAt(110, 40, QTransform()))
 
        # 3
        self.scene.focusItemChanged.connect(self.my_slot)
 
    def my_slot(self, new_item, old_item):
        print('new item: {}\nold item: {}'.format(new_item, old_item))
 
    # 4
    def mouseMoveEvent(self, event):
        print(self.scene.collidingItems(self.ellipse, Qt.IntersectsItemShape))
        super().mouseMoveEvent(event)
 
    # 5 还需要修改
    def mouseDoubleClickEvent(self, event):
        item = self.scene.itemAt(event.pos(), QTransform())
        self.scene.removeItem(item)
        super().mouseDoubleClickEvent(event)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

3 QGraphicsView视图类

视图其实是一个滚动区域,用于显示场景

import sys
from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtGui import QColor, QBrush
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)
 
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 500, 500)
        self.ellipse = self.scene.addEllipse(QRectF(200, 200, 50, 50), brush=QBrush(QColor(Qt.blue)))
        self.rect = self.scene.addRect(QRectF(300, 300, 50, 50), brush=QBrush(QColor(Qt.red)))
        self.ellipse.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
        self.rect.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
 
        self.setScene(self.scene)
 
        self.press_x = None
 
    # 1
    def wheelEvent(self, event):
        if event.angleDelta().y() < 0:
            self.scale(0.9, 0.9)
        else:
            self.scale(1.1, 1.1)
        # super().wheelEvent(event)
 
    # 2
    def mousePressEvent(self, event):
        self.press_x = event.x()
        # super().mousePressEvent(event)
 
    def mouseMoveEvent(self, event):
        if event.x() > self.press_x:
            self.rotate(10)
        else:
            self.rotate(-10)
        # super().mouseMoveEvent(event)
        
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

三、图形视图的坐标体系

场景坐标
视图坐标
图元坐标

图形视图提供了三种坐标系之间相互转换的函数,以及图元与图元之间的转换函数

  • QGraphicsView.mapToScene() 视图到场景
  • QGraphicsView.mapFromScene() 场景到视图
  • QGraphicsItem.mapFromScene() 场景到图元
  • QGraphicsItem.mapToScene() 图元到场景
  • QGraphicsItem.mapToParent() 子图元到父图元
  • QGraphicsItem.mapFromParent() 父图元到子图元
  • QGraphicsItem.mapToItem() 当前图元到其他图元
  • QGraphicsItem.mapFromItem() 其他图元到当前图元
import sys
from PyQt5.QtGui import QPixmap, QTransform
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView
 
 
class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)
 
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)
 
        self.rect = self.scene.addRect(100, 30, 100, 30)
        self.ellipse = self.scene.addEllipse(100, 80, 50, 40)
        self.pic = self.scene.addPixmap(QPixmap('pic.png').scaled(60, 60))
        self.pic.setOffset(100, 130)
 
        self.rect.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
        self.ellipse.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
        self.pic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
 
        self.setScene(self.scene)
 
    def mouseDoubleClickEvent(self, event):
        item = self.scene.itemAt(event.pos(), QTransform())
        self.scene.removeItem(item)
        super().mouseDoubleClickEvent(event)
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

猜你喜欢

转载自blog.csdn.net/peixin_huang/article/details/104325527