面向聰明小白的程式設計入門教程 深入Qt(Part 6)
經過上一節的學習,我們熟悉了Qt的一些基本用法。本節繼續深入。
1. 訊息框
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QMessageBox ) from datetime import datetime app = QApplication([]) btn = QPushButton("明月幾時有?") btn.resize(600, 600 * 0.618) btn.setFont(QFont(None, 72, QFont.Bold)) btn.setObjectName("MyButton") btn.setStyleSheet("#MyButton { color: magenta }") btn.clicked.connect(lambda: QMessageBox.information(btn, None, f"明月 {str(datetime.now())[:19]} 有")) btn.show() app.exec()
執行這段程式碼,點選按鈕,會彈出一個對話方塊,顯示系統的當前時間。
Qt內建了多種對話方塊,供開發者直接呼叫。最常用的對話方塊是訊息對話方塊,通過QMessageBox.information
呼叫。此函式接收至少三個引數,第一個引數是父控制元件,第二個引數是對話方塊標題,第三個引數是對話方塊正文。除了information
訊息對話方塊外,還可以使用warning
彈出報警對話方塊,使用critical
彈出錯誤對話方塊。
對於對話方塊來說,它會居中於父控制元件顯示。如果父控制元件為空,它會居中於螢幕顯示。
在這段程式碼中,我們接觸到到了很多新知識。其中:
-
datetime是Python內建的日期時間處理模組。
datetime.datetime
是日期時間(日期+時間)類。從datetime
匯入datetime
,模組名和類名相同。由於Python是一門草根語言,難免會有部分命名匪夷所思,以後就見怪不怪了。 -
datetime.now()
獲取當前的日期時間,返回一個日期時間(datetime)物件。 -
我們需要獲取日期時間的字串形式,所以呼叫了方法
str
。str
可以將物件轉換為字串。比如日期時間物件,轉換為字串的格式類似於2019-03-23 19:50:26.439716
。 -
由於我們不需要微秒部分,所以需要把多餘的部分擷取掉。語法
[0:19]
可以擷取字串的前19位。在大多數程式語言中,索引(序號)都是從0開始的,區間範圍一般都是左閉右開,[0:19]
即[0,19)
,即從索引0到索引18,共19個字元。[0:19]
可以簡寫為[:19]
。除了從前往後索引外,我們還可以從後往前索引。對於datetime
字串,[0:19]
於[0:-7]
擷取的結果一致。[0:-7]
表示從索引0擷取到索引-7(不包括)。負數的索引不是從0開始,而是從1開始。索引-7即表示倒數第七個字元。[0:-7]
也可以簡寫為[:-7]
。 -
information
方法的第三個引數是一個字串,因此我們需要做字串拼接。字串的拼接可以使用+
符號,也可以用f
語法。如果字串字首以f
字元,字串裡{}
裡面的文字就會當作Python
程式碼運算。
2. 動態更新窗體標題
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget ) from datetime import datetime app = QApplication([]) btn = QPushButton("明月幾時有?") btn.resize(600, 600 * 0.618) btn.setFont(QFont(None, 72, QFont.Bold)) btn.setObjectName("MyButton") btn.setStyleSheet("#MyButton { color: magenta }") btn.clicked.connect(lambda: btn.setWindowTitle(f"明月 {str(datetime.now())[:-7]} 有")) btn.show() app.exec()
執行這段程式碼,每次點選按鈕,都會將當前的系統時間更新到視窗的標題欄。
3. 使用狀態列
普通的按鈕只是一個簡單的控制元件,不可能內嵌一個狀態列。為了簡化開發,Qt
提供了一個通用的主窗體類QMainWindow
。這個窗體內建了選單欄、工具欄和狀態列,省卻了程式設計師自己佈局。
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QMainWindow, QStatusBar ) from datetime import datetime app = QApplication([]) wnd = QMainWindow() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(600, 600 * 0.618) sb: QStatusBar = wnd.statusBar() sb.setFont(QFont("Aria", 27)) sb.setStyleSheet("color: green") btn = QPushButton("明月幾時有?") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setObjectName("MyButton") btn.setStyleSheet("#MyButton { color: magenta }") btn.clicked.connect(lambda: sb.showMessage(f"明月 {str(datetime.now())} 有")) wnd.setCentralWidget(btn) wnd.show() app.exec()
執行以上程式碼,每次點選按鈕,都會把當前日期時間更新到狀態列。要點:
-
QMainWindow.setCentralWidget
用來設定窗體的中心控制元件,也就是主控制元件。這個中心控制元件的大小會自動適應窗體大小。所以,在中心控制元件上呼叫resize
沒有效果,需要resize
的是主窗體wnd
。 -
QMainWindow.statusBar
方法可以獲取窗體的狀態列 -
QStatusBar.showMessage
方法可以更新狀態列 -
QFont('Aria', 27)
建構函式會生成大小為27個畫素點(1K解析度的螢幕有200萬個畫素點)的Aria字型 -
主窗體是根控制元件,應該呼叫主窗體的
show
方法來顯示窗體。 -
由於
PyQt5
的程式碼不夠完善,IDE無法推導QMainWindow.statusBar
方法返回的資料型別。sb: QStatusBar
這種語法用來做型別提示,在執行程式碼時不起作用,但是可以告知IDE當前變數的型別,幫助IDE做程式碼分析與提示
4. 初識佈局
由於Qt
的狀態列在蘋果系統上顯示得挺醜,我們決定使用自定義的佈局來顯示當前時間。
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget, QLabel, QVBoxLayout ) from PyQt5.QtCore import Qt from datetime import datetime app = QApplication([None, '-style=Macintosh']) wnd = QWidget() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(600, 600 * 0.618) lbl = QLabel("Ready.") lbl.setStyleSheet("color: lime; font-size: 108px; background: teal") lbl.setAlignment(Qt.AlignCenter) btn = QPushButton("明月幾時有?") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setStyleSheet("color: magenta") btn.clicked.connect(lambda: lbl.setText(datetime.now().strftime('%X'))) box = QVBoxLayout() box.addWidget(lbl) box.addWidget(btn) wnd.setLayout(box) wnd.show() app.exec()
執行以上程式碼,可以看到窗體中從上到下排列了兩個控制元件。上面的是一個文字框,下面的是一個按鈕。拖動窗體邊框來拉伸窗體,會發現按鈕只能左右伸縮,而文字標籤會同時進行水平伸縮和垂直伸縮來填充剩餘空間。說明這兩個控制元件有不同的佈局策略。
這段程式碼引入的新知識:
-
QApplication
建構函式列表的第二個引數可以用來設定顯示主題。Qt支援Windows主題(Windows)和macOS主題(Macintosh) -
datetime.strftime
可以按照指定的格式來格式化日期時間,’%X’只顯示時分秒。 -
QVBoxLayout
是一種垂直佈局,子控制元件按照QVBoxLayout.addWidget
的執行順序依次排列各子元件
5. 均分佈局
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget, QLabel, QVBoxLayout, QSizePolicy ) from PyQt5.QtCore import Qt from datetime import datetime app = QApplication([None, '-style=Macintosh']) wnd = QWidget() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(600, 600 * 0.618) lbl = QLabel("Ready.") lbl.setStyleSheet("color: lime; font-size: 108px; background: teal") lbl.setAlignment(Qt.AlignCenter) btn = QPushButton("明月幾時有?") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setStyleSheet("color: magenta") btn.clicked.connect(lambda: lbl.setText(datetime.now().strftime('%X'))) btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) box = QVBoxLayout() box.addWidget(lbl) box.addWidget(btn) wnd.setLayout(box) wnd.show() app.exec()
要點:
-
QSizePolicy
類表示控制元件的縮放策略。QSizePolicy.Preferred
表示這個控制元件可以被縮放 -
QPushButton
是QWidget
的子類,QPushButton
可以呼叫QWidget
提供的任何方法 -
QWidget.setSizePolicy
可以用來設定控制元件的縮放策略,兩個引數分別代表水平方向和垂直方向的縮放策略
6. 按比例均分佈局
接下來的例子中,我們將演示如何按比例來均分佈局。
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget, QLabel, QVBoxLayout, QSizePolicy, QToolButton ) from PyQt5.QtCore import Qt from datetime import datetime app = QApplication([None, '-style=Macintosh']) wnd = QWidget() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(600, 600 * 0.618) lbl = QLabel("Ready.") lbl.setStyleSheet("color: lime; font-size: 108px; background: teal") lbl.setAlignment(Qt.AlignCenter) lbl.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) btn = QPushButton() btn.setText("明月幾時有?") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setStyleSheet("QPushButton { color: magenta; border: none }" "QPushButton:pressed { background: lightcyan }") btn.clicked.connect(lambda: lbl.setText(datetime.now().strftime('%X'))) btn.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) box = QVBoxLayout() box.setSpacing(0) box.setContentsMargins(0, 0, 0, 0) box.addWidget(lbl, 2) box.addWidget(btn, 1) wnd.setLayout(box) wnd.show() app.exec()
執行以上程式碼,縮放視窗,會發現兩個控制元件的高度始終保持2:1
。這段程式碼要點如下:
QVBoxLayout.addWidget QSizePolicy.Ignored QVBoxLayout.setSpacing QVBoxLayout.setContentMargins border: none QPushButton:pressed Python
7. 佈局巢狀
佈局可以互相巢狀。
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget, QLabel, QVBoxLayout, QSizePolicy, QToolButton, QHBoxLayout ) from PyQt5.QtCore import Qt from datetime import datetime app = QApplication([None, '-style=Macintosh']) wnd = QWidget() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(666, 666 * 0.618) lbl = QLabel("經海底問無由") lbl.setStyleSheet("color: lime; font-size: 108px; background: teal") lbl.setAlignment(Qt.AlignCenter) lbl.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) btn = QPushButton("明月幾時有") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setStyleSheet("QPushButton { color: aqua; border: none; background: seagreen }" "QPushButton:pressed { background: olive }") btn.clicked.connect(lambda: lbl.setText(str(datetime.now())[11:-4])) btn.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) bye = QPushButton("休休") bye.setFont(btn.font()) bye.setStyleSheet("QPushButton { color: aqua; border: none; background: darkgoldenrod }" "QPushButton:pressed { background: olive }") bye.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) bye.clicked.connect(QApplication.exit) btm = QHBoxLayout() btm.addWidget(btn, 2) btm.addWidget(bye, 1) box = QVBoxLayout() box.setSpacing(0) box.setContentsMargins(0, 0, 0, 0) box.addWidget(lbl, 2) box.addLayout(btm, 1) wnd.setLayout(box) wnd.show() app.exec()
執行以上程式碼,可以看到窗體同時使用了水平佈局和垂直佈局。要點:
QHBoxLayout QBoxLayout.addLayout
8. 全屏切換
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget, QLabel, QVBoxLayout, QSizePolicy, QToolButton, QHBoxLayout ) from PyQt5.QtCore import Qt from datetime import datetime app = QApplication([None, '-style=Macintosh']) wnd = QWidget() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(999, 999 * 0.618) lbl = QLabel("經海底問無由") lbl.setStyleSheet("color: lime; font-size: 108px; background: teal") lbl.setAlignment(Qt.AlignCenter) lbl.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) btn = QPushButton("明月幾時有") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setStyleSheet("QPushButton { color: aqua; border: none; background: seagreen }" "QPushButton:pressed { background: olive }") btn.clicked.connect(lambda: lbl.setText("經海底問無由\n" + str(datetime.now())[11:-4])) btn.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) big = QPushButton("畫屏幽") big.setFont(btn.font()) big.setStyleSheet("QPushButton { color: aqua; border: none; background: steelblue }" "QPushButton:pressed { background: olive }") big.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) big.clicked.connect(lambda: wnd.showNormal() if wnd.isFullScreen() else wnd.showFullScreen()) bye = QPushButton("休休") bye.setFont(btn.font()) bye.setStyleSheet("QPushButton { color: aqua; border: none; background: forestgreen }" "QPushButton:pressed { background: olive }") bye.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) bye.clicked.connect(QApplication.exit) btm = QHBoxLayout() btm.addWidget(btn, 3) btm.addWidget(big, 2) btm.addWidget(bye, 1) box = QVBoxLayout() box.setSpacing(0) box.setContentsMargins(0, 0, 0, 0) box.addWidget(lbl, 2) box.addLayout(btm, 1) wnd.setLayout(box) wnd.show() app.exec()
要點:
-
QWidget.showFullScreen()
進入全屏模式 -
QWidget.showNormal()
退出全屏模式 -
QWidget.isFullScreen()
判斷控制元件是否全屏,返回值是布林值(只有True和False兩種情況) -
吃飯 if 餓了 else 減肥
這種語法中if
要倒裝 -
字串中以
\
開頭的字元有特殊意義,比如\n
表示換行
9. 移除窗體邊框
from PyQt5.Qt import ( QApplication, QPushButton, QFont, QWidget, QLabel, QVBoxLayout, QSizePolicy, QHBoxLayout ) from PyQt5.QtCore import Qt from datetime import datetime app = QApplication([None, '-style=Macintosh']) wnd = QWidget() wnd.setWindowTitle("白雲千載空悠悠") wnd.resize(999, 999 * 0.618) lbl = QLabel("經海底問無由") lbl.setStyleSheet("color: lime; font-size: 108px; background: teal") lbl.setAlignment(Qt.AlignCenter) lbl.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) btn = QPushButton("明月幾時有") btn.setFont(QFont(None, 72, QFont.Bold)) btn.setStyleSheet("QPushButton { color: aqua; border: none; background: seagreen }" "QPushButton:pressed { background: olive }") btn.clicked.connect(lambda: lbl.setText("經海底問無由\n" + str(datetime.now())[11:-4])) btn.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) big = QPushButton("畫屏幽") big.setFont(btn.font()) big.setStyleSheet("QPushButton { color: aqua; border: none; background: steelblue }" "QPushButton:pressed { background: olive }") big.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) big.clicked.connect(lambda: wnd.showNormal() if wnd.isFullScreen() else wnd.showFullScreen()) bye = QPushButton("休休") bye.setFont(btn.font()) bye.setStyleSheet("QPushButton { color: aqua; border: none; background: forestgreen }" "QPushButton:pressed { background: olive }") bye.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) bye.clicked.connect(QApplication.exit) btm = QHBoxLayout() btm.addWidget(btn, 3) btm.addWidget(big, 2) btm.addWidget(bye, 1) box = QVBoxLayout() box.setSpacing(0) box.setContentsMargins(0, 0, 0, 0) box.addWidget(lbl, 2) box.addLayout(btm, 1) wnd.setLayout(box) wnd.setWindowFlags(Qt.FramelessWindowHint) wnd.show() app.exec()
QWidget.setWindowFlags(Qt.FramelessWindowHint)
可以移除窗體邊框。