)
前言先糾正標(biāo)題里一個容易誤導(dǎo)的說法這里的「可視化界面」指的是圖形用戶界面GUIGraphical User Interface也就是帶窗口、按鈕、輸入框的那種桌面程序不是數(shù)據(jù)可視化把數(shù)據(jù)畫成圖表。兩者中文里都叫「可視化」但技術(shù)棧完全不同。本文講的是前者。另外要說明PyCharm 在這套組合里只是編輯器它不參與界面渲染。用 VS Code、IDLE 甚至記事本寫 PyQt 程序都沒問題。PyCharm 的價值在于能方便地指定項目解釋器、能一步運行腳本、能調(diào)試信號回調(diào)。本文會順帶說一句工程配置但重點在 PyQt 本身。還有一件事必須先講清楚否則可能涉及法律問題PyQt 和 PySide 的許可證不同。PyQt 由 Riverbank 開發(fā)采用 GPL v3 加商業(yè)授權(quán)雙許可官方文檔明確寫著 PyQt6「不像大多數(shù) Qt 組件那樣提供 LGPL」。這意味著如果你用 GPL 版的 PyQt 發(fā)布閉源商業(yè)軟件會違反許可。而 PySide 是 Qt 官方的 Python 綁定采用 LGPL閉源項目更友好。選型時這一點比 API 差異重要得多。一、選型三個包怎么選包名提供方許可證安裝備注PyQt5RiverbankGPL v3 / 商業(yè)pip install PyQt5老項目多exec_()寫法PyQt6RiverbankGPL v3 / 商業(yè)pip install PyQt6當(dāng)前主推要求 Python 3.9PySide6Qt 官方LGPL / GPLpip install PySide6API 與 PyQt 高度相似需要留意的是PyQt6 要求 Python 3.9 或以上官方文檔原文是「PyQt6 requires Python v3.9 or later」。三者 API 大體同源很多代碼改改 import 就能互轉(zhuǎn)但版本間的寫法差異尤其是執(zhí)行方法名和枚舉必須區(qū)分清楚后面單獨講。二、最小可運行窗口與常用控件PyQt 程序的骨架永遠是四步創(chuàng)建QApplication、創(chuàng)建窗口、show()、進入事件循環(huán)。下面是 PyQt6 的寫法# 適用于 Python 3.9需先安裝pip install PyQt6import sysfrom PyQt6.QtWidgets import (QApplication, QWidget, QLabel,QPushButton, QLineEdit, QVBoxLayout)class MainWindow(QWidget):def __init__(self):super().__init__()self.setWindowTitle(我的第一個界面)self.resize(320, 200)layout QVBoxLayout(self) # 豎向布局掛到窗口上self.input QLineEdit()self.input.setPlaceholderText(請輸入名字)self.button QPushButton(打招呼)self.output QLabel(等待輸入)layout.addWidget(self.input)layout.addWidget(self.button)layout.addWidget(self.output)# 信號與槽按鈕被點擊時調(diào)用 self.on_clickself.button.clicked.connect(self.on_click)def on_click(self):name self.input.text().strip()self.output.setText(f你好{name} if name else 請先輸入名字)app QApplication(sys.argv) # 每個程序有且只有一個 QApplicationwindow MainWindow()window.show() # 先 show 再進入事件循環(huán)sys.exit(app.exec()) # 啟動事件循環(huán)退出時把返回碼交還系統(tǒng)幾個必須理解的細節(jié)。第一QApplication是整個程序的入口一個程序只能有一個它負責(zé)管理事件循環(huán)和全局狀態(tài)。第二app.exec()會阻塞程序停在這一行處理鼠標(biāo)、鍵盤等各種事件直到窗口全部關(guān)閉才返回所以window.show()必須寫在它前面。第三sys.exit(app.exec())把 Qt 的退出碼原樣交給操作系統(tǒng)這是官方示例的標(biāo)準(zhǔn)寫法。第四布局Layout負責(zé)自動排布控件不要用絕對坐標(biāo)自己擺——窗口一拉伸布局就全亂了。Qt 的控件widget都從QWidget派生。常用控件包括控件類名用途標(biāo)簽QLabel顯示文字或圖片按鈕QPushButton點擊觸發(fā)動作單行輸入QLineEdit輸入一行文本多行文本QTextEdit輸入或顯示多行文本列表QListWidget列表項選擇下拉框QComboBox從下拉項里選布局管理器負責(zé)把控件擺到窗口里常用的有三種QVBoxLayout豎向排列。QHBoxLayout橫向排列。QGridLayout網(wǎng)格排列用addWidget(w, row, col)指定行列。布局可以嵌套外層用QVBoxLayout其中一行的按鈕放進一個QHBoxLayout再把這個橫向布局addLayout到外層。記住不要手動設(shè)控件坐標(biāo)——setGeometry這類絕對定位在窗口縮放時會立刻暴露問題。三、信號與槽界面的「神經(jīng)」Qt 最核心的機制是信號與槽signals and slots。官方文檔的定義是當(dāng)某件值得關(guān)注的事情發(fā)生時會發(fā)出一個信號signal槽slot是一個 Python 可調(diào)用對象信號連接connect到槽之后信號發(fā)出時槽就被調(diào)用。發(fā)信號的一方不需要知道誰在接收這就實現(xiàn)了界面與邏輯的解耦。# 適用于 Python 3.9需先安裝pip install PyQt6self.button.clicked.connect(self.on_click) # 點擊信號連到自定義方法self.input.textChanged.connect(self.on_change) # 文本變化時觸發(fā)self.input.returnPressed.connect(self.on_click) # 回車時觸發(fā)官方文檔列出的信號/槽特性里有幾條值得記住一個信號可以連接多個槽一個槽也可以被多個信號連接連接可以是同步的direct也可以是排隊的queued還可以跨線程連接。注意connect里傳的是函數(shù)對象本身不能加小括號——寫成self.on_click()會在連接時就把函數(shù)執(zhí)行一次。四、版本差異與工具配置如果你對照網(wǎng)上的老教程可能會發(fā)現(xiàn)代碼跑不起來。以下是官方列出的主要差異執(zhí)行方法名官方文檔明確「Allexec_()andprint_()methods have been removed」。所以 PyQt6 里寫app.exec()而 PyQt5 里是app.exec_()。枚舉的作用域PyQt6 里所有具名枚舉都變成標(biāo)準(zhǔn)庫Enum的子類使用時需要帶作用域例如對齊要寫Qt.AlignmentFlag.AlignCenterPyQt5 里可以直接寫Qt.AlignCenter。信號的定義方式舊式寫法與新版寫法不同自定義信號需要用pyqtSignal一類機制聲明具體以其官方文檔的信號章節(jié)為準(zhǔn)。所以遷移代碼時exec_改exec、枚舉加作用域是最常見的兩處。不確定某個枚舉屬于哪個類時查官方文檔而不是憑印象補。最后說工具側(cè)。PyCharm 只需要做兩件事。第一指定項目解釋器在項目設(shè)置里選擇你安裝了 PyQt6 的那個解釋器虛擬環(huán)境更干凈否則會出現(xiàn)「命令行能裝、PyCharm 里 import 不到」的經(jīng)典問題。第二配置運行目標(biāo)右鍵腳本選運行或新建一個運行配置指向你的入口文件。調(diào)試時可以在槽函數(shù)里打斷點信號觸發(fā)時會停在斷點上——這正是把界面邏輯拆進獨立方法的好處。常見坑點忘了創(chuàng)建QApplication。? 直接MainWindow().show()報錯說沒有 QApplication 實例。 ? 先app QApplication(sys.argv)且整個程序只創(chuàng)建一次。connect時給函數(shù)加了括號。?self.button.clicked.connect(self.on_click())點擊沒反應(yīng)函數(shù)在綁定那一刻就被調(diào)用了一次。 ? 傳函數(shù)對象connect(self.on_click)。show()寫在exec()后面。? 先app.exec()再window.show()程序卡在事件循環(huán)里窗口不出現(xiàn)。 ?window.show()必須在進入事件循環(huán)之前。照抄 PyQt5 的exec_()。? 在 PyQt6 里寫app.exec_()提示方法不存在。 ? PyQt6 已移除exec_()改用app.exec()。枚舉不寫作用域。? PyQt6 里沿用Qt.AlignCenter報枚舉找不到。 ? PyQt6 的枚舉是Enum子類需要Qt.AlignmentFlag.AlignCenter這樣的限定名。用絕對坐標(biāo)擺控件。? 用setGeometry一個個定位置窗口一縮放界面就錯位。 ? 用QVBoxLayout/QHBoxLayout/QGridLayout自動布局需要伸縮時配合addStretch。在子線程里直接改界面。? 耗時任務(wù)放主線程界面「未響應(yīng)」或從子線程直接調(diào)用控件方法。 ? 耗時任務(wù)放子線程通過信號把結(jié)果發(fā)回主線程更新界面信號可以跨線程連接。忽略許可證差異。? 用 GPL 版 PyQt 發(fā)布閉源商業(yè)軟件。 ? 先確認 PyQt 是 GPL/商業(yè)雙許可、PySide 是 LGPL閉源項目通常優(yōu)先考慮 PySide6具體條款以官方文檔為準(zhǔn)。總結(jié)環(huán)節(jié)關(guān)鍵 API要點程序入口QApplication(sys.argv)全程序唯一窗口QWidget子類 show()show 要在 exec 之前事件循環(huán)app.exec()會阻塞PyQt6 無下劃線布局QVBoxLayout/QGridLayout不要絕對定位交互signal.connect(slot)傳函數(shù)對象不加括號版本差異枚舉作用域PyQt6 枚舉需限定名許可證GPL / LGPL閉源選型前先確認寫 PyQt 界面的心智模型其實很清晰界面是控件樹交互是信號與槽程序由事件循環(huán)驅(qū)動。把這三點想明白剩下的就是查控件文檔。最后提醒一句上面的代碼都是按官方文檔的接口寫的但你本機環(huán)境里的 PyQt 版本、Python 版本可能不同跑之前請先確認版本遇到 API 差異以 PyQt 官方文檔為準(zhǔn)。