在 PyQtNode Editor 的开发之旅中,经过前两篇博客对基础环境搭建和核心类结构的探索,我们已经迈出了坚实的步伐。今天,我们将聚焦于node_scene文件,深入解析其中的代码逻辑。这段代码构建了Scene类,它如同整个节点编辑器的 “管理中枢”,承担着组织和协调节点、边等关键元素的重要职责,是推动编辑器功能运转的核心力量。
首先创建一个node_scene.py文件
导入必要的模块
from node_graphics_scene import QDMGraphicsScene
这里从自定义模块 node_graphics_scene 中导入 QDMGraphicsScene 类。QDMGraphicsScene 类通常用于处理图形场景的可视化相关操作,比如绘制背景、管理场景中的图形元素显示等。通过导入这个类,Scene 类可以利用它来实现节点和边在界面上的展示,将逻辑数据与可视化效果进行关联。
定义 Scene 类
class Scene:
Scene 类用于管理节点编辑器中的节点和边的逻辑数据,以及与图形场景进行交互。它是整个节点编辑系统中数据管理和协调的核心部分,后续关于节点和边的创建、删除、存储等操作都会围绕这个类展开。
类的初始化方法
def __init__(self):
self.nodes = []
self.edges = []
self.scene_width = 64000
self.scene_height = 64000
self.initUI()
self.nodes = [] 和 self.edges = []:分别创建了两个空列表,用于存储节点和边的对象。在后续的操作中,当创建新的节点或边时,就会将它们添加到对应的列表中,方便进行统一管理和遍历操作 。
self.scene_width = 64000 和 self.scene_height = 64000:设置了图形场景的宽度和高度。这两个属性定义了整个节点编辑区域的大小范围,确定了节点和边可以放置的空间。
self.initUI():调用自定义的 initUI 方法,用于初始化与图形场景相关的设置,将逻辑上的 Scene 类与可视化的图形场景进行关联和配置。
初始化用户界面方法
def initUI(self):
self.grScene = QDMGraphicsScene(self)
self.grScene.setGrScene(self.scene_width, self.scene_height)
self.grScene = QDMGraphicsScene(self):创建了一个 QDMGraphicsScene 类的实例 grScene,并将当前的 Scene 类实例(self)作为参数传递进去。这样做是为了在 QDMGraphicsScene 类中能够获取到与当前逻辑场景相关的信息,实现逻辑与可视化的交互。例如,QDMGraphicsScene 类可能需要根据 Scene 类中的数据来决定如何绘制节点和边。
self.grScene.setGrScene(self.scene_width, self.scene_height):调用 QDMGraphicsScene 实例的 setGrScene 方法,将之前设置的场景宽度和高度传递进去。这个方法可能会根据传入的尺寸对图形场景进行初始化配置,比如设置场景的边界范围,以便正确显示节点和边。
节点和边的添加方法
def addNode(self, node):
self.nodes.append(node)
def addEdge(self, edge):
self.edges.append(edge)
addNode 方法:接收一个 node 参数,将传入的节点对象添加到 self.nodes 列表中。当在节点编辑器中创建新的节点时,就会调用这个方法将新节点纳入到场景的管理中,方便后续对节点进行查找、操作和存储等处理。
addEdge 方法:与 addNode 方法类似,接收一个 edge 参数,将传入的边对象添加到 self.edges 列表中。在节点之间建立连接关系时,新创建的边就会通过这个方法添加到场景中,实现对边的统一管理。
节点和边的删除方法
def removeNode(self, node):
self.nodes.remove(node)
def removeEdge(self, edge):
self.edges.remove(edge)
removeNode 方法:接收一个 node 参数,从 self.nodes 列表中移除指定的节点对象。当需要在节点编辑器中删除某个节点时,调用这个方法可以将该节点从场景的管理中移除,同时后续可能还会触发一些与该节点相关的清理操作,比如删除与该节点连接的边等。
removeEdge 方法:接收一个 edge 参数,从 self.edges 列表中移除指定的边对象。在断开节点之间的连接时,就会使用这个方法将对应的边从场景中删除,确保场景中的数据与实际的节点连接状态一致。
完整的node_scene文件:
# 从自定义模块node_graphics_scene中导入QDMGraphicsScene类。
# QDMGraphicsScene类通常用于处理图形场景的可视化相关操作,
# 例如绘制场景背景、管理场景中图形元素的显示等,为后续将逻辑数据可视化做准备
from node_graphics_scene import QDMGraphicsScene
# 定义Scene类,该类用于管理节点编辑器中的节点和边的逻辑数据,
# 以及与图形场景进行交互,是整个节点编辑系统数据管理和协调的核心部分
class Scene:
# 类的初始化方法,在创建Scene类的实例时自动调用,用于初始化对象的属性
def __init__(self):
# 创建一个空列表,用于存储节点对象。后续创建的节点都将添加到这个列表中,
# 方便对节点进行统一管理,如遍历、查找、操作等
self.nodes = []
# 创建一个空列表,用于存储边对象。与节点列表类似,
# 所有创建的边都会存放在这里,便于管理边的相关操作
self.edges = []
# 设置图形场景的宽度为64000,这个值定义了节点编辑区域在水平方向上的大小范围
self.scene_width = 64000
# 设置图形场景的高度为64000,确定了节点编辑区域在垂直方向上的大小范围
self.scene_height = 64000
# 调用initUI方法,用于初始化与图形场景相关的设置,
# 建立逻辑场景与可视化图形场景之间的联系
self.initUI()
# 初始化用户界面的方法,主要负责创建图形场景实例并进行相关配置
def initUI(self):
# 创建QDMGraphicsScene类的实例grScene,并将当前Scene类的实例self作为参数传入。
# 这样做可以使QDMGraphicsScene类获取到与当前逻辑场景相关的信息,
# 实现逻辑场景与可视化图形场景之间的交互
self.grScene = QDMGraphicsScene(self)
# 调用grScene的setGrScene方法,将之前设置的场景宽度和高度传入。
# 该方法会根据传入的尺寸对图形场景进行初始化配置,
# 比如设置场景的边界范围,以确保节点和边能在正确的区域内显示
self.grScene.setGrScene(self.scene_width, self.scene_height)
# 向场景中添加节点的方法,接收一个node参数,代表要添加的节点对象
def addNode(self, node):
# 将传入的节点对象添加到self.nodes列表中,
# 从而将新节点纳入到Scene类的管理体系中
self.nodes.append(node)
# 向场景中添加边的方法,接收一个edge参数,即要添加的边对象
def addEdge(self, edge):
# 将传入的边对象添加到self.edges列表中,
# 实现对新创建边的统一管理
self.edges.append(edge)
# 从场景中移除节点的方法,接收一个node参数,指定要移除的节点对象
def removeNode(self, node):
# 从self.nodes列表中移除指定的节点对象,
# 在实际应用中,删除节点可能还会引发与该节点相关的其他清理操作,
# 以保证数据的一致性和准确性
self.nodes.remove(node)
# 从场景中移除边的方法,接收一个edge参数,用于指定要移除的边对象
def removeEdge(self, edge):
# 从self.edges列表中移除指定的边对象,
# 确保场景中节点之间的连接关系与实际操作保持一致
self.edges.remove(edge)
在node_graphics_scene.py文件中:
初始化原来函数:
class QDMGraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
更改为:
class QDMGraphicsScene(QGraphicsScene):
def __init__(self, scene, parent=None):
super().__init__(parent)
self.scene = scene
并且增加一个函数:
def setGrScene(self, width, height):
self.setSceneRect(-width // 2, -height // 2, width, height)
并且删除原来代码中:
self.scene_width, self.scene_height = 64000, 64000
self.setSceneRect(-self.scene_width//2, -self.scene_height//2, self.scene_width, self.scene_height)
在窗口的node_editor_wnd.py文件中:
首先调用
from node_scene import Scene
将原来的代码中的:
self.grScene = QDMGraphicsScene()
更改为:
self.scene = Scene()
self.grScene = self.scene.grScene
通过以上的步骤:实现了Scene函数的分离。
节点类
创建一个node_graphics_node.py
# 从PyQt5的QtWidgets模块导入所有的类和函数,该模块包含各种用于创建用户界面的组件,如按钮、布局、视图等
from PyQt5.QtWidgets import *
# 从PyQt5的QtCore模块导入所有的类和函数,此模块提供了核心功能,包括信号与槽机制、事件循环、定时器等
from PyQt5.QtCore import *
# 从PyQt5的QtGui模块导入所有的类和函数,该模块用于处理图形相关操作,如画笔、画刷、字体、绘图路径等
from PyQt5.QtGui import *
# 定义QDMGraphicsNode类,继承自QGraphicsItem,用于在图形场景中绘制和管理节点的可视化表示
class QDMGraphicsNode(QGraphicsItem):
# 类的初始化方法,接收关联的逻辑节点对象node、节点标题title(默认值为'Node Graphics Item')和父对象parent(默认值为None)
def __init__(self, node, title='Node Graphics Item', parent=None):
# 调用父类QGraphicsItem的初始化方法,确保父类的属性和行为得到正确初始化
super().__init__(parent)
# 设置节点标题的颜色为白色,使用Qt预定义的颜色常量
self._title_color = Qt.white
# 创建一个字体对象,字体为Ubuntu,字号为10,用于显示节点标题
self._title_font = QFont("Ubuntu", 10)
# 定义节点的宽度为180个单位
self.width = 180
# 定义节点的高度为240个单位
self.height = 240
# 定义节点边角圆弧的大小以及一些装饰元素的尺寸为10.0个单位
self.edge_size = 10.0
# 定义节点标题区域的高度为24.0个单位
self.title_height = 24.0
# 定义节点内部元素与边框之间的内边距为4.0个单位
self._padding = 4.0
# 创建默认状态下的画笔对象,颜色为半透明的黑色(rgba: 7F000000),用于绘制节点边框
self._pen_default = QPen(QColor("#7F000000"))
# 创建节点被选中时的画笔对象,颜色为黄色(rgba: FFFFA637),用于突出显示选中的节点
self._pen_selected = QPen(QColor("#FFFFA637"))
# 创建用于填充节点标题区域的画刷对象,颜色为深灰色(rgba: FF313131)
self._brush_title = QBrush(QColor("#FF313131"))
# 创建用于填充节点内容区域的画刷对象,颜色为稍浅的灰色(rgba: E3212121)
self._brush_background = QBrush(QColor("#E3212121"))
# 调用initTitle方法,初始化节点标题相关的图形项
self.initTitle()
# 设置节点的标题为传入的参数值
self.title = title
# 调用initUI方法,初始化节点的用户界面相关属性和设置
self.initUI()
# 重写QGraphicsItem的boundingRect方法,定义节点图形项的边界矩形
# 该矩形用于确定节点的绘制范围以及碰撞检测等操作
def boundingRect(self):
return QRectF(
0,
0,
2 * self.edge_size + self.width,
2 * self.edge_size + self.height
).normalized()
# 初始化用户界面的方法,设置节点的一些交互属性
def initUI(self):
# 设置节点为可选择状态,用户可以通过鼠标点击选中节点
self.setFlag(QGraphicsItem.ItemIsSelectable)
# 设置节点为可移动状态,用户可以通过鼠标拖动节点改变其位置
self.setFlag(QGraphicsItem.ItemIsMovable)
# 初始化节点标题的方法,创建并配置用于显示标题的文本图形项
def initTitle(self):
# 创建一个QGraphicsTextItem对象,用于显示节点标题,将其添加为当前节点图形项的子项
self.title_item = QGraphicsTextItem(self)
# 设置标题文本的默认颜色为之前定义的_title_color(白色)
self.title_item.setDefaultTextColor(self._title_color)
# 设置标题文本的字体为之前定义的_title_font(Ubuntu, 10)
self.title_item.setFont(self._title_font)
# 设置标题文本在节点内的位置,x坐标为内边距,y坐标为0(位于节点顶部)
self.title_item.setPos(self._padding, 0)
# 设置标题文本的最大宽度,为节点宽度减去两倍的内边距,确保文本不会超出节点范围
self.title_item.setTextWidth(
self.width
- 2 * self._padding
)
# 使用@property装饰器创建title属性的 getter 方法,用于获取节点的标题
@property
def title(self): return self._title
# 使用@title.setter装饰器创建title属性的 setter 方法,用于设置节点的标题
# 当标题被设置时,更新显示标题的文本图形项的内容
@title.setter
def title(self, value):
self._title = value
self.title_item.setPlainText(self._title)
# 重写QGraphicsItem的paint方法,负责绘制节点的外观
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
# 绘制节点标题区域
path_title = QPainterPath()
# 设置路径的填充规则为Qt.WindingFill,用于处理复杂形状的填充
path_title.setFillRule(Qt.WindingFill)
# 添加一个圆角矩形到路径中,作为标题的主体部分
path_title.addRoundedRect(0, 0, self.width, self.title_height, self.edge_size, self.edge_size)
# 添加两个小矩形到路径中,用于绘制标题区域的边角装饰
path_title.addRect(0, self.title_height - self.edge_size, self.edge_size, self.edge_size)
path_title.addRect(self.width - self.edge_size, self.title_height - self.edge_size, self.edge_size, self.edge_size)
# 设置画笔为无,即不绘制边框
painter.setPen(Qt.NoPen)
# 设置画刷为_title_brush,用于填充标题区域
painter.setBrush(self._brush_title)
# 绘制简化后的路径,完成标题区域的绘制
painter.drawPath(path_title.simplified())
# 绘制节点内容区域
path_content = QPainterPath()
# 设置路径的填充规则为Qt.WindingFill
path_content.setFillRule(Qt.WindingFill)
# 添加一个圆角矩形到路径中,作为内容区域的主体部分
path_content.addRoundedRect(0, self.title_height, self.width, self.height - self.title_height, self.edge_size, self.edge_size)
# 添加两个小矩形到路径中,用于绘制内容区域的边角装饰
path_content.addRect(0, self.title_height, self.edge_size, self.edge_size)
path_content.addRect(self.width - self.edge_size, self.title_height, self.edge_size, self.edge_size)
# 设置画笔为无
painter.setPen(Qt.NoPen)
# 设置画刷为_brush_background,用于填充内容区域
painter.setBrush(self._brush_background)
# 绘制简化后的路径,完成内容区域的绘制
painter.drawPath(path_content.simplified())
# 绘制节点边框
path_outline = QPainterPath()
# 添加一个圆角矩形到路径中,作为节点的外边框
path_outline.addRoundedRect(0, 0, self.width, self.height, self.edge_size, self.edge_size)
# 根据节点是否被选中,设置不同的画笔
painter.setPen(self._pen_default if not self.isSelected() else self._pen_selected)
# 设置画刷为无,只绘制边框不填充
painter.setBrush(Qt.NoBrush)
# 绘制简化后的路径,完成边框的绘制
painter.drawPath(path_outline.simplified())
创建一个Node_node.py文件
# 从自定义模块node_graphics_node中导入QDMGraphicsNode类,
# QDMGraphicsNode类负责节点的可视化绘制,包括节点的外观、样式以及在图形场景中的显示,
# 为当前Node类提供可视化支持
from node_graphics_node import QDMGraphicsNode
# 定义Node类,用于表示节点编辑器中的节点,是节点逻辑数据的核心载体
class Node():
# 类的初始化方法,接收所属场景对象scene和节点标题title(默认值为"Undefined Node")
def __init__(self, scene, title="Undefined Node"):
# 将传入的场景对象赋值给实例属性self.scene,建立节点与场景的关联,
# 以便节点可以调用场景中的方法,如添加自身到场景中等
self.scene = scene
# 设置节点的标题为传入的参数值,用于标识节点的功能或类型
self.title = title
# 创建QDMGraphicsNode类的实例self.grNode,将当前Node实例(self)和节点标题传递进去。
# 这一步实现了节点逻辑数据(Node类)与可视化表示(QDMGraphicsNode类)的关联,
# 使得节点在逻辑层面的操作能够反映在图形界面上
self.grNode = QDMGraphicsNode(self, self.title)
# 调用场景对象的addNode方法,将当前节点实例添加到场景的节点管理列表中,
# 便于场景对节点进行统一管理,如遍历、查找等操作
self.scene.addNode(self)
# 调用场景的图形场景对象(self.scene.grScene)的addItem方法,
# 将节点的可视化图形项(self.grNode)添加到图形场景中,
# 这样节点就能够在可视化界面中显示出来
self.scene.grScene.addItem(self.grNode)
# 创建两个空列表,分别用于存储节点的输入端口和输出端口,
# 为后续实现节点之间的数据传输和连接关系管理做准备
self.inputs = []
self.outputs = []
在node_editor_wnd做如下修改:
导入Scene
from node_scene import Scene
# crate graphics scene
self.scene = Scene()
# self.grScene = self.scene.grScene
node = Node(self.scene, "My Awesome Node")
# create graphics view
self.view = QDMGraphicsView(self.scene.grScene, self)
同时关闭测试函数:
# self.addDebugContent()
此时的文件目录结构为:
运行主函数得到如下结果:
这样实现了第一个节点的制作。
节点风格化
创建一个node_content_widget.py文件
在节点编辑器的开发中,QDMNodeContentWidget类承担着展示节点内部内容的重要职责。这段代码通过继承QWidget类,创建了一个包含标签和文本编辑框的垂直布局组件,为节点提供了基本的内容显示功能。下面我们从代码设计和功能实现的角度进行详细解析。
在 PyQtNode Editor 的开发中,节点内容的可视化展示是关键环节之一。QDMNodeContentWidget类作为节点内容的基础组件,负责构建节点内部的 UI 结构。下面我们对其实现代码进行详细分析。
模块导入
from PyQt5.QtWidgets import *
这行代码导入了 PyQt5 库中所有的窗口部件类。PyQt5 是一个强大的 Python GUI 框架,提供了丰富的 UI 组件和功能。QtWidgets模块包含了各种基础的 UI 元素,如按钮、标签、文本框、布局管理器等,是构建图形界面的核心模块。
2. 类定义与初始化
class QDMNodeContentWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
类定义:QDMNodeContentWidget继承自QWidget,表明它是一个自定义的窗口部件。
初始化方法:调用父类的构造函数初始化对象,并调用initUI方法进行 UI 的初始化。
3. UI 初始化方法
def initUI(self):
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0,0,0,0)
self.setLayout(self.layout)
self.wdg_label = QLabel("Some Title")
self.layout.addWidget(self.wdg_label)
self.layout.addWidget(QTextEdit("foo"))
这部分代码实现了 UI 组件的创建和布局设置:
创建布局管理器:使用QVBoxLayout创建一个垂直布局,将组件按从上到下的顺序排列。
设置边距:通过setContentsMargins(0,0,0,0)将布局的边距设置为 0,使组件能够充分利用空间。
添加标签组件:创建一个标签 (QLabel) 显示 “Some Title”,并将其添加到布局中。
添加文本编辑框:创建一个文本编辑框 (QTextEdit) 并初始化为 “foo”,同样添加到布局中。
node_content_widget.py完整的代码
# 从PyQt5的QtWidgets模块导入所有的类和函数。
# QtWidgets模块是PyQt5中用于创建图形用户界面(GUI)的重要模块,
# 包含了各种基础的UI组件,如窗口、按钮、标签、文本框、布局管理器等,
# 导入该模块后,代码可以使用这些组件来构建自定义的用户界面。
from PyQt5.QtWidgets import *
# 定义QDMNodeContentWidget类,它继承自QWidget。
# QWidget是PyQt5中所有用户界面对象的基类,提供了基本的窗口部件功能,
# 如显示、隐藏、大小调整等。QDMNodeContentWidget类用于创建节点内容的可视化部件,
# 可以在其基础上根据需求添加更多特定功能和界面元素。
class QDMNodeContentWidget(QWidget):
# 类的初始化方法,接收一个可选的parent参数,用于指定该部件的父部件,默认为None(表示是顶级部件)。
def __init__(self, parent=None):
# 调用父类QWidget的初始化方法,确保继承自父类的属性和行为能够正确初始化,
# 同时设置当前部件的父部件为传入的parent参数。
super().__init__(parent)
# 调用initUI方法,用于初始化该窗口部件的用户界面,即构建和设置界面上显示的各种组件及其布局。
self.initUI()
# 初始化用户界面的方法,用于创建和设置窗口部件中显示的组件以及布局。
def initUI(self):
# 创建一个垂直布局管理器QVBoxLayout的实例,并将其赋值给self.layout。
# 垂直布局管理器会将添加到其中的子部件按照从上到下的顺序依次排列。
self.layout = QVBoxLayout()
# 设置布局管理器的内容边距为0,即布局内的子部件会紧密排列,不留边界空白,
# 使界面布局更加紧凑,充分利用空间。
self.layout.setContentsMargins(0, 0, 0, 0)
# 将创建好的垂直布局管理器设置为当前窗口部件的布局,
# 这样后续添加到布局中的子部件就会在该窗口部件中按照垂直布局规则进行显示。
self.setLayout(self.layout)
# 创建一个标签部件QLabel的实例,显示文本为"Some Title",
# 并将其赋值给self.wdg_label,方便后续对该标签进行操作或访问。
self.wdg_label = QLabel("Some Title")
# 将创建好的标签部件添加到垂直布局管理器中,
# 此时标签会显示在窗口部件的上方(因为垂直布局是从上到下排列)。
self.layout.addWidget(self.wdg_label)
# 创建一个文本编辑框部件QTextEdit的实例,初始文本内容为"foo",
# 并将其添加到垂直布局管理器中,
# 由于垂直布局的特性,文本编辑框会显示在标签部件的下方。
self.layout.addWidget(QTextEdit("foo"))
在node_node.py文件夹下:
将原来的这行代码:
self.grNode = QDMGraphicsNode(self, self.title)
更改为:
self.content = QDMNodeContentWidget()
self.grNode = QDMGraphicsNode(self)
与此同时,记得导入相应的文件包:
from node_content_widget import QDMNodeContentWidget
node_graphics_node.py文件夹下:
将原来的类初始化方法:
def __init__(self, node, title='Node Graphics Item', parent=None):
更改为:
def __init__(self, node, parent=None):
super().__init__(parent)
self.node = node
self.content = self.node.content
将 self.title = title更改为: self.title = self.node.title
并增加以下代码:
self.initContent()
self.initUI()
@property
def title(self): return self._title
@title.setter
def title(self, value):
self._title = value
self.title_item.setPlainText(self._title)
def initContent(self):
self.grContent = QGraphicsProxyWidget(self)
self.content.setGeometry(self.edge_size, self.title_height + self.edge_size,
self.width - 2*self.edge_size, self.height - 2*self.edge_size-self.title_height)
self.grContent.setWidget(self.content)
在node_editor_wnd.py文件夹下:
类的初始化中增加以下代码:
self.stylesheet_filename = 'qss/nodestyle.qss'
self.loadStylesheet(self.stylesheet_filename)
def loadStylesheet(self, filename):
print('STYLE loading:', filename)
file = QFile(filename)
file.open(QFile.ReadOnly | QFile.Text)
stylesheet = file.readAll()
QApplication.instance().setStyleSheet(str(stylesheet, encoding='utf-8'))
最终运行主函数,
得到:
此时文件夹的目录如下:
由于此时node_graphics_node.py更改较多,附上最终的完整代码:
node_graphics_node.py
# 从PyQt5的QtWidgets模块导入所有的类和函数,该模块包含各种用于创建用户界面的组件,如按钮、布局、视图等
from PyQt5.QtWidgets import *
# 从PyQt5的QtCore模块导入所有的类和函数,此模块提供了核心功能,包括信号与槽机制、事件循环、定时器等
from PyQt5.QtCore import *
# 从PyQt5的QtGui模块导入所有的类和函数,该模块用于处理图形相关操作,如画笔、画刷、字体、绘图路径等
from PyQt5.QtGui import *
# 定义QDMGraphicsNode类,继承自QGraphicsItem,用于在图形场景中绘制和管理节点的可视化表示
class QDMGraphicsNode(QGraphicsItem):
# 类的初始化方法,接收关联的逻辑节点对象node、节点标题title(默认值为'Node Graphics Item')和父对象parent(默认值为None)
def __init__(self, node, parent=None):
# 调用父类QGraphicsItem的初始化方法,确保父类的属性和行为得到正确初始化
super().__init__(parent)
self.node = node
self.content = self.node.content
# 设置节点标题的颜色为白色,使用Qt预定义的颜色常量
self._title_color = Qt.white
# 创建一个字体对象,字体为Ubuntu,字号为10,用于显示节点标题
self._title_font = QFont("Ubuntu", 10)
# 定义节点的宽度为180个单位
self.width = 180
# 定义节点的高度为240个单位
self.height = 240
# 定义节点边角圆弧的大小以及一些装饰元素的尺寸为10.0个单位
self.edge_size = 10.0
# 定义节点标题区域的高度为24.0个单位
self.title_height = 24.0
# 定义节点内部元素与边框之间的内边距为4.0个单位
self._padding = 4.0
# 创建默认状态下的画笔对象,颜色为半透明的黑色(rgba: 7F000000),用于绘制节点边框
self._pen_default = QPen(QColor("#7F000000"))
# 创建节点被选中时的画笔对象,颜色为黄色(rgba: FFFFA637),用于突出显示选中的节点
self._pen_selected = QPen(QColor("#FFFFA637"))
# 创建用于填充节点标题区域的画刷对象,颜色为深灰色(rgba: FF313131)
self._brush_title = QBrush(QColor("#FF313131"))
# 创建用于填充节点内容区域的画刷对象,颜色为稍浅的灰色(rgba: E3212121)
self._brush_background = QBrush(QColor("#E3212121"))
# 调用initTitle方法,初始化节点标题相关的图形项
self.initTitle()
# 设置节点的标题为传入的参数值
self.title = self.node.title
self.initContent()
# 调用initUI方法,初始化节点的用户界面相关属性和设置
self.initUI()
@property
def title(self): return self._title
@title.setter
def title(self, value):
self._title = value
self.title_item.setPlainText(self._title)
# 重写QGraphicsItem的boundingRect方法,定义节点图形项的边界矩形
# 该矩形用于确定节点的绘制范围以及碰撞检测等操作
def boundingRect(self):
return QRectF(
0,
0,
2 * self.edge_size + self.width,
2 * self.edge_size + self.height
).normalized()
# 初始化用户界面的方法,设置节点的一些交互属性
def initUI(self):
# 设置节点为可选择状态,用户可以通过鼠标点击选中节点
self.setFlag(QGraphicsItem.ItemIsSelectable)
# 设置节点为可移动状态,用户可以通过鼠标拖动节点改变其位置
self.setFlag(QGraphicsItem.ItemIsMovable)
# 初始化节点标题的方法,创建并配置用于显示标题的文本图形项
def initTitle(self):
# 创建一个QGraphicsTextItem对象,用于显示节点标题,将其添加为当前节点图形项的子项
self.title_item = QGraphicsTextItem(self)
# 设置标题文本的默认颜色为之前定义的_title_color(白色)
self.title_item.setDefaultTextColor(self._title_color)
# 设置标题文本的字体为之前定义的_title_font(Ubuntu, 10)
self.title_item.setFont(self._title_font)
# 设置标题文本在节点内的位置,x坐标为内边距,y坐标为0(位于节点顶部)
self.title_item.setPos(self._padding, 0)
# 设置标题文本的最大宽度,为节点宽度减去两倍的内边距,确保文本不会超出节点范围
self.title_item.setTextWidth(
self.width
- 2 * self._padding
)
# 使用@property装饰器创建title属性的 getter 方法,用于获取节点的标题
# @property
# def title(self): return self._title
# # 使用@title.setter装饰器创建title属性的 setter 方法,用于设置节点的标题
# # 当标题被设置时,更新显示标题的文本图形项的内容
# @title.setter
# def title(self, value):
# self._title = value
# self.title_item.setPlainText(self._title)
def initContent(self):
# 创建一个QGraphicsProxyWidget对象,用于在QGraphicsScene(图形场景)中嵌入QWidget(普通窗口部件)。
# self表示将当前对象作为父对象传递,使得QGraphicsProxyWidget与当前对象关联,
# 这样在当前对象被管理或删除时,其关联的QGraphicsProxyWidget也能得到相应处理。
self.grContent = QGraphicsProxyWidget(self)
# 设置节点内容部件(self.content)的几何位置和大小。
# self.edge_size:表示节点边框圆角的大小,内容部件距离节点左边界的距离为edge_size,
# 确保内容不会绘制到圆角区域内。
# self.title_height + self.edge_size:内容部件距离节点上边界的距离,
# 即标题区域的高度加上圆角大小,保证内容在标题下方。
# self.width - 2*self.edge_size:内容部件的宽度,节点总宽度减去两侧圆角的宽度,
# 使得内容宽度适配节点整体宽度。
# self.height - 2*self.edge_size - self.title_height:内容部件的高度,
# 节点总高度减去上下两侧圆角的宽度以及标题区域的高度,合理分配节点内空间。
self.content.setGeometry(int(self.edge_size), int(self.title_height + self.edge_size),
int(self.width - 2 * self.edge_size),
int(self.height - 2 * self.edge_size - self.title_height))
# 将实际的内容部件(self.content,通常是一个QWidget及其子类对象)
# 设置到QGraphicsProxyWidget中。这样,原本的普通窗口部件就能在图形场景中进行展示,
# 实现将GUI元素集成到图形化节点中的效果,使其成为节点可视化的一部分。
self.grContent.setWidget(self.content)
# 重写QGraphicsItem的paint方法,负责绘制节点的外观
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
# 绘制节点标题区域
path_title = QPainterPath()
# 设置路径的填充规则为Qt.WindingFill,用于处理复杂形状的填充
path_title.setFillRule(Qt.WindingFill)
# 添加一个圆角矩形到路径中,作为标题的主体部分
path_title.addRoundedRect(0, 0, self.width, self.title_height, self.edge_size, self.edge_size)
# 添加两个小矩形到路径中,用于绘制标题区域的边角装饰
path_title.addRect(0, self.title_height - self.edge_size, self.edge_size, self.edge_size)
path_title.addRect(self.width - self.edge_size, self.title_height - self.edge_size, self.edge_size, self.edge_size)
# 设置画笔为无,即不绘制边框
painter.setPen(Qt.NoPen)
# 设置画刷为_title_brush,用于填充标题区域
painter.setBrush(self._brush_title)
# 绘制简化后的路径,完成标题区域的绘制
painter.drawPath(path_title.simplified())
# 绘制节点内容区域
path_content = QPainterPath()
# 设置路径的填充规则为Qt.WindingFill
path_content.setFillRule(Qt.WindingFill)
# 添加一个圆角矩形到路径中,作为内容区域的主体部分
path_content.addRoundedRect(0, self.title_height, self.width, self.height - self.title_height, self.edge_size, self.edge_size)
# 添加两个小矩形到路径中,用于绘制内容区域的边角装饰
path_content.addRect(0, self.title_height, self.edge_size, self.edge_size)
path_content.addRect(self.width - self.edge_size, self.title_height, self.edge_size, self.edge_size)
# 设置画笔为无
painter.setPen(Qt.NoPen)
# 设置画刷为_brush_background,用于填充内容区域
painter.setBrush(self._brush_background)
# 绘制简化后的路径,完成内容区域的绘制
painter.drawPath(path_content.simplified())
# 绘制节点边框
path_outline = QPainterPath()
# 添加一个圆角矩形到路径中,作为节点的外边框
path_outline.addRoundedRect(0, 0, self.width, self.height, self.edge_size, self.edge_size)
# 根据节点是否被选中,设置不同的画笔
painter.setPen(self._pen_default if not self.isSelected() else self._pen_selected)
# 设置画刷为无,只绘制边框不填充
painter.setBrush(Qt.NoBrush)
# 绘制简化后的路径,完成边框的绘制
painter.drawPath(path_outline.simplified())
通过以上的操作步骤,已经可以实现自定义节点的创建了。下面将进行不同节点之间的通信开发。
暂无评论内容