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 scene?self.grScene = QDMGraphicsScene()?
?# create graphics view?self.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_())
運行結果顯示如下。