PyQtNode Editor 以其独特的功能和灵活的扩展性,吸引了众多开发者的目光。
这篇博客作为系列开篇,将详细介绍开发 PyQtNode Editor 所需的基础环境、安装步骤,同时深入解读一段简单的 PyQt5 代码,为后续的开发工作奠定基础。
一、开发基础环境介绍
- Python
Python 是一种高级、解释型的编程语言,以其简洁、易读的语法和丰富的库而闻名。在 PyQtNode Editor 的开发中,Python 作为核心编程语言,负责实现各种逻辑和功能。它的动态类型系统和自动内存管理机制,让开发者能够更专注于业务逻辑的实现,提高开发效率。目前,Python 有 2.x 和 3.x 两个主要版本系列,在开发 PyQtNode Editor 时,推荐使用 Python 3.x 版本,因为它在性能、安全性和新特性上都有显著提升,并且得到了更广泛的支持。 - PyQt5
PyQt5 是 Python 编程语言和 Qt 库的成功融合,Qt 是一个跨平台的 C++ 图形用户界面应用程序框架,功能强大且稳定。而 PyQt5 通过提供 Python 绑定,让开发者能够使用 Python 语言轻松创建具有丰富用户界面的应用程序。它包含了大量的模块,涵盖了图形绘制、事件处理、网络通信等多个方面,为开发 PyQtNode Editor 提供了全面的功能支持。无论是创建简单的窗口,还是复杂的交互式界面,PyQt5 都能满足需求。 - 开发工具
PyCharm 是一款专业的 Python 集成开发环境(IDE),它具有强大的代码编辑、调试、智能提示等功能,能够帮助开发者快速定位和解决问题。建议采用Pycharm.
二、基础环境安装步骤
安装python
安装 PyQt5
pip install PyQt5
三、验证安装环境简单的例子
现在我们来详细解读以下这段代码,它是使用 PyQt5 创建一个简单窗口程序的基础示例:
import sys
from PyQt5.QtWidgets import *if __name__ == '__main__':app = QApplication(sys.argv)label = QLabel("Hello, PyQt5!")label.show()sys.exit(app.exec_())
代码详细解释:
- 导入模块
import sys
from PyQt5.QtWidgets import *
import sys:导入 Python 的sys模块,该模块提供了对 Python 解释器相关变量和函数的访问。在这个程序中,sys.argv用于获取命令行参数,后续会将其传递给QApplication对象,以便应用程序能够接收外部传入的参数信息。
from PyQt5.QtWidgets import *:从PyQt5.QtWidgets模块中导入所有的类和函数。QtWidgets模块是 PyQt5 中用于创建用户界面元素(如窗口、按钮、标签等)的核心模块。通过这种导入方式,我们可以直接使用QtWidgets模块中的各种类,无需每次都指定完整的模块路径。
2. 主程序入口
if __name__ == '__main__':
这是 Python 程序的主入口判断语句。当直接运行这个 Python 脚本时,__name__变量的值会被设置为’main’,此时,该条件下的代码块会被执行;而当这个脚本作为模块被其他脚本导入时,__name__变量的值将是模块名,这段代码块就不会被执行。这种机制确保了主程序逻辑只在直接运行脚本时执行,避免了在被导入时不必要的代码执行。
3. 创建应用程序对象
app = QApplication(sys.argv)
QApplication类是 PyQt5 应用程序的核心,它管理着应用程序的控制流和主要设置,包括事件处理、应用程序的生命周期等。这里通过传入sys.argv创建了一个QApplication对象app,使得应用程序能够处理命令行传入的参数,例如设置应用程序的名称、图标等信息。在一个 PyQt5 应用程序中,必须且只能有一个QApplication对象。
4. 创建标签并显示
label = QLabel("Hello, PyQt5!")
label.show()
label = QLabel(“Hello, PyQt5!”):创建了一个QLabel对象label,QLabel是 PyQt5 中用于显示文本或图像的控件。这里通过传入字符串 “Hello, PyQt5!”,将该文本设置为标签的显示内容。
label.show():调用show()方法,将创建好的标签控件显示在屏幕上。如果不调用show()方法,控件将不会被绘制和显示。
5. 进入应用程序主循环并退出
sys.exit(app.exec_())
app.exec_():启动 PyQt5 应用程序的主循环。在主循环中,应用程序不断地检测和处理各种事件(如鼠标点击、键盘输入等),保持应用程序的运行状态,直到用户关闭所有窗口或显式地退出应用程序。
sys.exit():当app.exec_()返回时,表示应用程序的主循环结束,此时通过sys.exit()方法来安全地退出 Python 程序。传入app.exec_()的返回值作为sys.exit()的参数,通常app.exec_()返回 0 表示正常退出,其他值表示异常退出,这样可以在程序外部获取应用程序的退出状态,进行进一步的处理。
得到运算结果:
四、创建窗口以及画布、网格
在 PyQtNode Editor 的开发过程中,我们常常需要自定义图形场景。接下来,就为大家详细解释一段用于创建自定义图形场景的代码
创建一个node_graphics_scene.py文件:
4.1 导入必要的模块
import math
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
这部分代码就像是我们开工前准备的工具箱。import math 是引入了 Python 里专门用来处理数学计算的工具,后面计算网格位置的时候会用到;from PyQt5.QtWidgets import * 是把 PyQt5 里用来搭建各种窗口、按钮这些界面元素的工具全拿了过来;from PyQt5.QtCore import * 则是搬来了管理程序运行、处理事件等核心功能的工具;from PyQt5.QtGui import * 是把负责画画、处理字体这些图形相关的工具也准备好了。
4.2 定义自定义图形场景类
class QDMGraphicsScene(QGraphicsScene):
这里我们定义了一个新的 “东西”,叫做 QDMGraphicsScene 类,它是从 QGraphicsScene 这个已有的类 “继承” 过来的。打个比方,QGraphicsScene 是一辆基础款汽车,我们的 QDMGraphicsScene 就是在这辆基础款汽车上进行改装升级,让它更符合我们开发 PyQtNode Editor 的需求,成为一个自定义的图形场景。
类的初始化方法
def __init__(self, parent=None):super().__init__(parent)self.gridSize = 20self.gridSquares = 5self._color_background = QColor("#393939")self._color_light = QColor("#2f2f2f")self._color_dark = QColor("#292929")`在这里插入代码片`self._pen_light = QPen(self._color_light)self._pen_light.setWidth(1)self._pen_dark = QPen(self._color_dark)self._pen_dark.setWidth(2)self.scene_width, self.scene_height = 64000, 64000self.setSceneRect(-self.scene_width//2, -self.scene_height//2, self.scene_width, self.scene_height)self.setBackgroundBrush(self._color_background)
init 方法就像是这个自定义图形场景类的 “出生设置”。当我们创建这个类的一个 “实例”(可以理解为造一辆属于我们自己的车)时,这个方法就会自动运行。
super().init(parent) 是先让我们造的这辆车保留基础款汽车的所有功能和设置。
接下来,self.gridSize = 20 和 self.gridSquares = 5 是在设置场景里网格的样子,gridSize 是每个小方格的边长,gridSquares 决定了每几个小方格组成一个大方格,这就好比我们在规划一张图纸上小格子和大格子的大小。
self._color_background、self._color_light 和 self._color_dark 分别定义了场景的背景颜色、浅网格线颜色和深网格线颜色。self._pen_light 和 self._pen_dark 则是创建了两支 “画笔”,一支用来画浅网格线,一支画深网格线,还设置好了它们的线宽。
self.scene_width, self.scene_height = 64000, 64000 确定了整个图形场景的大小,就像给我们画画的纸规定了尺寸。self.setSceneRect 方法是根据这个尺寸,确定场景的边界范围。最后 self.setBackgroundBrush(self._color_background) 把场景的背景颜色设置成我们之前定义好的颜色。
4.3自定义背景绘制方法
def drawBackground(self, painter, rect):super().drawBackground(painter, rect)left = int(math.floor(rect.left()))right = int(math.ceil(rect.right()))top = int(math.floor(rect.top()))bottom = int(math.ceil(rect.bottom()))first_left = left - (left % self.gridSize)first_top = top - (top % self.gridSize)lines_light, lines_dark = [], []for x in range(first_left, right, self.gridSize):if (x % (self.gridSize*self.gridSquares) != 0): lines_light.append(QLine(x, top, x, bottom))else: lines_dark.append(QLine(x, top, x, bottom))for y in range(first_top, bottom, self.gridSize):if (y % (self.gridSize*self.gridSquares) != 0): lines_light.append(QLine(left, y, right, y))else: lines_dark.append(QLine(left, y, right, y))painter.setPen(self._pen_light)painter.drawLines(*lines_light)painter.setPen(self._pen_dark)painter.drawLines(*lines_dark)
drawBackground 方法是用来画画的,当程序需要绘制场景背景时,这个方法就会被调用。
super().drawBackground(painter, rect) 是先让程序按照原来的方式把背景画好。
后面的一系列计算,比如 left、right、top、bottom 以及 first_left、first_top ,是在确定我们要画网格线的区域边界和起始位置,就像我们在图纸上量好要画格子的范围。
两个 for 循环则是在确定每一条网格线的位置,判断哪些是浅网格线,哪些是深网格线,并把它们分别存到 lines_light 和 lines_dark 这两个列表里。
最后,painter.setPen(self._pen_light) 和 painter.drawLines(*lines_light) 是拿起画浅网格线的 “画笔”,把所有浅网格线画出来;painter.setPen(self._pen_dark) 和 painter.drawLines(*lines_dark) 是拿起画深网格线的 “画笔”,把深网格线也画好。这样,我们自定义的网格背景就绘制完成啦!
node_graphics_scene.py 文件的完整代码:
import math
# 从PyQt5的QtWidgets模块导入所有类和函数,该模块用于创建用户界面元素
from PyQt5.QtWidgets import *
# 从PyQt5的QtCore模块导入所有类和函数,该模块包含核心非GUI功能,如事件循环、定时器等
from PyQt5.QtCore import *
# 从PyQt5的QtGui模块导入所有类和函数,该模块用于处理图形相关功能,如画布绘制、字体等
from PyQt5.QtGui import *# 定义一个继承自QGraphicsScene的类,用于创建自定义的图形场景
class QDMGraphicsScene(QGraphicsScene):# 类的初始化方法,接收一个parent参数(默认为None),用于指定父对象def __init__(self, parent=None):# 调用父类的初始化方法,确保父类的属性和行为得到正确初始化super().__init__(parent)# 设置场景的一些属性# 网格的单个方格大小self.gridSize = 20# 每多少个小方格组成一个大方格(用于区分网格线的粗细)self.gridSquares = 5# 定义场景背景颜色self._color_background = QColor("#393939")# 定义较浅的网格线颜色self._color_light = QColor("#2f2f2f")# 定义较深的网格线颜色self._color_dark = QColor("#292929")# 创建较浅网格线的画笔对象,并设置线宽为1self._pen_light = QPen(self._color_light)self._pen_light.setWidth(1)# 创建较深网格线的画笔对象,并设置线宽为2self._pen_dark = QPen(self._color_dark)self._pen_dark.setWidth(2)# 设置场景的宽度和高度self.scene_width, self.scene_height = 64000, 64000# 设置场景的矩形区域,确定场景的边界范围self.setSceneRect(-self.scene_width//2, -self.scene_height//2, self.scene_width, self.scene_height)# 设置场景的背景画刷为之前定义的背景颜色self.setBackgroundBrush(self._color_background)# 重写父类的drawBackground方法,用于自定义背景绘制逻辑def drawBackground(self, painter, rect):# 先调用父类的drawBackground方法,绘制默认背景super().drawBackground(painter, rect)# 计算需要绘制网格线的区域边界,取整操作确保边界在网格线上left = int(math.floor(rect.left()))right = int(math.ceil(rect.right()))top = int(math.floor(rect.top()))bottom = int(math.ceil(rect.bottom()))# 计算从左边界和上边界开始,距离最近的网格线位置first_left = left - (left % self.gridSize)first_top = top - (top % self.gridSize)# 初始化存储较浅和较深网格线的列表lines_light, lines_dark = [], []# 遍历水平方向的网格线位置for x in range(first_left, right, self.gridSize):# 如果不是大方格的边界线,则添加到较浅网格线列表if (x % (self.gridSize*self.gridSquares) != 0): lines_light.append(QLine(x, top, x, bottom))# 否则添加到较深网格线列表else: lines_dark.append(QLine(x, top, x, bottom))# 遍历垂直方向的网格线位置for y in range(first_top, bottom, self.gridSize):# 如果不是大方格的边界线,则添加到较浅网格线列表if (y % (self.gridSize*self.gridSquares) != 0): lines_light.append(QLine(left, y, right, y))# 否则添加到较深网格线列表else: lines_dark.append(QLine(left, y, right, y))# 设置画笔为较浅网格线画笔painter.setPen(self._pen_light)# 绘制所有较浅的网格线painter.drawLines(*lines_light)# 设置画笔为较深网格线画笔painter.setPen(self._pen_dark)# 绘制所有较深的网格线painter.drawLines(*lines_dark)
创建一个node_editor_wnd.py
准备 “搭建材料”
from PyQt5.QtWidgets import *
from node_graphics_scene import QDMGraphicsScene
这两行代码就像我们盖房子前准备的建筑材料。from PyQt5.QtWidgets import * 是把 PyQt5 里用来搭建各种窗口、按钮、布局等界面元素的 “建筑材料” 全搬了过来。不管是窗户(窗口)、门(按钮),还是房间的布局,都能从这里找到对应的材料。
from node_graphics_scene import QDMGraphicsScene 则是把之前我们自定义的图形场景类 QDMGraphicsScene 拿过来。这个类就像是我们提前定制好的特殊地板,上面有自定义的网格,我们要把它铺到即将搭建的房子(窗口)里。
4.4 定义窗口类
class NodeEditorWnd(QWidget):
这里我们定义了一个新的类 NodeEditorWnd ,它是从 QWidget 这个类 “继承” 过来的。QWidget 就像是一个基础房屋模板,而 NodeEditorWnd 是在这个基础模板上进行改造,给它添加我们需要的功能,让它变成一个专门用来编辑节点的窗口,就好比把普通房子改造成工作室。
初始化窗口
def __init__(self, parent=None):super().__init__(parent)
self.initUI()
init 方法就像是窗口的 “出生设置” 函数,当我们用 NodeEditorWnd 这个模板创建一个新窗口时,这个方法就会自动运行。
super().init(parent) 是先让新窗口保留基础房屋模板(QWidget)的所有功能和设置,就像孩子继承父母的基本特征。
self.initUI() 这行代码是调用了另一个函数 initUI ,这个函数专门用来设置窗口的外观和内部布局,就像我们要给房子装修,设计房间布局、安装门窗一样。
设计窗口外观和布局
def initUI(self):self.setGeometry(200, 200, 800, 600)
self.layout = QVBoxLayout()self.layout.setContentsMargins(0, 0, 0, 0)self.setLayout(self.layout)
# crate graphics sceneself.grScene = QDMGraphicsScene()
# create graphics viewself.view = QGraphicsView(self)self.view.setScene(self.grScene)self.layout.addWidget(self.view)
self.setWindowTitle("Node Editor")self.show()
self.setGeometry(200, 200, 800, 600) 这行代码是在确定窗口在屏幕上的位置和大小。前两个数字 200, 200 表示窗口左上角距离屏幕左边和上边各 200 个单位,就像确定房子在小区里的具体位置;后两个数字 800, 600 表示窗口的宽度是 800 个单位,高度是 600 个单位,也就是确定房子的长和宽。
self.layout = QVBoxLayout() 创建了一个垂直布局管理器,它就像是房子里的空间规划师,会把里面的东西(界面组件)从上到下依次摆放。self.layout.setContentsMargins(0, 0, 0, 0) 是把布局管理器四周的空白区域设置为 0,这样界面组件就能紧紧挨在一起,不浪费空间。self.setLayout(self.layout) 是把这个布局管理器应用到我们的窗口上,就像把规划师请进房子,开始规划房间布局。
self.grScene = QDMGraphicsScene() 这行代码是创建了一个我们之前自定义的图形场景对象 grScene ,就好比把定制好的特殊地板拿出来,准备铺到房子里。
self.view = QGraphicsView(self) 创建了一个图形视图 view ,它就像是一个 “展示框”,用来展示我们的图形场景。self.view.setScene(self.grScene) 是把我们创建好的图形场景 grScene 放到这个展示框里,就像把地板铺到展示框里展示出来。self.layout.addWidget(self.view) 是把这个展示框添加到我们之前创建的垂直布局管理器里,这样它就能在窗口中占据合适的位置。
最后,self.setWindowTitle(“Node Editor”) 是给窗口设置一个标题,就像给房子挂上一个门牌,告诉别人这是 “Node Editor” 窗口。self.show() 是把窗口显示出来,如果没有这行代码,窗口就像藏起来的房子,我们是看不到的。
以下是完整的node_editor_wnd.py代码
# 从PyQt5的QtWidgets模块导入所有类和函数,用于创建GUI界面组件
from PyQt5.QtWidgets import *# 从自定义模块中导入之前定义的图形场景类,用于创建带有网格的自定义场景
from node_graphics_scene import QDMGraphicsScene# 定义NodeEditorWnd类,继承自QWidget,用于创建节点编辑器的主窗口
class NodeEditorWnd(QWidget):# 类的初始化方法,接收一个可选的父窗口参数def __init__(self, parent=None):# 调用父类QWidget的初始化方法super().__init__(parent)# 调用自定义的UI初始化方法self.initUI()# 初始化用户界面的方法def initUI(self):# 设置窗口在屏幕中的位置和大小(x, y, width, height)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 = QGraphicsView(self)# 将图形场景设置到视图中,使视图显示该场景内容self.view.setScene(self.grScene)# 将图形视图添加到垂直布局中,使其占据窗口的全部空间self.layout.addWidget(self.view)# 设置窗口的标题栏文本self.setWindowTitle("Node Editor")# 显示窗口,使其可见self.show()
4.5 窗口进行可视化
主函数代码:
import sys
from PyQt5.QtWidgets import *from node_editor_wnd import NodeEditorWnd
#从自定义模块node_editor_wnd中导入NodeEditorWnd类,这个类是我们之前定义的节点编辑器主窗口,相当于把做好的 “房子框架” 拿过来用。if __name__ == '__main__':app = QApplication(sys.argv)wnd = NodeEditorWnd() #创建NodeEditorWnd类的实例wnd,这相当于 “盖好” 一个节点编辑器窗口。NodeEditorWnd类之前已经定义过,里面包含了窗口的布局、图形视图等设置。sys.exit(app.exec_())
运行结果显示如下。