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