汪群超 Chun-Chao Wang

Dept. of Statistics, National Taipei University, Taiwan

Qt Designer 設計概念及與 PyQt 結合的技術(一)

本單元起,必須安裝 Qt Designer 作為設計 GUI 的布局之用。安裝 Qt Designer 最簡便的方式:

在 Python 環境: > pip install pyqt6-tools
當然,之前必須先安裝 PyQt6 (> pip install pyqt6)。安裝完 pyqt6_tools 之後的 Designer 應用程式會被安置在 python 下的路徑。譬如,C:\Users\...\AppData\Local\Programs\Python\Python39\Lib\site-packages\qt6_applications\Qt\bin\designer.exe
為方便未來使用,可以為 designer.exe 建立桌面捷徑。

在 Anaconda 環境:在之前的單元安裝過 pyqt 之後,已經在 Anaconda 的路徑上安裝了 Designer,請循線找到 Designer (譬如在 Mac 的路徑:/Users/your_user_name/opt/anaconda3/bin/)。

在某些情況下,安裝 pyqt6-tools 或甚至降到 pyqt5-tools 不成功者,可以只安裝獨立的 Qt Designer 軟體。下載點:https://build-system.fman.io/qt-designer-download

Objective:

  1. 學習如何使用 Qt Designer 設計 Python GUI 應用程式的介面。
  2. 學習如何將設計好的介面檔案(*.ui)與 Python 主檔案(*.py)結合,並能操控元件之間的互動。
Qt Designer 為開發 GUI 程式介面應用軟體,為開發 Qt 的公司 Digia 的產品,透過 PyQt6 或 PySide6 並結合繪圖的 pyqtgraph 成為 Python GUI 的主流開發工具。

Prerequisite:

  1. Lesson 1, 2 的觀念與技術。
  2. 查詢與閱讀手冊的能力。

範例 1:使用 Designer 設計一個簡單的 GUI,視窗如下右圖。

使用元件:comboBox, lineEdit, label, pushbutton

注意事項:

  1. 下載本範例使用的 PyQtDesigner_1.ui 檔案 (解壓縮後,移到程式所在的目錄)

  2. 前一個單元刻意使用指令來佈建 GUI,屬於後臺編輯的概念,對一般初學者而言,門檻太高,不易親近,只適合解說 GUI 程式的概念與動線流程。Qt Designer 則是提供接近前台編輯的介面,讓設計者專心於元件的佈建,只需拖曳、安排、設定 properties 即可輕鬆完成複雜的 GUI 視窗。接著再交由 python 程式串聯元件之間的溝通與功能的呈現。也就是,設計者應該將大部分的時間花在這裡,而不是被元件佈建的瑣碎細節所牽絆。

  3. GUI 應用程式牽涉到使用者介面(Graphical User Interface)的設計與程式撰寫,兩方面都需要足夠的經驗,才能完成一個堪用的軟體。而獲取經驗的方式,便是不斷地練習、模仿與創作自己心目中想製作的應用軟體。

  4. 在此無法詳述 Designer 的設計細節,只能透過上課的實作演練去熟悉 Designer 的環境,多做練習,很快便能上手。

  5. 本範例刻意與前一單元的範例三相同,呈現兩種不同的做法。因此元件的變數名稱都設定相同,使前一個範例的大部分程式碼能在此直接使用。兩個程式最大的差異在元件的建立、特性的設定。利用 Designer 設計的 GUI 程式以 *.ui 為副檔名,內涵元件佈建的指令。在下列程式中,被 load 進來,成為程式的一部分。指令為 ui.loadUi(‘檔名’, self)。在此,檔名為 PyQtDesigner_1.ui

  6. 讀者可以比對下列程式碼與前一單元範例三的程式碼,便能理解 Designer 的作用。

from PyQt6 import QtWidgets, uic
import sys

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        #Load the UI Page by PyQt6
        uic.loadUi('PyQtDesigner_1.ui', self)
        self.setWindowTitle('Add more components')
        
        # signal
        self.go_button.clicked.connect(self.go)
    
    # slot
    def go(self):
        str1 = self.greeting.currentText()
        str2 = self.recipient.text()
        self.greet_rep.setText(str1 + ' ' + str2)

def main():
    app = QtWidgets.QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()

練習:使用 Designer 設計如下右圖的 GUI 介面。

內容取自 PyQt6 Tutorial / Creating applications with Qt Designer (有製作過程影片與文字說明)

注意事項:

  1. 下圖的 GUI 設計只是練習排列的技巧,label 的文字或其他元件的內容並不重要。建立視窗時,採 Dialog 模式,自動具備最下面的兩個按鈕。

  2. 這個練習的重點是利用現成的布局功能,將多個元件迅速地排列整齊。而非手動一一對齊。

  3. 請先看完製作過程的影片,再自行模仿一次。這裡牽涉到好幾個布局的技巧,非常值得收藏。

  4. 下圖右側的 Note 將幾個重點題列出來,方便迅速提醒。


範例 2:使用 Designer 設計如下右圖的 GUI 視窗。這是個簡單的圖形展示工具,透過 comboBox 元件選擇欲呈現的圖形,並將圖形呈現在 label 元件,順便置換上面的文字。

使用元件:comboBox, label, pushbutton

注意事項:

  1. 下載本範例之 ui 檔。在使用現成的 ui 檔前,試著先自己從 Designer 製作如下圖的 GUI 檔案。各元件的名稱可以從程式中找到。

  2. 在應用程式上呈現各種圖片是常見的應用。有些圖片只做裝飾或單純呈現,有些則是作為分析圖像的之用。若是前者,通常以 label 元件呈現圖片,如本範例;若是後者,則採 pyqtgraph 的繪圖方式。

  3. 本範例的主角是兩個 signal,一個藉由 comboBox 啟動圖形之開啟與貼上,另一個則是透過 EXIT 按鈕關閉視窗。初學者要掌握的是元件的啟動條件是甚麼?譬如,comboBox 依據選項的改變而啟動,這個機制叫做:currentIndexChanged 後面連接 connect(slot 函數)。另外,按鈕的部分則是 clicked,在前面的單元已經練習過了。

  4. 另一個值得參考的重點是,comboBox 引動的 slot 函數 showImg(slef, s),除了第一個必備的參數 self 之外,還附上第二個取名為 s 的參數。但 signal 呼叫端只是單純的 connect(showImg),並沒有給任何參數。原來,這個額外的參數是 comboBox 自動給出的。那麼 s 的內容是甚麼呢?是選項的數字?還是文字?遇上這種不確定的場合,可以在下面用 print(s) 觀察,或啟動 debug 機制來觀看。

  5. 另,Slot 函數裡第二個指令處裡兩件事:第一、從 comboBox 取得目前被選取的項目的文字(請注意,這裡故意採用與範例 1 不同的 method);第二、將該文字寫在最上面的 label。相關的用法不難直接從指令中窺探。

  6. 這個範例需要準備幾張圖片。下列程式的寫法,是假設所有圖片都放在上一層的目錄:../images/ 之下。圖檔名與其排列都是先安排在 self.picName 變數裡面。

  7. 本範例雖有兩個 signal,卻只有一個對應的 slot,因為 EXIT 按鈕的目的是關閉視窗,因此直接在 signal 處,呼叫關閉視窗的指令,即 self.close。

  8. 本範例的 GUI 視窗最下面還放了一個 Text Broswer 的元件,作為提醒讀者還有哪些功能可以隨後加上,作為後續練習與精進之用。這個文字元件以 Browser 為名,代表可以使用 html 語法,以展現更多元的多媒體表現。

from PyQt6 import QtWidgets, uic
from PyQt6.QtGui import QPixmap
import sys

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.picName = ["NTPU_Campus_1", "NTPU_Campus_2", "NTPU_Campus_3"]

        #Load the UI Page by PyQt6
        uic.loadUi('PyQtDesigner_2.ui', self)
        self.setWindowTitle('Show images on the label widget')

        # Signals
        self.comboBox_ImgName.currentIndexChanged.connect(self.showImg)
        self.pBut_exit.clicked.connect(self.close)

# Slots
    def showImg(self, s):
        self.label_Img.setPixmap(QPixmap(u"../images/" + self.picName[s]))
        self.label_cap.setText(self.comboBox_ImgName.itemText(s)) # set Label text
        # self.label_cap.setText(self.comboBox_ImgName.currentText())
    
def main():
    app = QtWidgets.QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()

練習:模仿上個範例的做法,製作如下圖的應用程式。


注意事項:

  1. 很明顯的,這是個圖片瀏覽器。四個按鈕各有功能,因此必須啟動四個 signal,不過功能的差異只在第幾張圖,也許可以共用 slot 函數。

  2. 多準備幾張圖,讓這個應用程式用起來很像樣。

  3. 在範例程式碼中,儲存圖片檔案採取 hard copy 的方式:self.picName = [“NTPU_Campus_1”, “NTPU_Campus_2”, “NTPU_Campus_3”],這在圖片很多的情況下,是不可行的。下列程式碼介紹正規的作法。假設所有的圖片都放在與程式檔所在目錄平行的目錄 images,則圖片檔名可以這樣取得:

 import os
...
        self.file_src = "../images/"
        self.picName = os.listdir(self.file_src)
...
%d bloggers like this: