![图片[1] - PyQtNode Editor 第二篇自定义可视化视图 - 宋马](https://pic.songma.com/blogimg/20250707/d7c28d4bb840496fb5882793edd0d20b.png)
在第一篇博客中,我们已经完成了 PyQtNode Editor 的基础环境搭建,并深入解析了自定义图形场景QDMGraphicsScene的实现原理。那个带有网格背景的场景就像一张空白的图纸,现在我们要在这张图纸上开始绘制真正的节点系统。
今天我们将聚焦于节点编辑器的核心数据结构设计,实现 “节点类” 与 “图形节点类” 的分离,并完成第一个可交互节点的创建。这一步是整个编辑器的骨架,就像建造房屋时搭建承重结构一样关键。
一、自定义可视化视图函数
创建一个node_graphic_view.py文件
在 PyQtNode Editor 的开发过程中,图形视图的设置对于用户最终看到的界面效果和操作体验至关重要。接下来,我们深入解析QDMGraphicsView类的代码,看看它是如何定制图形场景的显示与交互的。
导入必要的模块
from PyQt5.QtWidgets import QGraphicsView
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QGraphicsView:从PyQt5.QtWidgets模块中导入QGraphicsView类。QGraphicsView是 PyQt5 中用于显示QGraphicsScene(图形场景)的视图类,它就像是一个 “画框”,可以将在场景中绘制的图形、节点等内容展示出来,并且提供了缩放、滚动等与图形交互的功能基础。
from PyQt5.QtCore import *:导入PyQt5.QtCore模块中的所有内容。这个模块包含了 PyQt5 的核心功能,例如信号与槽机制、定时器、元对象系统等,这些功能为图形视图的事件处理、状态管理等提供底层支持 。
from PyQt5.QtGui import *:导入PyQt5.QtGui模块中的所有内容。该模块主要用于处理图形相关的操作,比如绘制图形、设置画笔和画刷样式、处理字体等,是实现图形视图美观显示效果的关键。
定义 QDMGraphicsView 类
class QDMGraphicsView(QGraphicsView):
这里定义了一个名为QDMGraphicsView的类,它继承自QGraphicsView。继承的作用在于,QDMGraphicsView类可以复用QGraphicsView类已有的功能,同时在此基础上添加或修改特定的功能,以满足 PyQtNode Editor 项目中对于图形视图的个性化需求。
、
def __init__(self, grScene, parent=None):
super().__init__(parent)
self.grScene = grScene
self.initUI()
self.setScene(self.grScene)
super().init(parent):调用父类QGraphicsView的初始化方法,确保父类的属性和行为得到正确的初始化,就像建造房子时先搭建好基本的框架结构。
self.grScene = grScene:将传入的图形场景对象grScene赋值给当前类的实例属性self.grScene。这样,QDMGraphicsView类的实例就与特定的图形场景建立了关联,后续视图展示的内容就是这个场景中的元素。
self.initUI():调用自定义的initUI方法,用于对图形视图进行一些个性化的设置,比如渲染效果、滚动条策略等。
self.setScene(self.grScene):将之前保存的图形场景对象self.grScene设置为当前图形视图要显示的场景。这一步就像是把一幅画放入画框中,图形视图会将场景中的图形、节点等内容展示出来供用户查看和交互。
def initUI(self):
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform):通过setRenderHints方法设置图形渲染的提示选项。这里使用了按位或操作组合了多个渲染提示:
QPainter.Antialiasing:启用抗锯齿功能,使绘制的图形边缘更加平滑,避免出现锯齿状。
QPainter.HighQualityAntialiasing:进一步提高抗锯齿的质量,让图形看起来更加精美。
QPainter.TextAntialiasing:对文本进行抗锯齿处理,保证显示的文字清晰、美观。
QPainter.SmoothPixmapTransform:使图像在缩放或变换时更加平滑,避免出现模糊或失真的情况。
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate):使用setViewportUpdateMode方法设置视图端口的更新模式为QGraphicsView.FullViewportUpdate。这意味着每当场景发生变化时,整个视图端口都会被更新。虽然这种方式在性能上可能不如其他更精细的更新模式,但可以确保视图显示的内容始终是最新和完整的。
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)和self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff):分别设置水平和垂直滚动条的策略为Qt.ScrollBarAlwaysOff,即始终隐藏滚动条。这表明在当前的设计中,不希望用户通过滚动条来滚动视图,可能是因为场景的尺寸设置以及整体交互设计不需要滚动条,或者后续会通过其他方式(如鼠标滚轮缩放等)来实现对场景的浏览。
完整代码node_graphic_view.py
# 导入PyQt5的QGraphicsView类,用于显示和操作QGraphicsScene中的内容
from PyQt5.QtWidgets import QGraphicsView
# 导入PyQt5的核心模块,包含信号与槽、事件系统等基础功能
from PyQt5.QtCore import *
# 导入PyQt5的GUI模块,包含绘图、字体、颜色等图形相关功能
from PyQt5.QtGui import *
# 自定义图形视图类,继承自QGraphicsView,用于显示和操作节点编辑器的图形场景
class QDMGraphicsView(QGraphicsView):
# 类的构造函数,接收图形场景对象和可选的父对象
def __init__(self, grScene, parent=None):
# 调用父类的构造函数完成初始化
super().__init__(parent)
# 保存传入的图形场景对象的引用
self.grScene = grScene
# 初始化用户界面设置
self.initUI()
# 将图形场景设置到视图中,使视图显示该场景的内容
self.setScene(self.grScene)
# 初始化用户界面的方法,设置视图的各种属性和行为
def initUI(self):
# 设置渲染提示,启用多项抗锯齿和高质量渲染选项:
# - Antialiasing:平滑图形边缘,减少锯齿
# - HighQualityAntialiasing:使用更高质量的抗锯齿算法
# - TextAntialiasing:平滑文本边缘,提高可读性
# - SmoothPixmapTransform:平滑处理缩放和变换后的像素图
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
# 设置视图更新模式为完全更新,每次场景变化时重绘整个视图
# 这种模式确保显示效果一致,但可能影响性能
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
# 设置水平滚动条策略为始终关闭,不显示水平滚动条
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 设置垂直滚动条策略为始终关闭,不显示垂直滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
二、调用自定义视图函数
回到node_graphic_wnd.py文件
导入我们自定义的画布样式:
from node_graphics_view import QDMGraphicsView
然后对原来系统的标准视图更改成我们自定义的视图:
这个是原来的
self.view = QGraphicsView(self)
self.view.setScene(self.grScene)
更改后的:
self.view = QDMGraphicsView(self.grScene, self)
然后添加一个addDebugContent()方法向场景中添加了多种图形元素用于测试:
def addDebugContent(self):
# 创建绿色画刷和黑色轮廓画笔
greenBrush = QBrush(Qt.green)
outlinePen = QPen(Qt.black)
outlinePen.setWidth(2)
# 添加可移动的矩形
rect = self.grScene.addRect(-100, -100, 80, 100, outlinePen, greenBrush)
rect.setFlag(QGraphicsItem.ItemIsMovable)
# 添加可选择和移动的文本
text = self.grScene.addText("This is my Awesome text!", QFont("Ubuntu"))
text.setFlag(QGraphicsItem.ItemIsSelectable)
text.setFlag(QGraphicsItem.ItemIsMovable)
text.setDefaultTextColor(QColor.fromRgbF(1.0, 1.0, 1.0))
# 添加可移动的按钮控件
widget1 = QPushButton("Hello World")
proxy1 = self.grScene.addWidget(widget1)
proxy1.setFlag(QGraphicsItem.ItemIsMovable)
proxy1.setPos(0, 30)
# 添加可选择的文本编辑控件
widget2 = QTextEdit()
proxy2 = self.grScene.addWidget(widget2)
proxy2.setFlag(QGraphicsItem.ItemIsSelectable)
proxy2.setPos(0, 60)
# 添加可选择和移动的线条
line = self.grScene.addLine(-200, -200, 400, -100, outlinePen)
line.setFlag(QGraphicsItem.ItemIsMovable)
line.setFlag(QGraphicsItem.ItemIsSelectable)
最终完整的node_graphic_wnd.py文件
# 从PyQt5的QtWidgets模块导入所有的类和函数,该模块包含各种用于创建用户界面的组件,如窗口、按钮、布局等
from PyQt5.QtWidgets import *
# 从PyQt5的QtGui模块导入所有的类和函数,此模块用于处理图形相关操作,如画笔、画刷、字体、绘图等功能
from PyQt5.QtGui import *
# 从PyQt5的QtCore模块导入所有的类和函数,该模块提供了PyQt5的核心功能,包括信号与槽机制、事件循环、定时器等
from PyQt5.QtCore import *
# 从自定义模块node_graphics_scene中导入QDMGraphicsScene类,该类用于创建自定义的图形场景
from node_graphics_scene import QDMGraphicsScene
# 从自定义模块node_graphics_view中导入QDMGraphicsView类,该类用于创建自定义的图形视图
from node_graphics_view import QDMGraphicsView
# 定义NodeEditorWnd类,继承自QWidget,用于创建节点编辑器的主窗口
class NodeEditorWnd(QWidget):
# 类的初始化方法,接收一个可选的父窗口参数parent,默认为None
def __init__(self, parent=None):
# 调用父类QWidget的初始化方法,确保父类的属性和行为得到正确初始化
super().__init__(parent)
# 调用initUI方法,初始化用户界面
self.initUI()
# 初始化用户界面的方法
def initUI(self):
# 设置窗口在屏幕上的位置和大小,参数依次为x坐标、y坐标、宽度、高度
self.setGeometry(200, 200, 800, 600)
# 创建一个垂直布局管理器,用于管理窗口内的子控件布局
self.layout = QVBoxLayout()
# 设置布局管理器的边距为0,即子控件将填充整个布局区域,不留边界空白
self.layout.setContentsMargins(0, 0, 0, 0)
# 将创建好的垂直布局设置为当前窗口的布局
self.setLayout(self.layout)
# 创建自定义的图形场景对象,用于承载图形元素
self.grScene = QDMGraphicsScene()
# 创建自定义的图形视图对象,传入图形场景对象和当前窗口作为参数
# 图形视图用于显示图形场景中的内容,并提供交互功能
self.view = QDMGraphicsView(self.grScene, self)
# 将图形视图添加到之前创建的垂直布局中,使其显示在窗口内
self.layout.addWidget(self.view)
# 设置窗口的标题栏显示文本
self.setWindowTitle("Node Editor")
# 显示窗口,使其在屏幕上可见
self.show()
# 调用addDebugContent方法,向图形场景中添加用于调试的内容
self.addDebugContent()
# 向图形场景中添加调试内容的方法
def addDebugContent(self):
# 创建一个绿色的画刷对象,用于填充图形区域
greenBrush = QBrush(Qt.green)
# 创建一个黑色的画笔对象,并设置画笔宽度为2,用于绘制图形的轮廓
outlinePen = QPen(Qt.black)
outlinePen.setWidth(2)
# 在图形场景中添加一个矩形,参数依次为矩形左上角x坐标、y坐标、宽度、高度、画笔、画刷
# 并将返回的矩形图形项对象赋值给rect变量
rect = self.grScene.addRect(-100, -100, 80, 100, outlinePen, greenBrush)
# 设置矩形图形项为可移动,用户可以通过鼠标拖动它
rect.setFlag(QGraphicsItem.ItemIsMovable)
# 在图形场景中添加一段文本,传入文本内容和字体对象
# 并将返回的文本图形项对象赋值给text变量
text = self.grScene.addText("This is my Awesome text!", QFont("Ubuntu"))
# 设置文本图形项为可选择,用户可以选中它
text.setFlag(QGraphicsItem.ItemIsSelectable)
# 设置文本图形项为可移动,用户可以通过鼠标拖动它
text.setFlag(QGraphicsItem.ItemIsMovable)
# 设置文本的默认颜色为白色,使用RGBF格式(红、绿、蓝分量值范围为0.0 - 1.0)
text.setDefaultTextColor(QColor.fromRgbF(1.0, 1.0, 1.0))
# 创建一个QPushButton按钮控件,显示文本为"Hello World"
widget1 = QPushButton("Hello World")
# 将按钮控件添加到图形场景中,并返回一个代理对象,用于在图形场景中管理该控件
proxy1 = self.grScene.addWidget(widget1)
# 设置按钮的代理对象为可移动,用户可以通过鼠标拖动按钮
proxy1.setFlag(QGraphicsItem.ItemIsMovable)
# 设置按钮在图形场景中的位置,参数为x坐标和y坐标
proxy1.setPos(0, 30)
# 创建一个QTextEdit文本编辑控件
widget2 = QTextEdit()
# 将文本编辑控件添加到图形场景中,并返回一个代理对象
proxy2 = self.grScene.addWidget(widget2)
# 设置文本编辑控件的代理对象为可选择,用户可以选中它
proxy2.setFlag(QGraphicsItem.ItemIsSelectable)
# 设置文本编辑控件在图形场景中的位置
proxy2.setPos(0, 60)
# 在图形场景中添加一条直线,参数依次为起点x坐标、y坐标、终点x坐标、y坐标、画笔
# 并将返回的直线图形项对象赋值给line变量
line = self.grScene.addLine(-200, -200, 400, -100, outlinePen)
# 设置直线图形项为可移动
line.setFlag(QGraphicsItem.ItemIsMovable)
# 设置直线图形项为可选择
line.setFlag(QGraphicsItem.ItemIsSelectable)
运行第一篇的主函数main()
得到测试结果:
从图片中依次显示了一个文字、线段、绿色矩形、节点窗口,这些都可以进行自由拖动。
![图片[2] - PyQtNode Editor 第二篇自定义可视化视图 - 宋马](https://pic.songma.com/blogimg/20250707/d119d5a8e04a45e5a03bd11846fcfd18.png)
鼠标拖动后:
目前文章的目录结构:

三、添加了鼠标中键按钮实现画布拖拽处理功能编写
回到编写的node_graphic_view.py文件
新增以下代码:
3.1 鼠标按键键函数功能
def mousePressEvent(self, event):
if event.button() == Qt.MiddleButton:
self.middleMouseButtonPress(event)
elif event.button() == Qt.LeftButton:
self.rightMouseButtonPress(event)
elif event.button() == Qt.RightButton:
self.leftMouseButtonPress(event)
else:
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.MiddleButton:
self.middleMouseButtonRelease(event)
elif event.button() == Qt.LeftButton:
self.leftMouseButtonRelease(event)
mousePressEvent:根据按下的鼠标按键(左 / 中 / 右)调用对应的处理方法
mouseReleaseEvent:根据释放的鼠标按键调用对应的处理方法
3.2 中键拖动实现核心逻辑
def middleMouseButtonPress(self, event):
# 1. 模拟左键释放事件
releaseEvent = QMouseEvent(QEvent.MouseButtonRelease, event.localPos(), event.screenPos(),
Qt.LeftButton, Qt.NoButton, event.modifiers())
super().mouseReleaseEvent(releaseEvent)
# 2. 设置拖动模式为ScrollHandDrag(手形拖动)
self.setDragMode(QGraphicsView.ScrollHandDrag)
# 3. 模拟左键按下事件
fakeEvent = QMouseEvent(event.type(), event.localPos(), event.screenPos(),
Qt.LeftButton, event.buttons() | Qt.LeftButton, event.modifiers())
super().mousePressEvent(fakeEvent)
def middleMouseButtonRelease(self, event):
# 1. 模拟左键释放事件
fakeEvent = QMouseEvent(event.type(), event.localPos(), event.screenPos(),
Qt.LeftButton, event.buttons() & ~Qt.LeftButton, event.modifiers())
super().mouseReleaseEvent(fakeEvent)
# 2. 恢复拖动模式为NoDrag
self.setDragMode(QGraphicsView.NoDrag)
这两个方法实现了中键拖动浏览场景的功能,采用了一种巧妙的事件模拟机制:
按下中键时:
先模拟释放左键(避免与其他左键操作冲突)
将视图的拖动模式设置为ScrollHandDrag(手形拖动模式)
再模拟按下左键(触发手形拖动)
释放中键时:
模拟释放左键(结束拖动)
将视图的拖动模式恢复为NoDrag
3.3 其他鼠标按键处理
def leftMouseButtonPress(self, event):
return super().mousePressEvent(event)
def leftMouseButtonRelease(self, event):
return super().mouseReleaseEvent(event)
def rightMouseButtonPress(self, event):
return super().mousePressEvent(event)
def rightMouseButtonRelease(self, event):
return super().mouseReleaseEvent(event)
这些方法目前只是简单地调用父类的实现,保留了默认的鼠标行为。在后续开发中,可以根据需要扩展这些方法,例如:
在右键按下时显示上下文菜单
实现左键选择节点的功能
添加鼠标滚轮缩放功能
此时完整的node_graphic_view.py代码文件
# 导入PyQt5的QGraphicsView类,用于显示和操作QGraphicsScene中的内容
from PyQt5.QtWidgets import QGraphicsView
# 导入PyQt5的核心模块,包含信号与槽、事件系统等基础功能
from PyQt5.QtCore import *
# 导入PyQt5的GUI模块,包含绘图、字体、颜色等图形相关功能
from PyQt5.QtGui import *
# 自定义图形视图类,继承自QGraphicsView,用于显示和操作节点编辑器的图形场景
class QDMGraphicsView(QGraphicsView):
# 类的构造函数,接收图形场景对象和可选的父对象
def __init__(self, grScene, parent=None):
# 调用父类的构造函数完成初始化
super().__init__(parent)
# 保存传入的图形场景对象的引用
self.grScene = grScene
# 初始化用户界面设置
self.initUI()
# 将图形场景设置到视图中,使视图显示该场景的内容
self.setScene(self.grScene)
# 初始化用户界面的方法,设置视图的各种属性和行为
def initUI(self):
# 设置渲染提示,启用多项抗锯齿和高质量渲染选项:
# - Antialiasing:平滑图形边缘,减少锯齿
# - HighQualityAntialiasing:使用更高质量的抗锯齿算法
# - TextAntialiasing:平滑文本边缘,提高可读性
# - SmoothPixmapTransform:平滑处理缩放和变换后的像素图
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
# 设置视图更新模式为完全更新,每次场景变化时重绘整个视图
# 这种模式确保显示效果一致,但可能影响性能
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
# 设置水平滚动条策略为始终关闭,不显示水平滚动条
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 设置垂直滚动条策略为始终关闭,不显示垂直滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def mousePressEvent(self, event):
# 判断鼠标按下的按键是否为中键
if event.button() == Qt.MiddleButton:
# 如果是中键按下,调用自定义的中键按下处理方法
self.middleMouseButtonPress(event)
# 判断鼠标按下的按键是否为左键
elif event.button() == Qt.LeftButton:
self.leftMouseButtonPress(event)
# 判断鼠标按下的按键是否为右键
elif event.button() == Qt.RightButton:
# 如果是右键按下,调用自定义的右键按下处理方法
self.rightMouseButtonPress(event)
else:
# 如果按下的是其他按键,调用父类的鼠标按下事件处理方法,
# 保证默认的事件处理逻辑能够执行
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
# 判断鼠标释放的按键是否为中键
if event.button() == Qt.MiddleButton:
# 如果是中键释放,调用自定义的中键释放处理方法
self.middleMouseButtonRelease(event)
# 判断鼠标释放的按键是否为左键
elif event.button() == Qt.LeftButton:
# 如果是左键释放,调用自定义的左键释放处理方法
self.leftMouseButtonRelease(event)
# 判断鼠标释放的按键是否为右键
elif event.button() == Qt.RightButton:
# 如果是右键释放,调用自定义的右键释放处理方法
self.rightMouseButtonRelease(event)
else:
# 如果释放的是其他按键,调用父类的鼠标释放事件处理方法,
# 维持默认的事件处理行为
super().mouseReleaseEvent(event)
def middleMouseButtonPress(self, event):
# 创建一个模拟的鼠标左键释放事件对象,用于模拟先释放可能存在的左键按下状态
# 参数依次为:事件类型(鼠标按键释放)、事件在视图局部坐标系的位置、在屏幕坐标系的位置、
# 释放的按键(左键)、未按下的按键、键盘修饰键状态
releaseEvent = QMouseEvent(QEvent.MouseButtonRelease, event.localPos(), event.screenPos(),
Qt.LeftButton, Qt.NoButton, event.modifiers())
# 调用父类的鼠标释放事件处理方法,处理模拟的左键释放事件
super().mouseReleaseEvent(releaseEvent)
# 设置图形视图的拖动模式为手形拖动模式,用户可以通过拖动来平移视图中的场景
self.setDragMode(QGraphicsView.ScrollHandDrag)
# 创建一个模拟的鼠标左键按下事件对象,用于触发手形拖动模式的操作
# 参数依次为:事件类型(与原始事件类型相同)、事件在视图局部坐标系的位置、在屏幕坐标系的位置、
# 按下的按键(左键)、当前按下的按键状态(添加左键按下状态)、键盘修饰键状态
fakeEvent = QMouseEvent(event.type(), event.localPos(), event.screenPos(),
Qt.LeftButton, event.buttons() | Qt.LeftButton, event.modifiers())
# 调用父类的鼠标按下事件处理方法,处理模拟的左键按下事件,从而启动手形拖动
super().mousePressEvent(fakeEvent)
def middleMouseButtonRelease(self, event):
# 创建一个模拟的鼠标左键释放事件对象,用于模拟释放左键以结束手形拖动
# 参数依次为:事件类型(鼠标按键释放)、事件在视图局部坐标系的位置、在屏幕坐标系的位置、
# 释放的按键(左键)、当前按下的按键状态(移除左键按下状态)、键盘修饰键状态
fakeEvent = QMouseEvent(event.type(), event.localPos(), event.screenPos(),
Qt.LeftButton, event.buttons() & ~Qt.LeftButton, event.modifiers())
# 调用父类的鼠标释放事件处理方法,处理模拟的左键释放事件,结束手形拖动
super().mouseReleaseEvent(fakeEvent)
# 将图形视图的拖动模式设置为无拖动模式,恢复到默认的交互状态
self.setDragMode(QGraphicsView.NoDrag)
def leftMouseButtonPress(self, event):
# 调用父类的鼠标左键按下事件处理方法,使用默认的左键按下处理逻辑
return super().mousePressEvent(event)
def leftMouseButtonRelease(self, event):
# 调用父类的鼠标左键释放事件处理方法,使用默认的左键释放处理逻辑
return super().mouseReleaseEvent(event)
def rightMouseButtonPress(self, event):
# 调用父类的鼠标右键按下事件处理方法,使用默认的右键按下处理逻辑
return super().mousePressEvent(event)
def rightMouseButtonRelease(self, event):
# 调用父类的鼠标右键释放事件处理方法,使用默认的右键释放处理逻辑
return super().mouseReleaseEvent(event)
此时重新运行主函数:按下鼠标中键可以实现对画布的自由拖拽以及移动。

四、鼠标滚轮实现对画布的自由缩放
在node_graphic_view.py代码文件下
首先在__init__函数下添加:
# 定义放大倍数因子,每次鼠标滚轮向上滚动时,视图将按照这个倍数进行放大
self.zoomInFactor = 1.25
# 定义是否开启缩放限制,当前设置为False,即暂不限制缩放范围
self.zoomClamp = False
# 定义当前的缩放级别,初始值设为10
self.zoom = 10
# 定义每次缩放时,缩放级别的变化量
self.zoomStep = 1
# 定义缩放范围,是一个列表,包含最小值和最大值,当前范围是[0, 10]
self.zoomRange = [0, 10]
在initUI函数下添加:
# 设置视图的变换锚点为鼠标所在位置,这意味着视图将以鼠标指针下方的点为中心进行缩放
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
在文件的末端添加子函数:
def wheelEvent(self, event):
# 计算缩小倍数因子,为放大倍数因子的倒数,用于鼠标滚轮向下滚动时缩小视图
zoomOutFactor = 1 / self.zoomInFactor
# 根据鼠标滚轮滚动方向计算缩放倍数和更新当前缩放级别
if event.angleDelta().y() > 0:
# 如果鼠标滚轮向上滚动(angleDelta().y() > 0),使用放大倍数因子
zoomFactor = self.zoomInFactor
# 增加当前缩放级别
self.zoom += self.zoomStep
else:
# 如果鼠标滚轮向下滚动,使用缩小倍数因子
zoomFactor = zoomOutFactor
# 减少当前缩放级别
self.zoom -= self.zoomStep
# 根据计算得到的缩放倍数,对视图进行缩放操作,x和y方向以相同倍数缩放
self.scale(zoomFactor, zoomFactor)
此时重新运行主函数
得到画面:
![图片[4] - PyQtNode Editor 第二篇自定义可视化视图 - 宋马](https://pic.songma.com/blogimg/20250707/422d8c733f5f4fd7ab6a48bf80178683.png)
滚动鼠标滚轮可是实现对画布的自由放大与缩小:

五、画布缩放进行优化
在node_graphic_view.py代码文件下进行编写
首先将缩放限制打开
# 定义是否开启缩放限制,当前设置为False,即暂不限制缩放范围
self.zoomClamp = True
在wheelEvent函数下添加以下进行编写:
# 初始化一个变量clamped,用于标记当前缩放级别是否超出了预设范围并被修正
clamped = False
# 判断当前的缩放级别self.zoom是否小于预设的缩放范围下限self.zoomRange[0]
# 如果小于下限,则将当前缩放级别self.zoom设置为范围下限self.zoomRange[0],
# 并将clamped标记为True,表示缩放级别已被修正
if self.zoom < self.zoomRange[0]: self.zoom, clamped = self.zoomRange[0], True
# 判断当前的缩放级别self.zoom是否大于预设的缩放范围上限self.zoomRange[1]
# 如果大于上限,则将当前缩放级别self.zoom设置为范围上限self.zoomRange[1],
# 并将clamped标记为True,表示缩放级别已被修正
if self.zoom > self.zoomRange[1]: self.zoom, clamped = self.zoomRange[1], True
# 判断是否需要执行视图缩放操作
# 条件为clamped为False(即当前缩放级别未超出范围并被修正)
# 或者self.zoomClamp为False(即未启用缩放限制功能)
# 只有满足上述条件之一时,才会执行后续的视图缩放代码
if not clamped or self.zoomClamp is False:
# 根据计算得到的缩放倍数,对视图进行缩放操作,x和y方向以相同倍数缩放
self.scale(zoomFactor, zoomFactor)
此时重新运行主函数:
发现画布并不能限的放大和缩小,具有一定的限制。
最大为:
![图片[5] - PyQtNode Editor 第二篇自定义可视化视图 - 宋马](https://pic.songma.com/blogimg/20250707/66186f3f3de546f8bbc0cb3248be9dae.png)
最小为:
![图片[6] - PyQtNode Editor 第二篇自定义可视化视图 - 宋马](https://pic.songma.com/blogimg/20250707/19f3074276bb40ce97124f7569a9b083.png)
完整的node_graphic_view.py
# 导入PyQt5的QGraphicsView类,用于显示和操作QGraphicsScene中的内容
from PyQt5.QtWidgets import QGraphicsView
# 导入PyQt5的核心模块,包含信号与槽、事件系统等基础功能
from PyQt5.QtCore import *
# 导入PyQt5的GUI模块,包含绘图、字体、颜色等图形相关功能
from PyQt5.QtGui import *
# 自定义图形视图类,继承自QGraphicsView,用于显示和操作节点编辑器的图形场景
class QDMGraphicsView(QGraphicsView):
# 类的构造函数,接收图形场景对象和可选的父对象
def __init__(self, grScene, parent=None):
# 调用父类的构造函数完成初始化
super().__init__(parent)
# 保存传入的图形场景对象的引用
self.grScene = grScene
# 初始化用户界面设置
self.initUI()
# 将图形场景设置到视图中,使视图显示该场景的内容
self.setScene(self.grScene)
# 定义放大倍数因子,每次鼠标滚轮向上滚动时,视图将按照这个倍数进行放大
self.zoomInFactor = 1.25
# 定义是否开启缩放限制,当前设置为False,即暂不限制缩放范围
self.zoomClamp = True
# 定义当前的缩放级别,初始值设为10
self.zoom = 10
# 定义每次缩放时,缩放级别的变化量
self.zoomStep = 1
# 定义缩放范围,是一个列表,包含最小值和最大值,当前范围是[0, 10]
self.zoomRange = [0, 10]
# 初始化用户界面的方法,设置视图的各种属性和行为
def initUI(self):
# 设置渲染提示,启用多项抗锯齿和高质量渲染选项:
# - Antialiasing:平滑图形边缘,减少锯齿
# - HighQualityAntialiasing:使用更高质量的抗锯齿算法
# - TextAntialiasing:平滑文本边缘,提高可读性
# - SmoothPixmapTransform:平滑处理缩放和变换后的像素图
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
# 设置视图更新模式为完全更新,每次场景变化时重绘整个视图
# 这种模式确保显示效果一致,但可能影响性能
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
# 设置水平滚动条策略为始终关闭,不显示水平滚动条
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 设置垂直滚动条策略为始终关闭,不显示垂直滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 设置视图的变换锚点为鼠标所在位置,这意味着视图将以鼠标指针下方的点为中心进行缩放
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
def mousePressEvent(self, event):
# 判断鼠标按下的按键是否为中键
if event.button() == Qt.MiddleButton:
# 如果是中键按下,调用自定义的中键按下处理方法
self.middleMouseButtonPress(event)
# 判断鼠标按下的按键是否为左键
elif event.button() == Qt.LeftButton:
self.leftMouseButtonPress(event)
# 判断鼠标按下的按键是否为右键
elif event.button() == Qt.RightButton:
# 如果是右键按下,调用自定义的右键按下处理方法
self.rightMouseButtonPress(event)
else:
# 如果按下的是其他按键,调用父类的鼠标按下事件处理方法,
# 保证默认的事件处理逻辑能够执行
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
# 判断鼠标释放的按键是否为中键
if event.button() == Qt.MiddleButton:
# 如果是中键释放,调用自定义的中键释放处理方法
self.middleMouseButtonRelease(event)
# 判断鼠标释放的按键是否为左键
elif event.button() == Qt.LeftButton:
# 如果是左键释放,调用自定义的左键释放处理方法
self.leftMouseButtonRelease(event)
# 判断鼠标释放的按键是否为右键
elif event.button() == Qt.RightButton:
# 如果是右键释放,调用自定义的右键释放处理方法
self.rightMouseButtonRelease(event)
else:
# 如果释放的是其他按键,调用父类的鼠标释放事件处理方法,
# 维持默认的事件处理行为
super().mouseReleaseEvent(event)
def middleMouseButtonPress(self, event):
# 创建一个模拟的鼠标左键释放事件对象,用于模拟先释放可能存在的左键按下状态
# 参数依次为:事件类型(鼠标按键释放)、事件在视图局部坐标系的位置、在屏幕坐标系的位置、
# 释放的按键(左键)、未按下的按键、键盘修饰键状态
releaseEvent = QMouseEvent(QEvent.MouseButtonRelease, event.localPos(), event.screenPos(),
Qt.LeftButton, Qt.NoButton, event.modifiers())
# 调用父类的鼠标释放事件处理方法,处理模拟的左键释放事件
super().mouseReleaseEvent(releaseEvent)
# 设置图形视图的拖动模式为手形拖动模式,用户可以通过拖动来平移视图中的场景
self.setDragMode(QGraphicsView.ScrollHandDrag)
# 创建一个模拟的鼠标左键按下事件对象,用于触发手形拖动模式的操作
# 参数依次为:事件类型(与原始事件类型相同)、事件在视图局部坐标系的位置、在屏幕坐标系的位置、
# 按下的按键(左键)、当前按下的按键状态(添加左键按下状态)、键盘修饰键状态
fakeEvent = QMouseEvent(event.type(), event.localPos(), event.screenPos(),
Qt.LeftButton, event.buttons() | Qt.LeftButton, event.modifiers())
# 调用父类的鼠标按下事件处理方法,处理模拟的左键按下事件,从而启动手形拖动
super().mousePressEvent(fakeEvent)
def middleMouseButtonRelease(self, event):
# 创建一个模拟的鼠标左键释放事件对象,用于模拟释放左键以结束手形拖动
# 参数依次为:事件类型(鼠标按键释放)、事件在视图局部坐标系的位置、在屏幕坐标系的位置、
# 释放的按键(左键)、当前按下的按键状态(移除左键按下状态)、键盘修饰键状态
fakeEvent = QMouseEvent(event.type(), event.localPos(), event.screenPos(),
Qt.LeftButton, event.buttons() & ~Qt.LeftButton, event.modifiers())
# 调用父类的鼠标释放事件处理方法,处理模拟的左键释放事件,结束手形拖动
super().mouseReleaseEvent(fakeEvent)
# 将图形视图的拖动模式设置为无拖动模式,恢复到默认的交互状态
self.setDragMode(QGraphicsView.NoDrag)
def leftMouseButtonPress(self, event):
# 调用父类的鼠标左键按下事件处理方法,使用默认的左键按下处理逻辑
return super().mousePressEvent(event)
def leftMouseButtonRelease(self, event):
# 调用父类的鼠标左键释放事件处理方法,使用默认的左键释放处理逻辑
return super().mouseReleaseEvent(event)
def rightMouseButtonPress(self, event):
# 调用父类的鼠标右键按下事件处理方法,使用默认的右键按下处理逻辑
return super().mousePressEvent(event)
def rightMouseButtonRelease(self, event):
# 调用父类的鼠标右键释放事件处理方法,使用默认的右键释放处理逻辑
return super().mouseReleaseEvent(event)
def wheelEvent(self, event):
# 计算缩小倍数因子,为放大倍数因子的倒数,用于鼠标滚轮向下滚动时缩小视图
zoomOutFactor = 1 / self.zoomInFactor
# 根据鼠标滚轮滚动方向计算缩放倍数和更新当前缩放级别
if event.angleDelta().y() > 0:
# 如果鼠标滚轮向上滚动(angleDelta().y() > 0),使用放大倍数因子
zoomFactor = self.zoomInFactor
# 增加当前缩放级别
self.zoom += self.zoomStep
else:
# 如果鼠标滚轮向下滚动,使用缩小倍数因子
zoomFactor = zoomOutFactor
# 减少当前缩放级别
self.zoom -= self.zoomStep
# 初始化一个变量clamped,用于标记当前缩放级别是否超出了预设范围并被修正
clamped = False
# 判断当前的缩放级别self.zoom是否小于预设的缩放范围下限self.zoomRange[0]
# 如果小于下限,则将当前缩放级别self.zoom设置为范围下限self.zoomRange[0],
# 并将clamped标记为True,表示缩放级别已被修正
if self.zoom < self.zoomRange[0]: self.zoom, clamped = self.zoomRange[0], True
# 判断当前的缩放级别self.zoom是否大于预设的缩放范围上限self.zoomRange[1]
# 如果大于上限,则将当前缩放级别self.zoom设置为范围上限self.zoomRange[1],
# 并将clamped标记为True,表示缩放级别已被修正
if self.zoom > self.zoomRange[1]: self.zoom, clamped = self.zoomRange[1], True
# 判断是否需要执行视图缩放操作
# 条件为clamped为False(即当前缩放级别未超出范围并被修正)
# 或者self.zoomClamp为False(即未启用缩放限制功能)
# 只有满足上述条件之一时,才会执行后续的视图缩放代码
if not clamped or self.zoomClamp is False:
# 根据计算得到的缩放倍数,对视图进行缩放操作,x和y方向以相同倍数缩放
self.scale(zoomFactor, zoomFactor)












暂无评论内容