PyQt5入門教程
2019/12/11更新:我平時不看CSDN的,之前一時興起發(fā)了過來,沒想到反響還不錯。這次就順便把后來新增的一個小節(jié)放上來,并且在文末增加了我的GitHub(一看GitHub就知道我是個菜雞椅棺,大家都是互相學(xué)習(xí)啦~)
注:這是當(dāng)時閑著無聊寫到github page的,在CSDN上也看了大佬們各種各樣的教程跟疑難雜癥解答齐蔽,感覺我這個不放出來也有點可惜两疚,希望各位能夠從中收益吧。
在網(wǎng)上看了不少關(guān)于PyQt5的中文教程含滴,但是無外乎是過時了诱渤,講解不清晰易懂,或者資料不完整谈况。Youtube上面倒是有不少視頻勺美,但是不少Youtuber居然還在手寫ui而不是利用方便快捷的Qt Designer。僅有的幾個視頻雖然利用了Qt Designer來設(shè)計UI碑韵,但是他們并沒有將UI跟邏輯分離赡茸,這種行為并不是我期望的。
為此祝闻,我花費了不少時間在網(wǎng)上尋找各種資料占卧。于是乎,我最終還是下定決心把自己的學(xué)習(xí)過程給記錄下來联喘。記錄下來是給我自己復(fù)習(xí)跟參考的华蜒,如果有人能夠從中受益,那也挺好豁遭,不用浪費時間去到處找答案叭喜。
0x00 安裝環(huán)境清單
我使用的環(huán)境如下:
Windows 10 (Build 17763)
Python 3.7.2
VSCode 1.33.0
PyQt5
Qt Designer
如果你使用的是OSX或者Linux,請自行替換教程中的一些操作蓖谢。
本文并不討論Python和VSCode的安裝捂蕴,如果沒有VSCode譬涡,你可以用各種同類IDE替代或者安裝它。
本文不討論多Python共存启绰,畢竟Python2.7在2020年就要退役了昂儒,而且我本人也沒這需求。
0x01 安裝PyQt5
下面直接使用pip來安裝PyQt5委可,此處可能是pip/pip3,或者兩者皆可腊嗡,后面不再重復(fù)
直接pip安裝PyQt5
pip install PyQt51
由于Qt Designer已經(jīng)在Python3.5版本從PyQt5轉(zhuǎn)移到了tools着倾,因此我們還需要安裝pyqt5-tools
pip install pyqt5-tools1
到這一步儡毕,PyQt5就安裝完成了绢陌,你可以通過下面若干可選的操作來檢查是否已經(jīng)安裝成功:
Win+S呼出Cornata主面板(搜索框),輸入designer贾富,如果看到跟下圖類似的結(jié)果說明PyQt Designer已經(jīng)被安裝
在cmd中輸入pyuic5客们,如果返回“Error: one input ui-file must be specified”說明安裝成功崇决。
0x02 初識Qt Designer
注:Qt Designer的界面是全英文的,幸運的是有漢化方法底挫,不過因為我本人用不上恒傻,所以如果有這方面需求可以自行搜索。
我比較習(xí)慣用Win+S呼出Cornata主面板(搜索框)來啟動各種應(yīng)用建邓,那么這里就是在搜索框中輸入designer并敲回車盈厘,就能夠啟動Qt Designer了。
初次啟動會彈出這個“New Form”窗口官边,一般來說選擇“Main Window”然后點擊“Create”就可以了沸手。下方有個“Show this Dialogue on Startup”的checkbox,如果不想每次啟動都看到這個“New Form”窗口注簿,可以取消勾選契吉。
創(chuàng)建“Main Window”之后,我們會看到如下畫面
下面就來簡單介紹下整個畫面的構(gòu)成:
左側(cè)的“Widget Box”就是各種可以自由拖動的組件
中間的“MainWindow - untitled”窗體就是畫布
右上方的"Object Inspector"可以查看當(dāng)前ui的結(jié)構(gòu)
右側(cè)中部的"Property Editor"可以設(shè)置當(dāng)前選中組件的屬性
右下方的"Resource Browser"可以添加各種素材诡渴,比如圖片捐晶,背景等等,目前可以不管
大致了解了每個板塊之后玩徊,就可以正式開始編寫第一個UI了
0x03 HelloWorld!
注:從這里開始租悄,相關(guān)代碼可以在/assets/code/pyqt5中找到
注:本文用到的代碼都在我github,就不在CSDN這里上傳了
通常來說恩袱,編寫GUI有兩種方法:第一種就是直接使用方便快捷的Qt Designer泣棋,第二種就是寫代碼。在有Qt Designer的情況下畔塔,是完全不推薦費時費力去手寫GUI代碼的潭辈。Qt Designer可以所見即所得鸯屿,并且可以方便的修改并做出各種調(diào)整。
按照慣例把敢,我們先來實現(xiàn)一個能夠顯示HelloWorld的窗口寄摆。
1)添加文本
在左側(cè)的“Widget Box”欄目中找到“Display Widgets”分類,將“Label”拖拽到屏幕中間的“MainWindow”畫布上修赞,你就獲得了一個僅用于顯示文字的文本框婶恼,如下圖所示。
2)編輯文本
雙擊上圖中的“TextLabel”柏副,就可以對文本進行編輯勾邦,這里我們將其改成“HelloWorld!”,如下圖所示割择。如果文字沒有完全展示出來眷篇,可以自行拖拽空間改變尺寸。
特別提醒荔泳,編輯完文本之后記得敲擊回車令其生效蕉饼!
3)添加按鈕
使用同樣的方法添加一個按鈕(PushButton)并將其顯示的文本改成“HelloWorld!”,如下圖所示玛歌。
4)修改窗口標(biāo)題
下面修改窗口標(biāo)題昧港。選中右上方的"Object Inspector"中的“MainWindow”,然后在右側(cè)中部的"Property Editor"中找到“windowTitle”這個屬性沾鳄,在Value這一欄進行修改慨飘,修改完記得敲擊回車。
5)編輯菜單欄
注意到畫布的左上方有個“Type Here”译荞,雙擊它即可開始編輯菜單欄瓤的。菜單欄支持創(chuàng)建多級菜單以及分割線(separator)。我隨意創(chuàng)建了一些菜單項目吞歼,如下圖所示圈膏。
6)預(yù)覽
使用快捷鍵Ctrl+R預(yù)覽當(dāng)前編寫的GUI(或者從菜單欄的Form > Preview / Preview in進入)
7)保存
如果覺得完成了,那就可以保存成*.ui的文件篙骡,這里我們保存為HelloWorld.ui稽坤。為了方便演示,我將文件保存到D盤糯俗。
8)生成Python代碼
使用cmd將目錄切到D盤并執(zhí)行下面的命令尿褪。請自行將下面命令中的name替換成文件名,比如本例中的“HelloWorld.ui”
pyuic5 -o name.py name.ui1
生成的代碼應(yīng)該類似下圖所示
9)運行Python代碼
此時嘗試運行剛剛生成的“HelloWorld.py”是沒用的得湘,因為生成的文件并沒有程序入口杖玲。因此我們在同一個目錄下另外創(chuàng)建一個程序叫做“main.py”,并輸入如下內(nèi)容淘正。在本例中摆马,gui_file_name就是HelloWorld臼闻,請自行替換。
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
import gui_file_name
if __name__ == '__main__':
? ? app = QApplication(sys.argv)
? ? MainWindow = QMainWindow()
? ? ui = gui_file_name.Ui_MainWindow()
? ? ui.setupUi(MainWindow)
? ? MainWindow.show()
? ? sys.exit(app.exec_())123456789101112
然后運行“main.py”囤采,你就能看到剛剛編寫的GUI了述呐!
10)組件自適應(yīng)
如果你剛剛嘗試去縮放窗口,會發(fā)現(xiàn)組件并不會自適應(yīng)縮放蕉毯,因此我們需要回到Qt Designer中進行一些額外的設(shè)置乓搬。
點擊畫布空白處,然后在上方工具欄找到grid layout或者form layout恕刘,在本例中我們使用grid layout缤谎。兩種layout的圖標(biāo)如下圖所示。
順帶一提褐着,上圖中l(wèi)ayout的左邊有三條橫線以及三條豎線的圖標(biāo),這兩個是用于對齊組件托呕,非常實用含蓉。
設(shè)置grid layout后,我們使用Ctrl+R預(yù)覽项郊,這次組件可以自適應(yīng)了馅扣!因為我們已經(jīng)將UI(HelloWorld.py/HelloWorld.ui)跟邏輯(main.py)分離,因此直接重復(fù)步驟7-8即可完成UI的更新着降,無需改動邏輯(main.py)部分差油。
0x04 Interaction
剛剛寫的HelloWorld中,我們設(shè)置的按鈕(PushButton)是沒有實際作用的任洞,因為我們并沒有告訴這個按鈕應(yīng)該做什么蓄喇。實際上,要讓這個按鈕做點什么只需要增加一行代碼就可以了交掏。
1)獲取按鈕id
打開HelloWorld.ui妆偏,在designer中選中對應(yīng)的按鈕,從“Property Editor”中可以得知這個按鈕的“objectName”叫做“pushButton”盅弛,如下圖所示钱骂。
2)設(shè)置觸發(fā)
Qt中有“信號和槽(signal and slot)”這個概念,不過目前無需深究挪鹏,也無需在Designer中去設(shè)置對應(yīng)按鈕的“信號和槽”见秽,直接在“main.py”中“MainWindow.show()”的后面加入下面這樣的一行代碼
ui.pushButton.clicked.connect(click_success)1
下面簡單解釋下這行代碼
pushButton就是剛剛獲取的按鈕id
clicked就是信號,因為是點擊讨盒,所以我們這里用clicked
click_success就是對應(yīng)要調(diào)用的槽解取,注意這里函數(shù)并不寫成click_success()
3)設(shè)置函數(shù)
既然剛剛設(shè)置了按鈕的觸發(fā)并綁定了一個函數(shù)click_success,我們就要在“main.py”中實現(xiàn)它催植。示例如下
def click_success():
? ? print("啊哈哈哈我終于成功了肮蛹!")12
4)運行勺择!
UI跟邏輯分離的好處就在這里,我們這次不用去管“HelloWorld.py”了伦忠,直接運行修改完的“main.py”省核。點擊按鈕,這次你會發(fā)現(xiàn)在控制臺中有了我們預(yù)設(shè)的輸出昆码。
0x05 Conversion
這次我們來進行實戰(zhàn)演練气忠,編寫一個帶GUI的匯率轉(zhuǎn)換器。
1)設(shè)計UI
通過上面的講解赋咽,你應(yīng)該能夠毫無壓力的設(shè)計上面這樣的UI并獲得對應(yīng)的代碼旧噪。如果不行,那么不建議繼續(xù)往下閱讀脓匿,應(yīng)當(dāng)回頭復(fù)習(xí)淘钟。
2)傳參
現(xiàn)在我們有了GUI的代碼以及上一節(jié)中使用的“main.py”,我們可以開始編寫這個匯率轉(zhuǎn)換器的邏輯部分陪毡。
在上一節(jié)米母,我們介紹了如何讓按鈕響應(yīng)點擊操作,但是并沒有接受任何參數(shù)毡琉,而且只是在控制臺輸出铁瞒。但是,上一節(jié)中說明了并不能通過正常的方式進行傳參桅滋。因此慧耍,對于傳參,有兩種解決方案丐谋,一種是使用lambda芍碧,還有一種是使用functool.partial。在接下來的環(huán)節(jié)中我們會使用partial笋鄙。
partial的用法如下所示:
partial(function, arg1, arg2, ......)1
既然使用partial傳參师枣,那么我們就要在程序(main.py)的頭部加上下面這行。
from functools import partial1
然后我們把上一節(jié)中的按鈕觸發(fā)那行代碼修改成下面這樣萧落。
ui.pushButton.clicked.connect(partial(convert, ui))1
3)編寫convert函數(shù)
首先践美,我們要獲取用戶輸入的數(shù)字。為了使得教程簡潔易懂找岖,我們這次只講解單向的匯率轉(zhuǎn)換陨倡。既然是單項的轉(zhuǎn)換,那么我們只需要獲取左側(cè)的文本框id许布。在本例中兴革,左側(cè)的文本框id為lineEdit。如果你對此感到一頭霧水,請停下并回頭復(fù)習(xí)杂曲。
獲取文本使用的是text()方法庶艾,因此讀取用戶輸入的代碼如下
input = ui.lineEdit.text()1
接著我們進行匯率轉(zhuǎn)換,注意這里要進行類型轉(zhuǎn)換
result = float(input) * 6.71
最后我們讓右邊的文本框顯示結(jié)果
ui.lineEdit_2.setText(str(result))1
下面是convert函數(shù)的代碼
def convert(ui):
? ? input = ui.lineEdit.text()
? ? result = float(input) * 6.7
? ? ui.lineEdit_2.setText(str(result))1234
一個簡單的匯率轉(zhuǎn)換器就這樣誕生了擎勘!
那么咱揍,如何知道一個組件都有什么方法呢?直接去Qt官方文檔查看就可以了棚饵。本節(jié)使用到的lineEdit的相關(guān)方法在這里
0x06 threading
1)前言
這幾天在用PyQt5寫東西的時候遇到這樣一個問題煤裙,網(wǎng)上資料也特別少,我感覺值得拿出來說一說噪漾。
我的程序中使用了threading模塊硼砰,GUI作為主線程去啟動負責(zé)邏輯處理的子線程。其中欣硼,我設(shè)計的GUI里頭有一個日志框题翰,用來代替終端顯示各種日志輸出。既然子線程是負責(zé)邏輯處理诈胜,那么想當(dāng)然的就會直接在子線程操作GUI的顯示遍愿。
都說了想當(dāng)然,那當(dāng)然不行咯耘斩,在子線程對GUI操作的時候,終端會出現(xiàn)下面這個錯誤桅咆,但是程序又不會馬上閃退括授。
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)12
更讓人摸不著頭腦的是,過一陣子閃退的時候岩饼,會出現(xiàn)下面這句話:
段錯誤荚虚,核心已轉(zhuǎn)儲1
這啥玩意兒?能說人話么籍茧?一番搜索之后版述,發(fā)現(xiàn)這個原來英語叫做“Segmentation fault (core dumped)”。
"Segmentation fault"用人話來說大概就是“你嘗試訪問你無法訪問的內(nèi)存”寞冯。
然后我把上面的報錯信息搜索了下渴析,發(fā)現(xiàn)之前有人在StackOverflow問過,但是答案牛頭不對馬嘴吮龄,不過倒是在評論區(qū)發(fā)現(xiàn)了大佬的留言俭茧。
It is likely that the asker was not actually directly using QTextCursor, but rather using GUI code from a thread that was not the GUI thread. Attempting this seems to result in this error arising from Qt-internal code, e.g. for QTextEdit.append()1
簡而言之,就是說雖然報錯顯示QTextCursor漓帚,但是實際上是在其它線程通過Qt內(nèi)部的方法間接調(diào)用了這個東西母债。
熱心大佬還留了個鏈接,我跟過去看了,收獲不少毡们。
It appears you're trying to access QtGui classes from a thread other than the main thread. Like in some other GUI toolkits (e.g. Java Swing), that's not allowed.
Although QObject is reentrant, the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.123
這個終于說到點子上了迅皇,一句話總結(jié)就是子線程不能調(diào)用主線程的QtGui類。
所以大佬給出的方案如下:
A solution is to use signals and slots for communication between the main thread (where the GUI objects live) and your secondary thread(s). Basically, you emit signals in one thread that get delivered to the QObjects via the other thread.1
大概翻譯下衙熔,就是說可以通過信號和槽來完成子線程跟GUI所在的主線程的通信登颓,就是通過在子線程釋放信號,傳遞到主線程的槽來完成青责。
可惜的是挺据,大佬并沒有給出示例代碼,那接下來就是動手實踐了脖隶。
2)實踐
首先我們在子線程的代碼中創(chuàng)建一個對象扁耐,并且繼承QObject(因為需要釋放信號)。
class UpdateLog(QObject):
? ? update_signal = pyqtSignal()
? ? def __init__(self):
? ? ? ? QObject.__init__(self)
? ? def update(self):
? ? ? ? self.update_signal.emit()12345678
update_signal = pyqtSignal()就是使用Signal類來創(chuàng)建一個自定義的信號产阱。
self.update_signal.emit()就是當(dāng)條件滿足的時候婉称,子線程可以調(diào)用UpdateLog類的update方法,就會發(fā)出信號构蹬。
做完這些之后王暗,主線程中別忘了連擊信號和槽,比如self.afk.utils.logger.update_signal.connect(self.write_log)庄敛。然后現(xiàn)在再嘗試運行程序俗壹,就沒有任何問題了。
不僅如此藻烤,其實其它需要共享的信息绷雏,也可以通過自定義信號和槽來傳遞。
那么怖亭,現(xiàn)在就可以愉快的在PyQt程序中使用threading模塊了涎显。
0x0? 小結(jié)
本文只是拋磚引玉,上面這些只是PyQt5的入門內(nèi)容兴猩。不過學(xué)會了簡單的交互方法期吓,其它的也差不多能依葫蘆畫瓢做出來。
本文中設(shè)計的程序在/assets/code/pyqt5中倾芝。
那么讨勤,就先寫到這里了!
完整文件下載地址: