首頁>技術>

前言

本系列介紹如何在 Python 中使用 Qt for Python 進行 GUI 應用程式開發。

1. 簡介1.1 術語

下表列示了本文涉及到的一些術語:

術語表

1.2 絕對定位

絕對定位是一種原始的定位方式:首先要明確待放置部件的座標值(x, y)和尺寸(寬、高),這樣 Qt 就知道該把元件放在哪裡以及如何設定元件的尺寸。

絕對定位非常簡單、直觀和易於理解掌握,但是存在一個問題:當用戶改變了視窗大小(比如使用滑鼠拖動視窗邊緣進行拉伸操作)時,採用絕對定位的元件仍然保持原狀(原位置、原尺寸),而不會有任何響應(如隨著視窗同步改變尺寸等)。

存在這樣的問題是正常的,因為如果程式程式碼中未做特殊處理,那麼在視窗變化時,Qt 並不知道部件是否需要(對自己的位置和尺寸)進行更新以及如何更新。

解決這類問題通常有兩種方法:

方法一:可以在程式中設定禁止改變視窗大小。這種方法比較簡單,但通常並不可取。方法二:可以在程式中專門對發生視窗大小改變時呼叫自定義的部件更新函式,讓部件按照一定的規則完成隨視窗大小變化而同步更新。

絕對定位機制通常適用於視窗和部件固定的應用情形。

2. 座標系統

要進行絕對定位的前提是必要要有座標系統。

每個 GUI 都有對應的座標系統,座標系統用於在圖形介面程式中進行視窗和元件的定位以及尺寸設定。

一、物理座標(或裝置座標)

Windows 系統上的物理座標又稱為裝置座標,是指輸出裝置的座標。最常見的輸出裝置為顯示器,它是一個以畫素為基本單位進行描述的裝置。

二、物理座標系(或裝置座標系)特點

物理座標(或裝置座標)對應的座標系統為物理座標系(或裝置座標系)。

物理座標系(或裝置座標系)的特點是:

座標原點(0, 0)在左上角,X軸向右為正,Y軸向下為正。X軸和Y軸的負半軸為虛設,超出裝置的部分無法顯示。

三、物理座標系(或裝置座標系)分類

物理座標系(或裝置座標系)可以分為螢幕座標系、視窗座標系和客戶區域座標系三種相互獨立的座標系:

螢幕座標系:以螢幕左上角為座標原點(0, 0)。用物件距離螢幕左上角的水平距離和垂直距離來標識物件的位置,以畫素為單位來表示。視窗座標系:以視窗左上角為座標原點(0, 0)。用物件距離視窗左上角的水平距離和垂直距離來標識物件的位置。視窗客戶區域座標系:以視窗內客戶區域左上角為座標原點(0, 0)。用物件距離視窗客戶區域左上角的水平距離和垂直距離來標識物件的位置。

Qt 中使用物理座標系(或裝置座標系)來對視窗和部件進行定位以及尺寸設定。

3. 視窗幾何形狀相關函式3.1 Qt 視窗幾何形狀相關函式

QWidget 類提供了一些處理視窗或部件幾何形狀的函式。這些函式適用於以下兩類情形:

第一類情形:適用於視窗(包括視窗框架)的函式。

——(1) x() 函式

——(2) y() 函式

——(3) frameGeometry() 函式

——(4) pos() 函式

——(5) move(x, y) 函式

第二類情形:適用於視窗客戶區域(不包括視窗框架)的函式。

——(1) geometry() 函式

——(2) width() 函式

——(3) height() 函式

——(4) rect() 函式

——(5) size() 函式

3.2 座標訪問函式

一、物理座標函式示意圖

Qt 的 QWidget 類提供了視窗部件所需的物理座標系統函式,可用於在座標系統中對視窗和部件進行定位以及尺寸設定。

下圖顯示了可使用的 QWidget 類的大多數座標函式:

QWidget 類提供的大多數座標函式

二、Qt 座標訪問函式分類及說明

Qt 提供了三組座標訪問函式:

(一)、frameGeometry() 提供的一組視窗(含視窗框架)相關的座標訪問函式

——frameGeometry() :返回視窗(含視窗框架)左上角在螢幕座標上的座標值和尺寸(寬度和高度),型別為 QRect;

——frameGeometry().x() :返回視窗(含視窗框架)左上角在螢幕座標上的橫座標(x 值),等價於 x() 函式,等價於 pos().x() 函式;

——frameGeometry().y() :返回視窗(含視窗框架)左上角在螢幕座標上的縱座標(y 值),等價於 y() 函式,等價於 pos().y() 函式;

——frameGeometry().width() :返回視窗(含視窗框架)的寬度;

——frameGeometry().height() :返回視窗(含視窗框架)的高度。

(二)、geometry() 提供的一組視窗客戶區域(不含視窗框架)相關的座標訪問函式

——geometry() :返回視窗客戶區域左上角在螢幕座標上的座標值和尺寸(寬度和高度),型別為 QRect;

——geometry().x() :返回視窗客戶區域左上角在螢幕座標上的橫座標(x 值);

——geometry().y() :返回視窗客戶區域左上角在螢幕座標上的縱座標(y 值);

——geometry().width() :返回視窗客戶區域的寬度,等價於 width() 函式,等價於 rect().width() 函式,等價於 size().width() 函式;

——geometry().height() :返回視窗客戶區域的高度,等價於 height() 函式,等價於 rect().height() 函式,等價於 size().height() 函式;

(三)、 QWidget 提供的一組相關座標訪問函式

——x() :返回視窗(含視窗框架)左上角在螢幕座標上的橫座標(x 值),等價於 frameGeometry().x() 函式,等價於 pos().x() 函式;

——y() :返回視窗(含視窗框架)左上角在螢幕座標上的縱座標(y 值),等價於 frameGeometry().y() 函式,等價於 pos().y() 函式;

——width() :返回視窗客戶區域的寬度,等價於 geometry().width() 函式;

——height() :返回視窗客戶區域的高度,等價於 geometry().height() 函式。

注1:提供上述三組座標函式是為了跨平臺使用,由於不同平臺的標題欄大小不同,以及注意每組座標系統中的適用區域。

注2:在編碼開發中,geometry() 和 frameGeometry() 中的幾何資料必須在 show() 呼叫後才有效!!! 如果在 show() 呼叫之前呼叫的話資料是無效的,因為在不同的平臺中如果視窗還未顯示出來則無法知道邊框大小,只有將視窗顯示出來後才能獲取有效的座標系統資料。

3.3 絕對定位常用設定函式

我們在進行視窗或部件定位時,涉及位置和尺寸的常用的設定函式如下:

可以透過 setGeometry(x, y, w, h) 來設定/調整部件相對其父視窗的位置(左上角座標)和尺寸(寬和高);可以透過 move(x, y) 來設定/調整部件的位置(相對其父視窗的座標);可以透過 resize(w, h) 來設定/調整部件的尺寸(寬和高);可以透過 setMinimumSize(w, h) 來設定/調整部件的最小尺寸(寬和高);可以透過 setMaximumSize(w, h) 來設定/調整部件的最大尺寸(寬和高);其他設定部件尺寸的函式

注:最常用的是前3個設定函式。

一、setGeometry() 函式

 setGeometry(self, x:int, y:int, w:int, h:int) -> None

——功能:該函式用於設定/調整部件(或視窗)相對其父視窗的位置(左上角座標)和尺寸(寬和高)。

——引數 x:表示部件左上角橫座標,int 型別;

——引數 y:表示部件左上角縱座標,int 型別;

——引數 w:表示部件的寬度值(以畫素為單位),int 型別;

——引數 h:表示部件的高度值(以畫素為單位),int 型別。

注:視窗客戶區域左上角為座標原點(0, 0)。

二、move() 函式

 move(self, x:int, y:int) -> None

——功能:該函式用於設定/調整部件(或視窗)(相對其父視窗的座標)。

——引數 x:表示部件左上角橫座標,int 型別;

——引數 y:表示部件左上角縱座標,int 型別。

注:螢幕左上角為座標原點(0, 0)。

三、resize() 函式

 resize(self, w:int, h:int) -> None

——功能:該函式用於設定/調整部件(或視窗)的尺寸(寬和高)。

——引數 w:表示部件的寬度值(以畫素為單位),int 型別;

——引數 h:表示部件的高度值(以畫素為單位),int 型別。

四、setMinimumSize() 函式

 setMinimumSize(self, minw:int, minh:int) -> None

——功能:該函式用於設定/調整元件的最小尺寸(寬和高)。

——引數 minw:表示元件的最小寬度值(以畫素為單位),int 型別;

——引數 minh:表示元件的最小高度值(以畫素為單位),int 型別。

五、setMaximumSize() 函式

 setMaximumSize(self, maxw:int, maxh:int) -> None

——功能:該函式用於設定/調整元件的最大尺寸(寬和高)。

——引數 maxw:表示元件的最大寬度值(以畫素為單位),int 型別;

——引數 maxh:表示元件的最大高度值(以畫素為單位),int 型別。

六、設定部件尺寸的其他函式

 setMinimumWidth(self, minw:int) -> None setMinimumHeight(self, minh:int) -> None setMaximumWidth(self, maxw:int) -> None setMaximumHeight(self, maxh:int) -> None

——功能:上述四個函式分別用於單獨設定/調整元件的最小寬度、最小高度、最大寬度、最大高度。

——引數 minw:表示元件的最小寬度值(以畫素為單位),int 型別;

——引數 minh:表示元件的最小高度值(以畫素為單位),int 型別;

——引數 maxw:表示元件的最大寬度值(以畫素為單位),int 型別;

——引數 maxh:表示元件的最大高度值(以畫素為單位),int 型別。

4. 絕對定位示例原型

在進行 GUI 應用程式編碼之前,一般建議先勾畫出 GUI 框架(窗體及各部件的佈局等)。

本絕對定位示例原型如下:

絕對定位示例原型

5. 初始示例5.1 初始示例目標

我們首先確定初始示例程式所設想達到的目標。

一、示例目標

本示例目標是建立一個 Python GUI 應用程式,在主視窗中指定位置分別放置一個行編輯器(QLineEdit)部件。

注:為簡化處理主視窗中只放置了一個部件。

主視窗及部件的位置、尺寸及其他屬性如下:

主視窗

——(1) 視窗左上角在螢幕中的座標為(300, 300)

——(2) 尺寸:寬420px,高50px

——(3) 標題:絕對佈局示例程式

行編輯器部件(QLineEdit)

——(1) 左上角座標為(10, 10)

——(2) 尺寸:寬400px,高30px

——(3) 文字內容:我是一個絕對定位的行編輯器:座標(10, 10),尺寸(寬400px,高30px)

——(4) 文字對齊方式:居中對齊

——(5) 行編輯器顏色:文字顏色為藍色;背景色為粉紅色;

5.2 初始示例程式碼

利用 Visual Studio Code 編輯示例程式碼,並儲存為檔案(如:C:\MyPySide6\MyPySide6LayoutApp01.py)。

from PySide6.QtWidgets import (QWidget, QApplication, QLineEdit)from PySide6.QtCore import Qtclass MyMainWindow(QWidget):    def __init__(self):        super(MyMainWindow, self).__init__()        self.initUi()    def initUi(self):        self.lineEdit = QLineEdit("我是一個絕對定位的行編輯器:座標(10, 20),尺寸(寬400px,高30px)", self, alignment=Qt.AlignCenter)        self.lineEdit.setStyleSheet("color: blue; background-color: pink;")        self.lineEdit.move(10, 10)        self.lineEdit.resize(400, 30)        self.setWindowTitle("絕對定位示例程式")        self.setGeometry(300, 300, 420, 50)if __name__ == '__main__':    import sys    app = QApplication(sys.argv)    win = MyMainWindow()    win.show()    sys.exit(app.exec_())
5.3 示例程式碼解析

本示例程式碼共分三部分:

一、匯入模組(或類)部分

from PySide6.QtWidgets import (QWidget, QApplication, QLineEdit)from PySide6.QtCore import Qt

——第1行程式碼:表示從 PySide6.QtWidgets 模組匯入後續程式碼中會用到 QApplication 類、QWidget 類、QLineEdit 類。

——第2行程式碼:表示從 PySide6.QtCore 模組匯入後續程式碼中會用到 Qt 類。

二、自定義 MyMainWindow 類部分

其次,自定義 MyMainWindow 類(即主視窗,繼承自 QWidget 類):

(一)定義類的宣告

class MyMainWindow(QWidget):    ...

自定義 MyMainWindow 類的宣告語句。類名為 MyMainWindow,該類繼承自 QWidget 類。

(二)定義類的建構函式

 def __init__(self):     super(MyMainWindow, self).__init__()     self.initUi()

上述程式碼定義了自定義 MyMainWindow 類的建構函式:

——第1行程式碼:宣告一個類建構函式( __init__(self)),有一個傳遞引數(self)。

——第2行程式碼:透過 super() 方法繼承了父類(QWidget)建構函式中的全部屬性。

——第3行程式碼:呼叫 initUi() 方法,完成窗體的初始化。該方法會在類中加以定義。

(三)定義類方法 initUi(self)

def initUi(self):	self.lineEdit = QLineEdit("我是一個絕對定位的行編輯器:座標(10, 20),尺寸(寬400px,高30px)", self, alignment=Qt.AlignCenter)    self.lineEdit.setStyleSheet("color: blue; background-color: pink;")    self.lineEdit.move(10, 10)    self.lineEdit.resize(400, 30)    self.setWindowTitle("絕對定位示例程式")    self.setGeometry(300, 300, 420, 50)

上述程式碼定義了一個類方法 initUi(self) ,用於窗體的初始化,建立了窗體上的所有部件並設定其屬性。

——第1行程式碼:宣告一個類方法(函式名 initUi),有一個傳遞引數(self)。

——第2行程式碼:建立1個行編輯器物件(物件名為 lineEdit),並設定其顯示文字內容(居中對齊)。

——第3行程式碼:行編輯器物件呼叫其 setStyleSheet() 方法,設定其背景色為粉紅色,文字顏色為藍色。

——第4行程式碼:行編輯器物件呼叫其 move() 方法,設定行編輯器放置的左上角座標為(10, 10)。

——第5行程式碼:行編輯器物件呼叫其 resize() 方法,設定行編輯器的尺寸(寬400px,高30px)。

——第7行程式碼:窗體呼叫其 setGeometry() 方法,設定窗體客戶區域左上角螢幕座標(300, 300)和尺寸(寬420px,高50px)。

——第8行程式碼:設定窗體的標題("絕對定位示例程式")。

三、設定檔案執行入口部分

最後,在設定檔案執行入口部分,完成建立應用程式、建立和顯示自定義主視窗、執行應用程式直至退出。

if __name__ == "__main__":    import sys         app = QApplication(sys.argv)    win = MyMainWindow()    win.show()    sys.exit(app.exec_())

——第1行程式碼:透過 if __name__ == "__main__": 語句來設定檔案執行入口。

——第2行程式碼:匯入 Python 內建的 sys 模組,接下的 sys.argv 和 sys.ext() 會用到該模組。

——第4行程式碼:使用 QApplication 類建立一個應用程式物件(app),括號內的 sys.argv 表示構造時含的傳遞引數。

——第5行程式碼:使用自定義的 MyMainWindow 類建立應用程式的主視窗物件(win)。

——第6行程式碼:呼叫主視窗物件(win)的 show() 方法來顯示該主視窗。

——第7行程式碼:執行應用程式,直至退出。

初始示例程式執行視窗

上圖是程式執行後的三種視窗:

上面的是程式執行後縮小後的視窗:此時行編輯器並沒有進行同步縮放,導致顯示不完整;中間的是程式執行後原始視窗:滿足設計目標;下面的是程式執行後拉伸後的視窗:此時行編輯器並沒有進行同步縮放,導致出現大片空白區域(按視窗最大化快捷按鈕後的效果也是類似)。

可以看出,本初始示例程式雖然初步實現了示例目標,還明視訊記憶體在問題(行編輯器並不會隨著視窗大小變化而同步縮放)。

6.2 改進方法

我們先採取第一種方法來加以改進,該方法簡單粗暴,只需在程式碼中呼叫視窗的 setFixedSize() 方法即可實現。

 def setFixedSize(self, w:int, h:int) -> None

——該方法功能:為物件設定固定的寬度和高度。

——引數 w:整形變數,表示設定固定的寬度值。

——引數 h:整形變數,表示設定固定的高度值。

6.3 改進程式碼及解析

利用 Visual Studio Code 在原初始示例程式碼基礎上進行編輯,並另存為檔案(如:C:\MyPySide6\MyPySide6LayoutApp02.py)。

一、改進程式碼

在原始示例程式碼中定義的 initUi(self) 函式的最後新增如下程式碼語句:

self.setFixedSize(self.width(), self.height())

注:因僅增加一行程式碼,就不再單獨展示改進示例程式的完整程式碼了。

二、程式碼解析

上述改進程式碼呼叫 setFixedSize() 方法,設定其寬度值為窗體當前的寬度,其高度值為窗體當前的高度。

下圖是程式執行後的視窗:

改進示例1程式執行視窗

7. 改進示例27.1 改進示例目標2

在初始示例目標基礎上,程式視窗允許進行拉伸或縮小(視窗最小為原始尺寸,不能再小),視窗拉伸時行編輯器應隨著著視窗同步拉伸或縮小,並且行編輯器文字中的寬度和高度值應實時重新整理。同時要求視窗最小化、最大化、關閉快捷按鈕也都正常有效。

為簡化起見,變化規則設定如下:

——(1) 視窗、行編輯器設定有最小尺寸,即各自構造時的初始尺寸。

——(2) 行編輯器:位置(左上角座標)保持不變,在保持離視窗客戶區域上下左右各10px外,寬度和寬度會隨著視窗同步縮放。

7.2 改進方法

我們再採取第二種方法來加以改進。

改進技術原理:在視窗構造完成之後,視窗和各部件的位置和尺寸就確定下來了。當視窗大小發生(如拉伸視窗)時,會自動觸發 resizeEvent() 事件。可以在 resizeEvent() 事件處理函式中對視窗中各部件的位置和尺寸更新進行處理,進而實現當視窗大小發生時,視窗中各部件也能隨著視窗按相應規則同步縮放。

改進思路

(1) 在程式程式碼中重新實現 resizeEvent() 事件處理函式,這樣當發生視窗大小改變時會呼叫重新實現的 resizeEvent() 事件處理函式,讓視窗及部件按照相應的規則完成隨視窗大小變化而同步更新。

(2) 另外,各部件通常會有顯示的最小尺寸,如果不加控制會導致視窗介面顯示不完整,所以需要對視窗以及各部件的最小尺寸進行設定。

這種改進方法從技術原理上來講是可行的,但實踐中很少真正應用。原因是:一方面是這種做法需要逐一對各部件的位置和尺寸進行計算,然後重新設定其位置和尺寸,會非常複雜,容易疏忽遺漏某些情況導致不能達到預期效果;另一方面可以藉助佈局定位機制就能便捷地實現同樣的效果,沒必要採用這種低效的實現方法。

本文介紹這種改進方法,只是從技術角度驗證一下,幫助加深對絕對定位機制的理解。

7.3 改進示例程式碼

利用 Visual Studio Code 在原初始示例程式碼基礎上進行編輯,並另存為檔案(如:C:\MyPySide6\MyPySide6LayoutApp03.py)。

from PySide6.QtWidgets import (QWidget, QApplication, QLineEdit)from PySide6.QtCore import Qtfrom PySide6.QtGui import QResizeEventclass MyMainWindow(QWidget):    def __init__(self):        super(MyMainWindow, self).__init__()        self.initUi()    def initUi(self):        self.lineEdit = QLineEdit("我是一個絕對定位的行編輯器:座標(10, 20),尺寸(寬400px,高30px)", self, alignment=Qt.AlignCenter)        self.lineEdit.setStyleSheet("color: blue; background-color: pink;")        self.lineEdit.move(10, 10)        self.lineEdit.resize(400, 30)        self.setGeometry(300, 300, 420, 50)        self.setMinimumSize(420, 50)        self.lineEdit.setMinimumSize(400, 30)        self.setWindowTitle("絕對定位示例程式")    def updateText(self):        str_width = str(self.width()-10*2)        str_height = str(self.height()-10*2)        text_lineEdit = "我是一個絕對定位的行編輯器:座標(10, 20),尺寸(寬" + str_width +"px,高" + str_height +"px)"        self.lineEdit.setText(text_lineEdit)    def resizeEvent(self, event:QResizeEvent):        self.lineEdit.setGeometry(10, 10, self.width()-10*2, self.height()-10*2)        self.updateText()if __name__ == '__main__':    import sys    app = QApplication(sys.argv)    win = MyMainWindow()    win.show()    sys.exit(app.exec_())
7.4 改進程式碼解析

改進程式碼基於初始示例程式碼,在本節中對已經解析過的程式碼就不再解析,只是針對改進程式碼進行解析。

一、在定義類方法 initUi(self) 中增加設定視窗和行編輯器的最小尺寸

在原始示例程式碼中定義的 initUi(self) 函式的最後新增如下2行程式碼語句:

self.setMinimumSize(420, 50)self.lineEdit.setMinimumSize(400, 30)

——第1行程式碼:透過 setMinimumSize() 方法設定視窗的最小尺寸為(寬420px, 高50px)

——第2行程式碼:透過 setMinimumSize() 方法設定行編輯器的最小尺寸為(寬400px, 高30px)

二、重新實現 resizeEvent() 事件處理函式

在自定義 MyMainWindow 類中重新實現 resizeEvent() 事件處理函式:

def resizeEvent(self, event:QResizeEvent):    self.lineEdit.setGeometry(10, 10, self.width()-10*2, self.height()-10*2)    self.updateText()

——第1行程式碼:宣告一個事件處理函式(函式名 resizeEvent),有兩個傳遞引數(self 和 event),其中 event 是 QResizeEvent 型別(需要在檔案開頭部分從 PySide6.QtGui 模組中匯入 QResizeEvent 類)。

——第2行程式碼:呼叫行編輯器的 setGeometry() 方法,設定行編輯器左上角座標保持不變,寬度和高度隨著視窗同步縮放(保持距離視窗客戶區域上下左右各10px)。

——第3行程式碼:呼叫自定義函式 updateText(),重新整理行編輯器文字內容中的寬度和高度值

三、自定義函式 updateText()

在自定義 MyMainWindow 類中自定義函式 updateText() :

def updateText(self):	str_width = str(self.width()-10*2)    str_height = str(self.height()-10*2)    text_lineEdit = "我是一個絕對定位的行編輯器:座標(10, 20),尺寸(寬" + str_width +"px,高" + str_height +"px)"    self.lineEdit.setText(text_lineEdit)

——第1行程式碼:宣告一個自定義函式(函式名 updateText),有一個傳遞引數(self)。

——第2行程式碼:呼叫 str() 方法將視窗客戶區域的寬度值減去左右留空10px後的數值轉換為字串;

——第3行程式碼:呼叫 str() 方法將視窗客戶區域的高度值減去上下留空10px後的數值轉換為字串;

——第4行程式碼:拼接行編輯器文字內容到字串變數(text_lineEdit)

——第5行程式碼:呼叫行編輯器的 setText() 方法重新整理其文字內容(主要是重新整理文字內容中的寬度和高度值)

下圖是程式執行後的兩種視窗(上面的是程式執行後原始視窗,下面是程式執行後拉伸後的視窗):

改進示例2程式執行視窗

可以發現第2次改進後的程式視窗實現了改進示例目標2:

能按要求在視窗中顯示行編輯器文字資訊(粉紅色背景,藍色字型,居中對齊方式);程式視窗可以進行拉伸或縮小(視窗最小為原始尺寸,不能再小),視窗拉伸時行編輯器會隨著著視窗同步拉伸或縮小,並且行編輯器文字中的寬度和高度值會實時重新整理。視窗最小化、最大化、關閉快捷按鈕也都正常有效。結束語

本文是《Qt for Python 學習筆記》系列第七篇,比較詳細地介紹了 Qt for Python 的絕對定位(包括座標系統、視窗幾何形狀相關函式、座標訪問函式、絕對定位常用設定函式、絕對佈局示例和兩個改進示例等),讓讀者對 Qt for Python 絕對定位有個較為全面的瞭解和掌握,不過原則上並不建議讀者採用絕對定位的方式來進行開發。

接下來會介紹 Qt for Python 佈局管理中較為常用的盒式佈局及其子類水平佈局,敬請期待!

19
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Spyder –用Python編寫的免費開源科學領域IDE