PyQt5(designer)入門(mén)教程

PyQt5入門(mén)教程

2019/12/11更新:我平時(shí)不看CSDN的,之前一時(shí)興起發(fā)了過(guò)來(lái),沒(méi)想到反響還不錯(cuò)。這次就順便把后來(lái)新增的一個(gè)小節(jié)放上來(lái),并且在文末增加了我的GitHub(一看GitHub就知道我是個(gè)菜雞,大家都是互相學(xué)習(xí)啦~)

注:這是當(dāng)時(shí)閑著無(wú)聊寫(xiě)到github page的,在CSDN上也看了大佬們各種各樣的教程跟疑難雜癥解答,感覺(jué)我這個(gè)不放出來(lái)也有點(diǎn)可惜,希望各位能夠從中收益吧。

在網(wǎng)上看了不少關(guān)于PyQt5的中文教程,但是無(wú)外乎是過(guò)時(shí)了,講解不清晰易懂,或者資料不完整。Youtube上面倒是有不少視頻,但是不少Youtuber居然還在手寫(xiě)ui而不是利用方便快捷的Qt Designer。僅有的幾個(gè)視頻雖然利用了Qt Designer來(lái)設(shè)計(jì)UI,但是他們并沒(méi)有將UI跟邏輯分離,這種行為并不是我期望的。

為此,我花費(fèi)了不少時(shí)間在網(wǎng)上尋找各種資料。于是乎,我最終還是下定決心把自己的學(xué)習(xí)過(guò)程給記錄下來(lái)。記錄下來(lái)是給我自己復(fù)習(xí)跟參考的,如果有人能夠從中受益,那也挺好,不用浪費(fèi)時(shí)間去到處找答案。

0x00 安裝環(huán)境清單

我使用的環(huán)境如下:

Windows 10 (Build 17763)

Python 3.7.2

VSCode 1.33.0

PyQt5

Qt Designer

如果你使用的是OSX或者Linux,請(qǐng)自行替換教程中的一些操作。

本文并不討論P(yáng)ython和VSCode的安裝,如果沒(méi)有VSCode,你可以用各種同類IDE替代或者安裝它。

本文不討論多Python共存,畢竟Python2.7在2020年就要退役了,而且我本人也沒(méi)這需求。

0x01 安裝PyQt5

下面直接使用pip來(lái)安裝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就安裝完成了,你可以通過(guò)下面若干可選的操作來(lái)檢查是否已經(jīng)安裝成功:

Win+S呼出Cornata主面板(搜索框),輸入designer,如果看到跟下圖類似的結(jié)果說(shuō)明PyQt Designer已經(jīng)被安裝

在cmd中輸入pyuic5,如果返回“Error: one input ui-file must be specified”說(shuō)明安裝成功。

0x02 初識(shí)Qt Designer

注:Qt Designer的界面是全英文的,幸運(yùn)的是有漢化方法,不過(guò)因?yàn)槲冶救擞貌簧?,所以如果有這方面需求可以自行搜索。

我比較習(xí)慣用Win+S呼出Cornata主面板(搜索框)來(lái)啟動(dòng)各種應(yīng)用,那么這里就是在搜索框中輸入designer并敲回車,就能夠啟動(dòng)Qt Designer了。

初次啟動(dòng)會(huì)彈出這個(gè)“New Form”窗口,一般來(lái)說(shuō)選擇“Main Window”然后點(diǎn)擊“Create”就可以了。下方有個(gè)“Show this Dialogue on Startup”的checkbox,如果不想每次啟動(dòng)都看到這個(gè)“New Form”窗口,可以取消勾選。

創(chuàng)建“Main Window”之后,我們會(huì)看到如下畫(huà)面

下面就來(lái)簡(jiǎn)單介紹下整個(gè)畫(huà)面的構(gòu)成:

左側(cè)的“Widget Box”就是各種可以自由拖動(dòng)的組件

中間的“MainWindow - untitled”窗體就是畫(huà)布

右上方的"Object Inspector"可以查看當(dāng)前ui的結(jié)構(gòu)

右側(cè)中部的"Property Editor"可以設(shè)置當(dāng)前選中組件的屬性

右下方的"Resource Browser"可以添加各種素材,比如圖片,背景等等,目前可以不管

大致了解了每個(gè)板塊之后,就可以正式開(kāi)始編寫(xiě)第一個(gè)UI了

0x03 HelloWorld!

注:從這里開(kāi)始,相關(guān)代碼可以在/assets/code/pyqt5中找到

注:本文用到的代碼都在我github,就不在CSDN這里上傳了

通常來(lái)說(shuō),編寫(xiě)GUI有兩種方法:第一種就是直接使用方便快捷的Qt Designer,第二種就是寫(xiě)代碼。在有Qt Designer的情況下,是完全不推薦費(fèi)時(shí)費(fèi)力去手寫(xiě)GUI代碼的。Qt Designer可以所見(jiàn)即所得,并且可以方便的修改并做出各種調(diào)整。

按照慣例,我們先來(lái)實(shí)現(xiàn)一個(gè)能夠顯示HelloWorld的窗口。

1)添加文本

在左側(cè)的“Widget Box”欄目中找到“Display Widgets”分類,將“Label”拖拽到屏幕中間的“MainWindow”畫(huà)布上,你就獲得了一個(gè)僅用于顯示文字的文本框,如下圖所示。

2)編輯文本

雙擊上圖中的“TextLabel”,就可以對(duì)文本進(jìn)行編輯,這里我們將其改成“HelloWorld!”,如下圖所示。如果文字沒(méi)有完全展示出來(lái),可以自行拖拽空間改變尺寸。

特別提醒,編輯完文本之后記得敲擊回車令其生效!

3)添加按鈕

使用同樣的方法添加一個(gè)按鈕(PushButton)并將其顯示的文本改成“HelloWorld!”,如下圖所示。

4)修改窗口標(biāo)題

下面修改窗口標(biāo)題。選中右上方的"Object Inspector"中的“MainWindow”,然后在右側(cè)中部的"Property Editor"中找到“windowTitle”這個(gè)屬性,在Value這一欄進(jìn)行修改,修改完記得敲擊回車。

5)編輯菜單欄

注意到畫(huà)布的左上方有個(gè)“Type Here”,雙擊它即可開(kāi)始編輯菜單欄。菜單欄支持創(chuàng)建多級(jí)菜單以及分割線(separator)。我隨意創(chuàng)建了一些菜單項(xiàng)目,如下圖所示。

6)預(yù)覽

使用快捷鍵Ctrl+R預(yù)覽當(dāng)前編寫(xiě)的GUI(或者從菜單欄的Form > Preview / Preview in進(jìn)入)

7)保存

如果覺(jué)得完成了,那就可以保存成*.ui的文件,這里我們保存為HelloWorld.ui。為了方便演示,我將文件保存到D盤(pán)。

8)生成Python代碼

使用cmd將目錄切到D盤(pán)并執(zhí)行下面的命令。請(qǐng)自行將下面命令中的name替換成文件名,比如本例中的“HelloWorld.ui”

pyuic5 -o name.py name.ui1

生成的代碼應(yīng)該類似下圖所示

9)運(yùn)行Python代碼

此時(shí)嘗試運(yùn)行剛剛生成的“HelloWorld.py”是沒(méi)用的,因?yàn)樯傻奈募](méi)有程序入口。因此我們?cè)谕粋€(gè)目錄下另外創(chuàng)建一個(gè)程序叫做“main.py”,并輸入如下內(nèi)容。在本例中,gui_file_name就是HelloWorld,請(qǐng)自行替換。

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

然后運(yùn)行“main.py”,你就能看到剛剛編寫(xiě)的GUI了!

10)組件自適應(yīng)

如果你剛剛嘗試去縮放窗口,會(huì)發(fā)現(xiàn)組件并不會(huì)自適應(yīng)縮放,因此我們需要回到Qt Designer中進(jìn)行一些額外的設(shè)置。

點(diǎn)擊畫(huà)布空白處,然后在上方工具欄找到grid layout或者form layout,在本例中我們使用grid layout。兩種layout的圖標(biāo)如下圖所示。

順帶一提,上圖中l(wèi)ayout的左邊有三條橫線以及三條豎線的圖標(biāo),這兩個(gè)是用于對(duì)齊組件,非常實(shí)用。

設(shè)置grid layout后,我們使用Ctrl+R預(yù)覽,這次組件可以自適應(yīng)了!因?yàn)槲覀円呀?jīng)將UI(HelloWorld.py/HelloWorld.ui)跟邏輯(main.py)分離,因此直接重復(fù)步驟7-8即可完成UI的更新,無(wú)需改動(dòng)邏輯(main.py)部分。

0x04 Interaction

剛剛寫(xiě)的HelloWorld中,我們?cè)O(shè)置的按鈕(PushButton)是沒(méi)有實(shí)際作用的,因?yàn)槲覀儾](méi)有告訴這個(gè)按鈕應(yīng)該做什么。實(shí)際上,要讓這個(gè)按鈕做點(diǎn)什么只需要增加一行代碼就可以了。

1)獲取按鈕id

打開(kāi)HelloWorld.ui,在designer中選中對(duì)應(yīng)的按鈕,從“Property Editor”中可以得知這個(gè)按鈕的“objectName”叫做“pushButton”,如下圖所示。

2)設(shè)置觸發(fā)

Qt中有“信號(hào)和槽(signal and slot)”這個(gè)概念,不過(guò)目前無(wú)需深究,也無(wú)需在Designer中去設(shè)置對(duì)應(yīng)按鈕的“信號(hào)和槽”,直接在“main.py”中“MainWindow.show()”的后面加入下面這樣的一行代碼

ui.pushButton.clicked.connect(click_success)1

下面簡(jiǎn)單解釋下這行代碼

pushButton就是剛剛獲取的按鈕id

clicked就是信號(hào),因?yàn)槭屈c(diǎn)擊,所以我們這里用clicked

click_success就是對(duì)應(yīng)要調(diào)用的槽,注意這里函數(shù)并不寫(xiě)成click_success()

3)設(shè)置函數(shù)

既然剛剛設(shè)置了按鈕的觸發(fā)并綁定了一個(gè)函數(shù)click_success,我們就要在“main.py”中實(shí)現(xiàn)它。示例如下

def click_success():

? ? print("啊哈哈哈我終于成功了!")12

4)運(yùn)行!

UI跟邏輯分離的好處就在這里,我們這次不用去管“HelloWorld.py”了,直接運(yùn)行修改完的“main.py”。點(diǎn)擊按鈕,這次你會(huì)發(fā)現(xiàn)在控制臺(tái)中有了我們預(yù)設(shè)的輸出。

0x05 Conversion

這次我們來(lái)進(jìn)行實(shí)戰(zhàn)演練,編寫(xiě)一個(gè)帶GUI的匯率轉(zhuǎn)換器。

1)設(shè)計(jì)UI

通過(guò)上面的講解,你應(yīng)該能夠毫無(wú)壓力的設(shè)計(jì)上面這樣的UI并獲得對(duì)應(yīng)的代碼。如果不行,那么不建議繼續(xù)往下閱讀,應(yīng)當(dāng)回頭復(fù)習(xí)。

2)傳參

現(xiàn)在我們有了GUI的代碼以及上一節(jié)中使用的“main.py”,我們可以開(kāi)始編寫(xiě)這個(gè)匯率轉(zhuǎn)換器的邏輯部分。

在上一節(jié),我們介紹了如何讓按鈕響應(yīng)點(diǎn)擊操作,但是并沒(méi)有接受任何參數(shù),而且只是在控制臺(tái)輸出。但是,上一節(jié)中說(shuō)明了并不能通過(guò)正常的方式進(jìn)行傳參。因此,對(duì)于傳參,有兩種解決方案,一種是使用lambda,還有一種是使用functool.partial。在接下來(lái)的環(huán)節(jié)中我們會(huì)使用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)編寫(xiě)convert函數(shù)

首先,我們要獲取用戶輸入的數(shù)字。為了使得教程簡(jiǎn)潔易懂,我們這次只講解單向的匯率轉(zhuǎn)換。既然是單項(xiàng)的轉(zhuǎn)換,那么我們只需要獲取左側(cè)的文本框id。在本例中,左側(cè)的文本框id為lineEdit。如果你對(duì)此感到一頭霧水,請(qǐng)停下并回頭復(fù)習(xí)。

獲取文本使用的是text()方法,因此讀取用戶輸入的代碼如下

input = ui.lineEdit.text()1

接著我們進(jìn)行匯率轉(zhuǎn)換,注意這里要進(jì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

一個(gè)簡(jiǎn)單的匯率轉(zhuǎn)換器就這樣誕生了!

那么,如何知道一個(gè)組件都有什么方法呢?直接去Qt官方文檔查看就可以了。本節(jié)使用到的lineEdit的相關(guān)方法在這里

0x06 threading

1)前言

這幾天在用PyQt5寫(xiě)東西的時(shí)候遇到這樣一個(gè)問(wèn)題,網(wǎng)上資料也特別少,我感覺(jué)值得拿出來(lái)說(shuō)一說(shuō)。

我的程序中使用了threading模塊,GUI作為主線程去啟動(dòng)負(fù)責(zé)邏輯處理的子線程。其中,我設(shè)計(jì)的GUI里頭有一個(gè)日志框,用來(lái)代替終端顯示各種日志輸出。既然子線程是負(fù)責(zé)邏輯處理,那么想當(dāng)然的就會(huì)直接在子線程操作GUI的顯示。

都說(shuō)了想當(dāng)然,那當(dāng)然不行咯,在子線程對(duì)GUI操作的時(shí)候,終端會(huì)出現(xiàn)下面這個(gè)錯(cuò)誤,但是程序又不會(huì)馬上閃退。

QObject::connect: Cannot queue arguments of type 'QTextCursor'

(Make sure 'QTextCursor' is registered using qRegisterMetaType().)12

更讓人摸不著頭腦的是,過(guò)一陣子閃退的時(shí)候,會(huì)出現(xiàn)下面這句話:

段錯(cuò)誤,核心已轉(zhuǎn)儲(chǔ)1

這啥玩意兒?能說(shuō)人話么?一番搜索之后,發(fā)現(xiàn)這個(gè)原來(lái)英語(yǔ)叫做“Segmentation fault (core dumped)”。

"Segmentation fault"用人話來(lái)說(shuō)大概就是“你嘗試訪問(wèn)你無(wú)法訪問(wèn)的內(nèi)存”。

然后我把上面的報(bào)錯(cuò)信息搜索了下,發(fā)現(xiàn)之前有人在StackOverflow問(wèn)過(guò),但是答案牛頭不對(duì)馬嘴,不過(guò)倒是在評(píng)論區(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

簡(jiǎn)而言之,就是說(shuō)雖然報(bào)錯(cuò)顯示QTextCursor,但是實(shí)際上是在其它線程通過(guò)Qt內(nèi)部的方法間接調(diào)用了這個(gè)東西。

熱心大佬還留了個(gè)鏈接,我跟過(guò)去看了,收獲不少。

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

這個(gè)終于說(shuō)到點(diǎn)子上了,一句話總結(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

大概翻譯下,就是說(shuō)可以通過(guò)信號(hào)和槽來(lái)完成子線程跟GUI所在的主線程的通信,就是通過(guò)在子線程釋放信號(hào),傳遞到主線程的槽來(lái)完成。

可惜的是,大佬并沒(méi)有給出示例代碼,那接下來(lái)就是動(dòng)手實(shí)踐了。

2)實(shí)踐

首先我們?cè)谧泳€程的代碼中創(chuàng)建一個(gè)對(duì)象,并且繼承QObject(因?yàn)樾枰尫判盘?hào))。

class UpdateLog(QObject):

? ? update_signal = pyqtSignal()

? ? def __init__(self):

? ? ? ? QObject.__init__(self)

? ? def update(self):

? ? ? ? self.update_signal.emit()12345678

update_signal = pyqtSignal()就是使用Signal類來(lái)創(chuàng)建一個(gè)自定義的信號(hào)。

self.update_signal.emit()就是當(dāng)條件滿足的時(shí)候,子線程可以調(diào)用UpdateLog類的update方法,就會(huì)發(fā)出信號(hào)。

做完這些之后,主線程中別忘了連擊信號(hào)和槽,比如self.afk.utils.logger.update_signal.connect(self.write_log)。然后現(xiàn)在再嘗試運(yùn)行程序,就沒(méi)有任何問(wèn)題了。

不僅如此,其實(shí)其它需要共享的信息,也可以通過(guò)自定義信號(hào)和槽來(lái)傳遞。

那么,現(xiàn)在就可以愉快的在PyQt程序中使用threading模塊了。

0x0? 小結(jié)

本文只是拋磚引玉,上面這些只是PyQt5的入門(mén)內(nèi)容。不過(guò)學(xué)會(huì)了簡(jiǎn)單的交互方法,其它的也差不多能依葫蘆畫(huà)瓢做出來(lái)。

本文中設(shè)計(jì)的程序在/assets/code/pyqt5中。

那么,就先寫(xiě)到這里了!

完整文件下載地址:

http://www.gumenghua.com/59.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容