PyQt5的快速入門
0. 寫在前面
- 本文為博主個人對自己學(xué)習(xí)
PyQt5
GUI技術(shù)的綱要式的總結(jié),主要的目的是以比較宏觀的視角再次對PyQt5
這一項技術(shù)作一次總結(jié),通過該文可以快速讓讀者建立起PyQt5
最基本的知識體系,了解PyQt5
的基本界面的設(shè)計
、信號與槽
和多線程
等知識,并簡單學(xué)會使用Qt Designer
工具來實現(xiàn)快速的Python GUI界面設(shè)計。 - 特別感謝王銘東老師在B站上發(fā)布的PyQt5快速入門視頻教程。
- 博主個人在學(xué)習(xí)的過程中全程跟敲了代碼,現(xiàn)完全開源,感興趣的同學(xué)可以在這里獲取。
1. 思維導(dǎo)圖
- 以下是博主個人在學(xué)習(xí)的過程中繪制的簡要的 “輔助讀懂PyQt5” 所需掌握的基本知識體系,需要注意的事情是,PyQt5的知識遠(yuǎn)遠(yuǎn)不止下圖中所展示出來的內(nèi)容,在實際的項目開發(fā)中,需要我們能在掌握必要的常識的基礎(chǔ)上,根據(jù)實際的項目需求,學(xué)會自己主動查找有用的資料,并最終完成自己的開發(fā)任務(wù)。
2. 第一個PyQt5的應(yīng)用程序
import sys
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('第一個PyQt5程序')
# 設(shè)置窗口背景色
w.setStyleSheet("background-color: rgb(255, 255, 255);")
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
-
如上文的代碼所示,運行后我們可以得到這樣的一個窗口:
-
from PyQt5.QtWidgets import QApplication, QWidget
,我們可以使用from <module> import ....
的形式來導(dǎo)入我們在進(jìn)行Python GUI開發(fā)中所需要的庫,這些庫通常是QtWidgets
,QtGui
和QtCore
。 -
在Python的GUI開發(fā)中,我們需要了解的是,任何一個PyQt的應(yīng)用程序都是從
app = QApplication(sys.argv)
QApplication開始的。每一個應(yīng)用程序都需要也僅僅需要一個QApplication
來管理整個應(yīng)用程序。 -
QWidget
是PyQt
中默認(rèn)的三種重要的窗口之一,我們需要根據(jù)我們具體的GUI開發(fā)的需求來選擇我們使用的基礎(chǔ)的窗口類型。這里我們使用w = QWidget()
來實例化一個QWidget
實例。實例化窗口之后,窗口本身是有很多的屬性可以被設(shè)置的,這一部分我們會在下文有適當(dāng)?shù)目偨Y(jié)并展開說說。比如,在這個樣例中我們主要是使用了w.setWindowTitle('第一個PyQt5程序')
來設(shè)置了窗口的標(biāo)題,并且使用了w.setStyleSheet("background-color: rgb(255, 255, 255);")
來設(shè)置窗口的背景色為白色。 -
通過
w.show()
我們可以讓我們所定義的窗體的實例對象展示出來。 -
最后,我們通過
app.exec_()
來讓應(yīng)用程序進(jìn)入事件循環(huán)(Event loop)
。另外,這里之所以使用的是exec_
,是為了避免與Python 2
中的exec
保留字發(fā)生沖突。
與應(yīng)用程序的每次交互(無論是按鍵、單擊鼠標(biāo)還是鼠標(biāo)移動)都會生成一個事件,該事件放置在事件隊列中。在事件循環(huán)中,在每次迭代時檢查隊列,如果找到等待事件,則將事件和控制傳遞給事件的特定事件處理程序。事件處理程序處理事件,然后將控制權(quán)傳遞回事件循環(huán)以等待更多事件。每個應(yīng)用程序只有一個正在運行的事件循環(huán)。
3. PyQt5的常用基本控件和布局
3.1 PyQt5的常用基本控件
3.1.1 按鈕控件 QPushButton
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之按鈕')
# 在窗口里面添加按鈕控件
btn = QPushButton("按鈕")
# 把按鈕綁定到對應(yīng)的窗口,等于是添加到窗口中顯示
btn.setParent(w)
# 設(shè)置窗口背景色
w.setStyleSheet("background-color: rgb(255, 255, 255);")
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 這里其實符合面向?qū)ο缶幊痰木幊踢壿?,這些UI控件本質(zhì)上都是一類一類的對象,在使用這些對象的時候,我們需要先創(chuàng)建一個對象的實例,然后再來對他們進(jìn)行操作來實現(xiàn)我們想要實現(xiàn)的功能。
- 通過
btn = QPushButton("按鈕")
,我們實現(xiàn)了在窗口里面添加按鈕控件,該按鈕控件的文本顯示為“按鈕”
。 - 但是僅僅只是創(chuàng)建了控件的實例的話,是沒有辦法在窗體上顯示出來的,還需要我們通過
btn.setParent(w)
把按鈕綁定到對應(yīng)的窗口,這樣我們的按鈕才能夠在窗口中顯示。
這里的QPushButton其實只是Button類型的控件的一個典型的代表,在PyQt5中實際可用的按鈕控件還有很多,詳細(xì)可以在
Qt Designer
中查看或者在需要的時候查閱文檔。
3.1.2 文本標(biāo)簽控件 QLabel
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之文本')
# 在窗口里面添加按鈕控件,并綁定到w窗口
btn = QPushButton("按鈕", w)
# 把按鈕綁定到對應(yīng)的窗口,等于是添加到窗口中顯示
# 設(shè)置按鈕的位置
btn.move(100, 100)
# 在窗口里面添加文本標(biāo)簽,并綁定到w窗口
label = QLabel("文本標(biāo)簽", w)
# 設(shè)置文本標(biāo)簽的位置
# label.move(100, 150)
# 設(shè)置位置與大小 (x,y)(左上角0,0;橫x,縱y) (w,h)(寬度,高度)
label.setGeometry(100, 150, 150, 50)
# 設(shè)置窗口背景色
w.setStyleSheet("background-color: rgb(255, 255, 255);")
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 除了上文說的
xxx.setParent(w)
方法,我們還可以在一開始創(chuàng)建控件的時候就把控件綁定到窗體上,這樣就簡化了代碼,如我們可以使用label = QLabel("文本標(biāo)簽", w)
實現(xiàn)在窗口里面添加文本標(biāo)簽,并直接綁定到w窗口。
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
w.resize(1000, 500)
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之QLabel展示圖片')
# 創(chuàng)建一個位圖變量
photo = QPixmap('imgs/Florian.jpg')
# 按比例縮放至1000*500
scaled_photo = photo.scaled(1000, 500, Qt.KeepAspectRatio)
# 創(chuàng)建一個標(biāo)簽控件實例
label = QLabel(w)
# 將位圖變量綁定到標(biāo)簽控件實例上
label.setPixmap(scaled_photo)
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 特別地,QLabel 不僅僅可以用來顯示文字,它還可以用來顯示圖片??梢允褂?QLabel 通過
setPixmap()
顯示圖像。這接受一個像素圖,您可以通過將圖像文件名傳遞給QPixmap
類來創(chuàng)建該像素圖。比如:widget.setPixmap(QPixmap('otje.jpg'))
這里的QLabel其實只是Display類型的控件的一個典型的代表,在PyQt5中實際可用的按鈕控件還有很多,詳細(xì)可以在
Qt Designer
中查看或者在需要的時候查閱文檔。
3.1.3 單行輸入框控件 QLineEdit
import sys
from PyQt5.QtWidgets import *
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之輸入框')
# 純文本
label = QLabel("賬號:", w)
label.setGeometry(100, 100, 50, 30)
# 文本框
edit = QLineEdit(w)
edit.setPlaceholderText("請輸入賬號")
edit.setGeometry(150, 100, 200, 30)
# 在窗口里面添加控件
btn = QPushButton("注冊", w)
btn.setGeometry(200, 150, 80, 30)
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 通常,我們可以使用
edit = QLineEdit(w)
來創(chuàng)建一個單行的輸入框,可以使用edit.setPlaceholderText("請輸入賬號")
來設(shè)置未輸入數(shù)據(jù)情況下的提示信息,如果我們想要在該輸入行中輸入密碼的話,我們也可以使用password.setEchoMode(QLineEdit.Password)
來將其設(shè)置為密文模式。
這里的QLineEdit其實只是Input類型的控件的一個典型的代表,在PyQt5中實際可用的按鈕控件還有很多,詳細(xì)可以在
Qt Designer
中查看或者在需要的時候查閱文檔。
3.1.4 A Quick Widgets Demo
- 該部分內(nèi)容轉(zhuǎn)載自PyQt5 Widgets
- 在這篇文章中更加詳細(xì)地距離了PyQt的各種奇妙的控件,有興趣的讀者可以從這里進(jìn)入,去更進(jìn)一步地閱讀和學(xué)習(xí)!
- 我們這里僅舉其中的一個比較爆炸性的例子作為展示。
import sys
from PyQt5.QtWidgets import (
QApplication,
QCheckBox,
QComboBox,
QDateEdit,
QDateTimeEdit,
QDial,
QDoubleSpinBox,
QFontComboBox,
QLabel,
QLCDNumber,
QLineEdit,
QMainWindow,
QProgressBar,
QPushButton,
QRadioButton,
QSlider,
QSpinBox,
QTimeEdit,
QVBoxLayout,
QWidget,
)
# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Widgets App")
layout = QVBoxLayout()
widgets = [
QCheckBox,
QComboBox,
QDateEdit,
QDateTimeEdit,
QDial,
QDoubleSpinBox,
QFontComboBox,
QLCDNumber,
QLabel,
QLineEdit,
QProgressBar,
QPushButton,
QRadioButton,
QSlider,
QSpinBox,
QTimeEdit,
]
for w in widgets:
layout.addWidget(w())
widget = QWidget()
widget.setLayout(layout)
# Set the central widget of the Window. Widget will expand
# to take up all the space in the window by default.
self.setCentralWidget(widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Widget | 功能 |
---|---|
QCheckbox | 多選框 |
QComboBox | 下拉列表框 |
QDateEdit | 日期編輯框 |
QDateTimeEdit | 日期及時間編輯框 |
QDial | 可旋轉(zhuǎn)表盤 |
QDoubleSpinbox | 浮點數(shù)的數(shù)字微調(diào)器 |
QFontComboBox | 字體列表 |
QLCDNumber | 簡易液晶數(shù)字顯示屏 |
QLabel | 文本標(biāo)簽,不支持交互 |
QLineEdit | 單行文本輸入框 |
QProgressBar | 進(jìn)度條 |
QPushButton | 按鈕 |
QRadioButton | 單選框 |
QSlider | 滑塊 |
QSpinBox | 整數(shù)數(shù)字微調(diào)器 |
QTimeEdit | 時間編輯框 |
3.2 PyQt5的常用基本控件的基本屬性編輯
3.2.1 調(diào)整窗口大小
import sys
from PyQt5.QtWidgets import *
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之調(diào)整窗口大小')
# 重置窗口大小
w.resize(800, 600)
# 將窗口設(shè)置在屏幕的左上角
# w.move(0, 0)
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
#pic_center)
- 在 PyQt 中,我們可以使用
w.resize(800, 600)
來對窗體或其它控件的大小屬性來進(jìn)行自定義,兩個參數(shù)分別對應(yīng)的是width和height。從上圖我們也可以清晰看出,設(shè)置后的主窗體的大小確實就是800 * 600
。
3.2.2 調(diào)整窗口位于屏幕中間
import sys
from PyQt5.QtWidgets import *
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之調(diào)整窗口于正中間位置')
# 重置窗口大小
w.resize(800, 600)
# 調(diào)整窗口在屏幕中央顯示
# 獲取我當(dāng)前屏幕桌面的中心點的位置信息
center_pointer = QDesktopWidget().availableGeometry().center()
print(center_pointer)
x = center_pointer.x()
y = center_pointer.y()
print(x, y)
# 打印窗口默認(rèn)的幾何信息(位置和大?。?/span>
print(w.frameGeometry())
print(w.frameGeometry().getRect())
print(type(w.frameGeometry().getRect()))
# 獲取窗口的默認(rèn)幾何信息
old_x, old_y, width, height = w.frameGeometry().getRect()
print(old_x, old_y, width, height)
# 基于屏幕中心點和窗口的默認(rèn)大小來調(diào)至合適的屏幕中央(自適應(yīng))
w.move(x - int(width / 2), y - int(height / 2))
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 在PyQt的坐標(biāo)系的構(gòu)建中,是以 左上角為原點,從左向右為正方向的X軸(Width),以及從上到下為正方向的Y軸(Height) 來進(jìn)行坐標(biāo)系的構(gòu)建的。
- 實現(xiàn)該效果的思路首先是,先使用
center_pointer = QDesktopWidget().availableGeometry().center()
獲取我當(dāng)前屏幕桌面的中心點的位置信息。 - 然后使用
old_x, old_y, width, height = w.frameGeometry().getRect()
獲取窗口控件的幾何屬性,主要是獲取它的width和height。 - 這時候,就只需要用我們所獲取到的屏幕的中心點的坐標(biāo)來分別減去一般的窗體的高度和寬度,這樣我們就可以獲得窗體相對于屏幕的坐標(biāo)系中,如果要讓窗體顯示在屏幕正中的情況下,窗體左上角定位點所應(yīng)該在的具體的位置坐標(biāo)了,這時候再使用
w.move(x - int(width / 2), y - int(height / 2))
重新定位即可。
3.2.3 設(shè)置窗體圖標(biāo)
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之設(shè)置窗口圖標(biāo)')
# 設(shè)置窗口圖標(biāo)
w.setWindowIcon(QIcon('imgs/clock.png'))
# PyQt5隱藏上方的標(biāo)簽欄
# w.setWindowFlag(Qt.FramelessWindowHint)
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 在PyQt中我們可以使用
w.setWindowIcon(QIcon('imgs/clock.png'))
來給我們自己的窗口添加酷炫的Icon。
3.2.4 隱藏窗口標(biāo)簽欄
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget類
w = QWidget()
# 設(shè)置窗口標(biāo)題
w.setWindowTitle('PyQt5程序之隱藏窗口標(biāo)簽欄')
# PyQt5隱藏上方的標(biāo)簽欄
w.setWindowFlag(Qt.FramelessWindowHint)
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
# 設(shè)置窗口顯示
w.show()
# 程序進(jìn)行循環(huán)等待狀態(tài)
app.exec_()
- 在PyQt中我們可以使用
w.setWindowFlag(Qt.FramelessWindowHint)
來隱藏上方的標(biāo)簽欄,這樣我們就相當(dāng)于獲得了一個完全空白的窗口面板,我們可以在上面來進(jìn)行完全自定義的具有個性的GUI界面的設(shè)計與實現(xiàn)。
3.3 PyQt5的常用布局
3.3.1 盒子布局垂直版QVBoxLayout
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 設(shè)置大小
self.resize(300, 300)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之垂直布局')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 垂直布局(限制)
layout = QVBoxLayout()
# 作用是在布局器中增加一個伸縮量,里面的參數(shù)表示QSpacerItem的個數(shù),默認(rèn)值為零
# 會將你放在layout中的空間壓縮成默認(rèn)的大小
# 下面的空白區(qū)域的比例為:1:1:1:2
# 加了一個伸縮器(可以理解為一個彈簧)
layout.addStretch(1)
# 設(shè)置布局的方向
layout.setDirection(QBoxLayout.TopToBottom)
# 設(shè)置布局的間距
layout.setContentsMargins(10, 10, 10, 10)
# 設(shè)置布局的內(nèi)邊距
layout.setSpacing(10)
# 設(shè)置布局
self.setLayout(layout)
# 按鈕1
btn1 = QPushButton("按鈕1")
# 添加到布局器中
layout.addWidget(btn1, Qt.AlignmentFlag.AlignTop)
# 把按鈕添加到布局中
layout.addWidget(btn1)
# 加了一個伸縮器(可以理解為一個彈簧)
layout.addStretch(1)
# 按鈕2
btn2 = QPushButton("按鈕2")
# 把按鈕添加到布局中
layout.addWidget(btn2)
# 加了一個伸縮器(可以理解為一個彈簧)
layout.addStretch(1)
# 按鈕3
btn3 = QPushButton("按鈕3")
# 把按鈕添加到布局中
layout.addWidget(btn3)
# 加了一個伸縮器(可以理解為一個彈簧)
layout.addStretch(2)
# 讓當(dāng)前的窗口使用這個排列的布局器
self.setLayout(layout)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 使用布局管理器可以自動調(diào)整窗口部件的大小和位置,以適應(yīng)窗口大小的改變。當(dāng)你改變窗口的大小時,布局管理器會自動重新計算窗口部件的位置和大小,使得窗口部件能夠適應(yīng)新的窗口大小,避免了手動調(diào)整窗口部件大小和位置的繁瑣工作。
- 在使用布局管理器之前,我們需要使用
layout = QVBoxLayout()
創(chuàng)建一個布局對象。 - 用布局來管理控件的方式不同于直接把控件綁定到窗口的形式,我們在創(chuàng)建控件的時候也不需要默認(rèn)指定
Parent
,取而代之的是layout.addWidget(btn1)
把想要添加的控件添加到布局對象中。 - 為了控制好所添加入布局管理器的控件與控件之前的控件距離,我們可以采用
layout.addStretch(2)
的方式來自定義控件與控件之前的距離,里面的參數(shù)是權(quán)重的比例,比如在上圖中我們在按鈕1、2、3的前后和中間分別設(shè)置了1:1:1:2
的權(quán)重的伸縮器,從最后的頁面中我們可以看到從上到下的所有的間隙的大小分布的比例差不多就是1:1:1:2
。 - 最后,布局無法直接被顯示,需要我們使用
self.setLayout(layout)
讓當(dāng)前的窗口使用我們定義好的布局器。
3.3.2 盒子布局水平版QHBoxLayout
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(400, 400)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之水平布局')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 垂直布局
# outer_container = QVBoxLayout()
outer_container = QHBoxLayout()
# 垂直布局器中有兩個組,一個是愛好組,另一個是性別組
hobby_group = QGroupBox("愛好")
# 創(chuàng)建一個垂直布局器
v_inner_layout = QVBoxLayout()
# 在愛好組中加入可選的愛好
choice1 = QRadioButton("文明6")
choice2 = QRadioButton("云頂之弈")
choice3 = QRadioButton("畢業(yè)設(shè)計")
# 把組件添加到布局器中
v_inner_layout.addWidget(choice1)
v_inner_layout.addWidget(choice2)
v_inner_layout.addWidget(choice3)
# 將布局器綁定到組
hobby_group.setLayout(v_inner_layout)
# 把當(dāng)前的愛好組加入到垂直布局中
outer_container.addWidget(hobby_group)
gender_group = QGroupBox("性別")
# 創(chuàng)建一個水平布局器
h_inner_layout = QHBoxLayout()
# 在愛好組中加入可選的愛好
boy = QRadioButton("男")
girl = QRadioButton("女")
# 把組件添加到布局器中
h_inner_layout.addWidget(boy)
h_inner_layout.addWidget(girl)
# 將布局器綁定到組
gender_group.setLayout(h_inner_layout)
# 把當(dāng)前的性別組加入到垂直布局中
outer_container.addWidget(gender_group)
# 讓當(dāng)前的窗口使用布局器
self.setLayout(outer_container)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 當(dāng)最頂層布局為垂直布局:
- 當(dāng)最頂層布局為水平布局:
- 從技術(shù)角度來說,水平布局的布局管理器和垂直布局的布局管理器是類似的不同的僅僅是從空間上來看,它約束的空間關(guān)系是一種水平的布局。都是先使用
QVBoxLayout()
或者QHBoxLayout()
創(chuàng)建一個垂直布局器或者水平布局器,然后再使用addWidget()
把控件添加到布局器上,最后使用setLayout()
讓布局綁定窗口或者控件。 - 然而這個案例中所不同的地方是,布局是可以嵌套的。當(dāng)然,布局是無法直接嵌套在另外一層布局上的,所以就需要中間的容器。在當(dāng)前的例子中,內(nèi)部的愛好和性別的兩個組控件,就是這中間的容器,
QGroupBox()
對外層的容器充當(dāng)控件,又作為內(nèi)層的布局的容器,分別添加了垂直方向和水平方向的布局管理器。
3.3.3 網(wǎng)格布局QGridLayout
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(300, 300)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之QGridLayout——計算器')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 最外層是一個垂直布局布局
outer_container = QVBoxLayout()
# 創(chuàng)建一個輸入框
input_box = QLineEdit()
input_box.setPlaceholderText('請輸入內(nèi)容')
# 把輸入框和按鈕放到布局中
outer_container.addWidget(input_box)
# 創(chuàng)建計算器的網(wǎng)絡(luò)布局
grid = QGridLayout()
# 計算器網(wǎng)格布局的數(shù)據(jù)準(zhǔn)備(這里刻意使用一種類似json格式的鍵值對的數(shù)據(jù)形式)
data = {
0: ['7', '8', '9', '+', '('],
1: ['4', '5', '6', '-', ')'],
2: ['1', '2', '3', '*', '<-'],
3: ['0', '.', '=', '/', 'C']
}
# 把網(wǎng)格布局添加到外層的垂直布局中
outer_container.addLayout(grid)
for key_row, numbers in data.items():
for index_column, value in enumerate(numbers):
btn = QPushButton(value)
grid.addWidget(btn, key_row, index_column)
# 讓當(dāng)前的窗口使用布局器
self.setLayout(outer_container)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 這是一個計算器的布局樣例。其中的按鍵是通過GridLayout來實現(xiàn)的。創(chuàng)建網(wǎng)格布局的方式是
grid = QGridLayout()
和其它的布局方式不同的地方主要是,對于網(wǎng)格布局,我們在添加控件addWidget()
的時候,還需要指定當(dāng)前的控件填在網(wǎng)格布局中的行列坐標(biāo)。坐標(biāo)是從0開始計數(shù)。 - 當(dāng)然,有時候我們也會遇到可能一個控件需要跨多個網(wǎng)格的情況,這時候就需要在添加控件的時候再額外指定它所跨的行的數(shù)量和所跨的列的數(shù)量。 比如
layout.addWidget(button, row, col, 1, 2)
,它表示當(dāng)前的按鈕會占據(jù)1行2列。
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QPushButton
class Calculator(QWidget):
def __init__(self):
super().__init__()
# 創(chuàng)建網(wǎng)格布局
layout = QGridLayout(self)
# 創(chuàng)建按鈕并添加到布局中
buttons = [
'7', '8', '9', '/',
'4', '5', '6', '*',
'1', '2', '3', '-',
'0', '=', '+'
]
row = 0
col = 0
for btnText in buttons:
if btnText == '0':
# 為數(shù)字“0”的按鈕設(shè)置 columnSpan 為 2
button = QPushButton(btnText, self)
layout.addWidget(button, row, col, 1, 2) # 占據(jù)1行2列
col += 2 # 跳過下一列
else:
button = QPushButton(btnText, self)
layout.addWidget(button, row, col)
col += 1
# 換行條件:當(dāng)?shù)竭_(dá)每行的最后一個按鈕時
if col == 4 and btnText != '0':
row += 1
col = 0
# 設(shè)置窗口的基本屬性
self.setLayout(layout)
self.setWindowTitle('計算器示例')
if __name__ == '__main__':
app = QApplication(sys.argv)
calc = Calculator()
calc.show()
sys.exit(app.exec_())
3.3.4 表單布局QFormLayout
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(400, 300)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之QFormLayout')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 創(chuàng)建一個垂直布局作為窗口的主要布局
mainLayout = QVBoxLayout()
# 創(chuàng)建一個表單布局(本質(zhì)上就是一行一行的部分)
formLayout = QFormLayout()
# 創(chuàng)建標(biāo)簽和對應(yīng)的字段控件
# 姓名行及其輸入框
nameLabel = QLabel('姓名:')
nameLineEdit = QLineEdit()
formLayout.addRow(nameLabel, nameLineEdit)
# 電子郵箱行及其輸入框
emailLabel = QLabel('電子郵箱:')
emailLineEdit = QLineEdit()
formLayout.addRow(emailLabel, emailLineEdit)
# 將表單布局添加到主布局中
mainLayout.addLayout(formLayout)
# 創(chuàng)建一個提交按鈕,并添加到主布局中
submitButton = QPushButton('提交')
mainLayout.addWidget(submitButton)
# 設(shè)置窗口的主布局
self.setLayout(mainLayout)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 表單布局最特別的地方在于表單布局的控件的添加不是我們所熟悉的
addWidget()
它不是一個一個控件來添加的,它是使用諸如formLayout.addRow(nameLabel, nameLineEdit)
的形式來一行一行來進(jìn)行控件的添加的。QFormLayout被設(shè)計用來創(chuàng)建一個兩列的表單布局,其中左列通常包含標(biāo)簽(QLabel),右列包含與之相關(guān)的控件(如QLineEdit、QCheckBox、QPushButton等)。 - 但是,這并不意味著我們必須遵循這種標(biāo)簽-控件的模式;我們可以根據(jù)需要添加任何類型的控件到左列或右列。例如,在同一行的左列添加一個QLabel,而在右列添加一個QPushButton?;蛘?,你也可以在左列和右列都添加QPushButton或其他類型的控件。QFormLayout會自動處理控件的對齊和間距,使得布局看起來整潔且有序。
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QFormLayout, QLabel, QLineEdit, QPushButton
class FormLayoutExample(QWidget):
def __init__(self):
super().__init__()
# 創(chuàng)建表單布局
layout = QFormLayout()
# 添加控件到布局中
layout.addRow("Name:", QLineEdit())
layout.addRow("Age:", QLineEdit())
# 在同一行添加不同類型的控件
button1 = QPushButton("Submit")
button2 = QPushButton("Cancel")
layout.addRow(button1, button2)
# 設(shè)置窗口的基本屬性
self.resize(400, 100)
self.setLayout(layout)
self.setWindowTitle('QFormLayout 示例')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = FormLayoutExample()
ex.show()
sys.exit(app.exec_())
3.3.5 棧布局QStackLayout
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Window1(QWidget):
def __init__(self):
super().__init__()
label = QLabel("這里是窗口1", self)
self.setStyleSheet("background-color: red")
# 設(shè)置標(biāo)簽內(nèi)的字體的屬性
font = QFont()
font.setPointSize(20)
label.setFont(font)
class Window2(QWidget):
def __init__(self):
super().__init__()
label = QLabel("這里是窗口2", self)
self.setStyleSheet("background-color: green")
# 設(shè)置標(biāo)簽內(nèi)的字體的屬性
font = QFont()
font.setPointSize(20)
label.setFont(font)
class MyWindow(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.windows_stack = None
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(400, 400)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之QStackLayout')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 垂直布局
outer_container = QVBoxLayout()
# 展示區(qū)控件
display = QWidget()
display.setStyleSheet("background-color:grey")
# 為展示區(qū)控件綁定抽屜布局(加上self的含義是讓它變成一個類變量)
self.windows_stack = QStackedLayout()
# 創(chuàng)建兩個新的窗口對象
window1 = Window1()
window2 = Window2()
self.windows_stack.addWidget(window1)
self.windows_stack.addWidget(window2)
# 將抽屜布局綁定到展示控件上
display.setLayout(self.windows_stack)
# 按鈕控件
btn1 = QPushButton("切換到窗口一")
btn2 = QPushButton("切換到窗口二")
# 為按鈕設(shè)置單擊事件
btn1.clicked.connect(self.show_window1)
btn2.clicked.connect(self.show_window2)
# 綁定控件
outer_container.addWidget(display)
outer_container.addWidget(btn1)
outer_container.addWidget(btn2)
# 讓當(dāng)前的窗口使用布局器
self.setLayout(outer_container)
def show_window1(self):
self.windows_stack.setCurrentIndex(0)
def show_window2(self):
self.windows_stack.setCurrentIndex(1)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
-
這是點擊窗口二按鈕的顯示情況:
-
這是點擊窗口一按鈕的顯示情況(也是默認(rèn)的情況):
- 其實本例子在使用窗口切換的按鈕中已經(jīng)用到和后面要講解的
信號和槽
相關(guān)的內(nèi)容了,這里暫時先按下不表,作為一個引子拋磚引玉,大家可以先對信號和槽
的使用有一個大概的印象。 - 這里有一個具體的實現(xiàn)細(xì)節(jié)是
display = QWidget()
,棧布局本身是作為一種布局存在,而不是作為一種UI控件存在,所以要使得棧布局能夠正常發(fā)揮自己的作用,需要給他找一個控件介質(zhì),這里用的是QWidget()
窗口控件。 - 棧布局也叫抽屜布局,“抽屜”的這個喻體很象形地體現(xiàn)出這種布局的特點,它就像是一個多層的抽屜,不同層的抽屜可以放、不同的“窗口”,但是當(dāng)們從上往下看的時候,同一個時候只能看到一個拉開的抽屜,也即只能看到一個窗口。使用這種“抽屜”布局能夠?qū)崿F(xiàn)同一空間區(qū)域的多樣化的應(yīng)用。一個形象的例子是我們電腦上的微信的窗口,左側(cè)是不同的好友,而右側(cè)則是我們和好友的聊天的頁面,我們點擊不同的好友的時候,右側(cè)的區(qū)域會轉(zhuǎn)變成不同的好友相關(guān)的聊天區(qū),但是空間依然還是那一塊空間。在棧布局中,不同的顯示窗口的顯示主要用
setCurrentIndex(0)
來實現(xiàn),通過指定想要展示的窗體的下標(biāo),來實現(xiàn)展示的窗體的切換。下標(biāo)從0開始計數(shù)。
3.4 PyQt5常用的三種窗口
3.4.1 PyQt5之QWidget
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyQWidget(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(700, 400)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之QWidget')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 創(chuàng)建一個標(biāo)簽
label = QLabel("我是PyQt5的三種重要窗口之一,"
"\n是QWidget窗口的示例~"
"\n我的特點是自定義的程度高。", self)
# 調(diào)整label的字體大小為30,顏色為green
label.setFont(QFont("微軟雅黑", 25, QFont.Bold))
label.setStyleSheet("color: green")
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyQWidget
w = MyQWidget()
w.show()
app.exec_()
-
QWidget
窗口的使用方法之一是直接讓我們所創(chuàng)建的自定義的窗口類繼承它,這樣我們所自定義的窗口就是QWidget
的子類,就能夠獲得這種窗口的特點。在PyQt
中,QWidget
是所有用戶界面對象的基類。
3.4.2 PyQt5之QMainWindow
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyQMainWindow(QMainWindow):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(700, 400)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之QMainWindow')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 創(chuàng)建菜單和動作
file_menu = QMenu('文件', self)
new_action = QAction('新建', self)
open_action = QAction('打開', self)
save_action = QAction('保存', self)
exit_action = QAction('退出', self)
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addSeparator() # 創(chuàng)建了一個分割的橫線
file_menu.addAction(exit_action)
# 創(chuàng)建菜單欄并添加菜單
menu_bar = QMenuBar(self)
menu_bar.addMenu(file_menu)
self.setMenuBar(menu_bar)
# 創(chuàng)建工具欄
tool_bar = self.addToolBar('工具')
edit_action = QAction('編輯', self)
tool_bar.addAction(edit_action)
# 設(shè)置中心部件
central_MainWidget = QWidget(self) # 使得中心區(qū)域可以容納多個以上的組件。
v_layout = QVBoxLayout()
# 創(chuàng)建一個文本編輯框
text_edit = QTextEdit(self)
# 創(chuàng)建一個標(biāo)簽
label = QLabel("我是PyQt5的三種重要窗口之一,"
"\n是QMainWindow窗口的示例~"
"\n我的特點是自帶菜單欄,工具欄,狀態(tài)欄和中心內(nèi)容區(qū)。", self)
# 調(diào)整label的字體大小為30,顏色為green
label.setFont(QFont("微軟雅黑", 20, QFont.Bold))
label.setStyleSheet("color: green")
# 創(chuàng)建一個按鈕,點擊時更新狀態(tài)欄信息
self.button = QPushButton('點擊更新狀態(tài)欄', self)
# 將控件綁定到布局
v_layout.addWidget(text_edit)
v_layout.addWidget(label)
v_layout.addWidget(self.button)
# 將布局綁定到主控件
central_MainWidget.setLayout(v_layout)
# 將主控件綁定到QMainWindow的中心內(nèi)容區(qū)
self.setCentralWidget(central_MainWidget)
# 創(chuàng)建狀態(tài)欄
self.status_bar = QStatusBar(self)
self.status_bar.showMessage('歡迎使用應(yīng)用程序')
# 設(shè)置狀態(tài)欄的永久顯示項
permanent_label = QLabel('永久信息:當(dāng)前模式')
self.status_bar.addPermanentWidget(permanent_label)
self.setStatusBar(self.status_bar)
# 連接動作的信號和槽
# (連接上了事件,有事件相關(guān)的控件都加了self變成了當(dāng)前類的屬性:類變量,
# 這樣才可以在當(dāng)前類的其它方法中直接使用。)
exit_action.triggered.connect(self.close)
self.button.clicked.connect(self.update_statusbar)
"""
* 這是中心內(nèi)容區(qū)的按鈕點擊了之后用來更新狀態(tài)欄的信息的方法。
"""
def update_statusbar(self):
# 更新狀態(tài)欄的臨時信息
self.status_bar.showMessage('新的風(fēng)暴已經(jīng)出現(xiàn)!', 5000) # 5秒后消失
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QMainWindow子類——MyQMainWindow
w = MyQMainWindow()
w.show()
app.exec_()
-
QMainWindow
是一個提供主應(yīng)用程序窗口的類,通常包含一個菜單欄、工具欄、狀態(tài)欄和一個中心部件(通常是一個 QWidget 或其子類)。QMainWindow
是用于構(gòu)建復(fù)雜應(yīng)用程序的主要窗口類,因為它提供了構(gòu)建多組件、多面板界面的結(jié)構(gòu)。 -
這是展開了頂部的菜單欄的
QMainWindow
-
一般使用
menu_bar = QMenuBar(self)
來創(chuàng)建一個頂部菜單欄,并綁定到當(dāng)前的QMainWindow
。 -
菜單欄中可以容納多個菜單,一般使用
file_menu = QMenu('文件', self)
來創(chuàng)建一個菜單。 -
菜單中可以有多個菜單項,在
PyQt
中我們稱為QAction
,比如我們可以用new_action = QAction('新建', self)
來創(chuàng)建一個名為“新建”的QAction
,QAction是用來表示窗口上的動作或事件的。它包含了多種信息,如圖標(biāo)、菜單文字、快捷鍵、狀態(tài)欄文字和浮動幫助等。它可以和信號一樣可以通過連接槽函數(shù)來實現(xiàn)特定的功能。 -
對于創(chuàng)建好的
QAction
我們需要file_menu.addAction(new_action)
將其添加到菜單中。 -
菜單中除了可以添加事件之外,還可以添加
file_menu.addSeparator()
以創(chuàng)建一個分割的橫線。 -
當(dāng)然,我們所創(chuàng)建的菜單也需要添加到菜單欄中:
menu_bar.addMenu(file_menu)
-
并將我們創(chuàng)建的菜單欄綁定到當(dāng)前的
QMainWindow
:self.setMenuBar(menu_bar)
。 -
當(dāng)前選中的這一條就是工具欄
-
QAction
的一個關(guān)鍵特性是其能夠根據(jù)添加的位置改變自己的顯示方式。例如,如果將其添加到菜單中,它會顯示為一個菜單項; 而如果添加到工具欄,則會顯示為一個按鈕。 這種靈活性使得QAction成為在Qt中創(chuàng)建交互性窗口界面的重要工具。 -
QAction
可以直接添加到工具欄上。 -
更新了狀態(tài)欄信息的
QMainWindow
-
使用
self.status_bar = QStatusBar(self)
創(chuàng)建一個狀態(tài)欄。 -
使用
self.status_bar.showMessage('歡迎使用應(yīng)用程序')
在左下腳短暫顯示狀態(tài)信息。 -
也可以自己創(chuàng)建一個標(biāo)簽
permanent_label = QLabel('永久信息:當(dāng)前模式')
,然后把這個標(biāo)簽設(shè)置為永久顯示的信息:self.status_bar.addPermanentWidget(permanent_label)
。 -
最后記得,把狀態(tài)欄綁定到當(dāng)前的主窗口:
self.setStatusBar(self.status_bar)
。 -
當(dāng)前選中的這一部分就是中心部件,在這個中心部件中,我們放置了一個
QTextEdit
、一個QLabel
和一個QPushBotton
,用垂直布局將這三個控件整合起來。 -
值得額外注意的是,對于
QMainWindow
而言,它的中心部件其實可以看成是一個獨立的窗口,就像是我們前面學(xué)習(xí)的QWidget
事實上,它也確實是這樣做的,用一個QWidget
的實例來作為容納的介質(zhì),所有其它的控件或者布局都放在這個QWidget
中,最后再self.setCentralWidget(central_MainWidget)
將主控件綁定到QMainWindow
的中心內(nèi)容區(qū)即可。
3.4.3 PyQt5之QDialog
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class CustomDialog(QDialog):
def __init__(self):
super().__init__()
# 設(shè)置大小
self.resize(200, 200)
self.setWindowTitle('自定義對話框')
# 創(chuàng)建布局
layout = QVBoxLayout()
# 創(chuàng)建標(biāo)簽
self.label = QLabel('請輸入一些文本:', self)
layout.addWidget(self.label)
# 創(chuàng)建按鈕
self.ok_button = QPushButton('確定', self)
self.ok_button.clicked.connect(self.accept) # 點擊按鈕時接受對話框
layout.addWidget(self.ok_button)
# 設(shè)置對話框的布局
self.setLayout(layout)
def get_text(self):
# 此處僅為示例,實際上你可能需要從某個輸入框獲取文本
return "示例文本" # 假設(shè)這是用戶輸入的文本
class MyQMainWindow(QMainWindow):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(400, 400)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之QDialog')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 添加一個按鈕用于打開對話框
self.open_dialog_button = QPushButton('打開對話框', self)
self.open_dialog_button.clicked.connect(self.show_dialog)
self.setCentralWidget(self.open_dialog_button)
def show_dialog(self):
# 創(chuàng)建并顯示自定義對話框
dialog = CustomDialog()
if dialog.exec_(): # 執(zhí)行對話框,如果用戶點擊了確定按鈕則返回True
text = dialog.get_text() # 獲取用戶輸入的文本
print(f"用戶輸入的文本是: {text}")
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QMainWindow子類——MyQMainWindow
w = MyQMainWindow()
w.show()
app.exec_()
- 這是主界面:
- 點擊
打開對話框
后,彈出正式的QDialog
-
QDialog
在PyQt
中是一個通用的對話框類,用于創(chuàng)建模態(tài)或非模態(tài)對話框窗口。模態(tài)對話框會阻止用戶與其他窗口交互,直到對話框被關(guān)閉;非模態(tài)對話框則允許用戶同時與多個窗口交互。在我們這里的例子中,我們創(chuàng)建的是一個模態(tài)對話框。
4. PyQt5的信號和槽
4.1 信號與槽之接收信號
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(500, 300)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之信號和槽1——接收信號')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 創(chuàng)建了一個按鈕
btn = QPushButton("點我點我!", self)
# 設(shè)置窗口的寬度和高度
btn.setGeometry(200, 200, 100, 30)
# 為按鈕的單擊信號綁定對應(yīng)的槽函數(shù)
btn.clicked.connect(self.on_btn_clicked)
def on_btn_clicked(self, arg):
print('點我點我!', arg)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 這是一個用來演示信號與槽的最基本的交互效果的一個示例,在示例中,我們通過多次點擊窗體中的“點我點我!”的按鈕,可以看到在控制臺輸出了“點我點我!”的字樣。
- 在上述的代碼中,主要體現(xiàn)
信號與槽
的相關(guān)知識的代碼是btn.clicked.connect(self.on_btn_clicked)
,這是將一個按鈕控件btn的單擊信號clicked
與我們定義的類函數(shù)(本質(zhì)上是槽函數(shù),是我們寫的對于btn
按鈕的單擊事件的響應(yīng)函數(shù))連接起來。一般來說,響應(yīng)函數(shù)(槽函數(shù))我們都會寫成是窗口類的類方法。 - 至于為什么在“點我點我!”字段后面害有一個
False
的原因是對于QPushButton
的clicked
信號,它并不傳遞任何數(shù)據(jù),所以通常不需要為槽函數(shù)定義參數(shù)。但是我們其實定義了一個參數(shù)arg
,所以PyQt
會自動傳入一個默認(rèn)值(對于沒有數(shù)據(jù)的信號,通常是False
)。
4.2 信號與槽之自定義信號
import sys
import time
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
# 自定義一個信號,自定義的信號只可以放在類變量這里
# 后面的str表示該信號將會傳遞一個str類型的數(shù)據(jù)變量
my_signal = pyqtSignal(str)
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(500, 300)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之信號和槽2——自定義信號')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 創(chuàng)建了一個按鈕
btn = QPushButton("測試自定義信號", self)
# 設(shè)置窗口的寬度和高度
btn.setGeometry(200, 200, 100, 30)
# 為按鈕的單擊信號綁定對應(yīng)的槽函數(shù)
btn.clicked.connect(self.on_btn_clicked)
# 為自定義的信號綁定他對應(yīng)的槽函數(shù)
self.my_signal.connect(self.find_error)
def on_btn_clicked(self):
for index, ip in enumerate(["192.168.21.%d" % x for x in range(1, 255)]):
print(f"正在檢查:{ip}...", end="")
if index % 5 == 0:
self.my_signal.emit("【發(fā)現(xiàn)錯誤!!】") # 相當(dāng)于啊調(diào)用了自定義信號my_signal的槽函數(shù)
else:
self.my_signal.emit("")
# 延時0.01s
time.sleep(0.01)
def find_error(self, msg):
print(msg)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 在上一個例子中,我們所使用的信號是按鈕控件自帶的
clicked
單擊信號,但是當(dāng)我們需要完成更加復(fù)雜的用戶交互的時候,有些時候我們所需要的信號并不一定有現(xiàn)成的,這時候就需要我們進(jìn)行信號的自定義。 - 我們可以使用形如
my_signal = pyqtSignal(str)
來自定義一個信號,其中后面的str
表示該信號將會傳遞一個str
類型的數(shù)據(jù)變量。當(dāng)我們需要傳入的參數(shù)很多的時候,一般我們也是只寫一個str
,而具體的字符串用JSON格式的字符串
來封裝。 - 和預(yù)定義好的信號不同的是,對于我們自定義的信號,我們得使用
self.my_signal.emit("【發(fā)現(xiàn)錯誤?。 ?)
,在代碼中主動進(jìn)行信號的發(fā)射來調(diào)用了自定義信號my_signal的槽函數(shù)。 - 當(dāng)然,相同的是,自定義的信號和預(yù)定義的信號一樣,都需要我們?yōu)樾盘栠B接上相對應(yīng)的響應(yīng)函數(shù):
self.my_signal.connect(self.find_error)
。
4.3 信號與槽之實例練習(xí)
import sys
import time
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
# 自定義一個信號,自定義的信號只可以放在類變量這里
# 后面的str表示該信號將會傳遞一個str類型的數(shù)據(jù)變量
my_signal = pyqtSignal(str)
# 用于更新顯示框處的信息的文本變量
info = ""
def __init__(self):
# 切記一定要調(diào)用父類的__init__方法,因為它里面有很多對UI控件的初始化操作
super().__init__()
# 把對當(dāng)前的窗口控件的具體的UI布局寫到有個專門的方法中,以免初始化的方法內(nèi)容太臃腫
self.init_ui()
def init_ui(self):
# 設(shè)置大小
self.resize(500, 350)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('PyQt5程序之信號和槽3——練習(xí)')
# 設(shè)置窗口圖標(biāo)
self.setWindowIcon(QIcon('imgs/clock.png'))
# 創(chuàng)建了一個多行文本框
self.text = QTextEdit(self)
# 設(shè)置 QTextEdit 為只讀
self.text.setReadOnly(True)
# 設(shè)置字體大小為20
font = QFont()
font.setPointSize(10)
self.text.setFont(font)
# 設(shè)置多行文本框的窗口的寬度和高度
self.text.setGeometry(50, 20, 400, 250)
# 創(chuàng)建了一個按鈕
btn = QPushButton("測試自定義信號", self)
# 設(shè)置窗口的寬度和高度
btn.setGeometry(200, 280, 100, 30)
# 為按鈕的單擊信號綁定對應(yīng)的槽函數(shù)
btn.clicked.connect(self.on_btn_clicked)
# 為自定義的信號綁定他對應(yīng)的槽函數(shù)
self.my_signal.connect(self.find_error)
def on_btn_clicked(self):
# 每次啟動前先清空輸入框
self.info = ""
self.text.setText("")
for index, ip in enumerate(["192.168.21.%d" % x for x in range(1, 255)]):
signal_line = "模擬,正在檢查:" + ip + "..."
if index % 5 == 0:
self.my_signal.emit(signal_line + "【發(fā)現(xiàn)錯誤!!】") # 相當(dāng)于啊調(diào)用了自定義信號my_signal的槽函數(shù)
else:
self.my_signal.emit(signal_line)
# 延時0.01s
time.sleep(0.01)
def find_error(self, msg):
self.info += msg + '\n'
self.text.setText(self.info)
# self.text.append(msg+'\n')
print(msg)
if __name__ == '__main__':
# 實例化QApplication類
# sys.argv是命令行的參數(shù)
app = QApplication(sys.argv)
# 實例化QWidget子類——MyWindow
w = MyWindow()
w.show()
app.exec_()
- 在實例練習(xí)中,和上一個實例不一樣的地方是,除了僅僅是在控制行中輸出內(nèi)容之外,我們還嘗試把內(nèi)容輸出到窗口的展示區(qū)。
- 相比于在上一個實例中我們在單擊事件的響應(yīng)函數(shù)中既傳參又進(jìn)行具體的操作不同,在這個實例中,我們在單擊事件的響應(yīng)函數(shù)中不執(zhí)行操作,只通過信號發(fā)射傳遞參數(shù)。而所有的打印和顯示到UI上的操作我們都留到自定義信號的響應(yīng)函數(shù)中才去具體執(zhí)行。
- 但是,依然出現(xiàn)的一個問題是,UI的文本的更新并沒有如我們設(shè)想的如控制行的輸出區(qū)一樣一行一行地輸出和更新,相反他需要等控制行中的所有的文本都打印完畢之后,他才會一次性把所有的文本更新到UI展示區(qū),而會產(chǎn)生這樣的現(xiàn)象的問題是因為這些所有的操作都是在主線程中執(zhí)行的,所以UI的更新會阻塞等待控制行的操作先處理完之后才一起更新。
- 而如果想要實現(xiàn)后臺操作和UI更新能夠同步進(jìn)行的話,就需要我們學(xué)會使用多線程技術(shù)。
5. PyQt5的多線程
5.1 PyQt5多線程基礎(chǔ)語法實例
"""
動態(tài)加載ui文件
"""
import sys
import time
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import *
from PyQt5 import uic
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.myThread = None
self.ui = None
self.ui_thread_btn = None
self.QThread_btn = None
self.text_edit = None
self.init_ui()
def init_ui(self):
self.ui = uic.loadUi("ui/QThread-1.ui")
self.ui_thread_btn = self.ui.pushButton
self.QThread_btn = self.ui.pushButton_2
self.text_edit = self.ui.textEdit
# 給登錄按鈕綁定信號和槽函數(shù)
self.ui_thread_btn.clicked.connect(self.click1)
# 給忘記密碼按鈕綁定信號和槽函數(shù)
self.QThread_btn.clicked.connect(self.click2)
def click1(self):
for i in range(5):
time.sleep(1)
print(f"正在使用UI線程,......{i+1}")
def click2(self):
# 這里的子線程必須要寫成self的屬性??!否則無法實現(xiàn)異步的效果!
# 因為我們并不希望myThread線程在click2函數(shù)運行結(jié)束的時候就被銷毀,我們希望它可以根據(jù)它的需求繼續(xù)留存著。
self.myThread = MyQThread()
self.myThread.start()
class MyQThread(QThread):
def __init__(self):
super().__init__()
def run(self):
for i in range(5):
time.sleep(1)
print(f"正在使用子線程,......{i+1}")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
# 展示窗口
# w.show() # 在這種情況下這是一個錯誤示范
w.ui.show() # 這才是正確的操作
sys.exit(app.exec_())
- 當(dāng)我點擊的是左邊的按鈕的時候,這時候是沒有使用多線程的情況,在命令行中輸出UI線程的時候,我是無法同時對輸入行內(nèi)的數(shù)字進(jìn)行編輯的。
- 而當(dāng)我點擊的是右邊的按鈕的時候,這時候是使用多線程的情況,在命令行中輸出子線程的時候,我可以同時對輸入行內(nèi)的數(shù)字進(jìn)行編輯。這就達(dá)到了我們使用多線程技術(shù)來實現(xiàn)對操作和UI更新的分離的效果。
- 在
PyQt
中使用多線程的方法并不難,主要是繼承PyQt
中本身自帶的QThread
類,重寫run
方法,在run
方法中書寫具體的業(yè)務(wù)邏輯即可。在使用的時候,只需要在對應(yīng)的響應(yīng)的槽函數(shù)中實例化一個線程實例,然后再啟動線程的start()
方法來運行run
方法就行。 - 這里使用的UI是使用Qt Designer進(jìn)行設(shè)計的,至于具體要如何使用Qt Desiner來進(jìn)行GUI的設(shè)計,以及對于已經(jīng)設(shè)計好的UI文件,我們要如何集成到Python的GUI項目中,在下一部分我們會具體展開講解。
6. PyQt5的界面設(shè)計神器——Qt Designer快速入門
6.1 Qt Designer界面介紹
文章來源:http://www.zghlxwxcb.cn/news/detail-851784.html
- 在進(jìn)入
Qt Designer
的時候,程序首先會讓我們選擇窗體,這里面一共有5種可以選擇的窗體,但是其實本質(zhì)上是三大類窗體,就是我們在上面所介紹的QWidget
、QMainWindow
和QDialog
。 - 在選擇窗體的類型之后,我們就得到一個帶有點陣的可以用于直接設(shè)計的GUI界面,其中左側(cè)有一系列的可以通過直接拖拽來移動到窗體中的UI控件和布局。
- 每當(dāng)我們拖拽控件到窗體上的時候,對象查看器中,就會多出來一個UI控件的對象。
- 當(dāng)我們選中一個UI對象的時候,屬性編輯器中就會顯示出當(dāng)前的對象的一些基本的屬性,我們可以通過屬性編輯器來直接對屬性進(jìn)行編輯。
- 我們也可以使用
Qt Designer
來進(jìn)行簡單的信號和槽的編輯和設(shè)置,但是這里面并不提供自定義的槽函數(shù)的實現(xiàn),所以對于Qt Designer
來說,他很適合用來進(jìn)行GUI的設(shè)計,但是他并不是適合用來為Python GUI程序注入靈魂——即進(jìn)行信號與槽的編輯。 - 此外,
Qt Designer
也支持對我們已經(jīng)設(shè)計好的GUI進(jìn)行預(yù)覽的功能。 - 當(dāng)我們完成了UI的設(shè)計之后,我們保存記得得到一份
.ui
文件。
6.2 將Qt Designer的UI文件集成到Python GUI項目
"""
動態(tài)加載ui文件
"""
import sys
import time
from PyQt5.QtWidgets import *
from PyQt5 import uic
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.ui = None
self.user_name = None
self.password = None
self.login_btn = None
self.forget_btn = None
self.text_browser = None
self.init_ui()
def init_ui(self):
self.ui = uic.loadUi("ui/login.ui")
# self.explain_function() # 這是一個用來對ui文件進(jìn)行輸出測試的測試方法
self.user_name = self.ui.lineEdit # 這是獲取用戶名輸入框
self.password = self.ui.lineEdit_2 # 這是獲取用戶密碼輸入框
self.login_btn = self.ui.pushButton # 這是獲取登錄按鈕
self.forget_btn = self.ui.pushButton_2 # 這是獲取忘記密碼按鈕
self.text_browser = self.ui.textBrowser # 這是獲取文本瀏覽器
# 給登錄按鈕綁定信號和槽函數(shù)
self.login_btn.clicked.connect(self.login)
# 給忘記密碼按鈕綁定信號和槽函數(shù)
self.forget_btn.clicked.connect(self.forget)
# 給文本瀏覽器綁定信號和槽函數(shù)
self.text_browser.textChanged.connect(self.show_text)
def login(self):
user_name = self.user_name.text()
password = self.password.text()
print("您當(dāng)前輸入的用戶名為:" + user_name)
print("您當(dāng)前輸入的密碼為:" + password)
print("登錄成功!")
# self.text_browser.setText("登錄成功!")
def forget(self):
print("找回密碼成功!")
# self.text_browser.setText("找回密碼成功!")
def show_text(self):
# 獲取當(dāng)前時間的字符串
self.text_browser.setText("文本信息改變" + time.gmtime().__str__())
def explain_function(self):
# 往下是使用print輸出來嘗試了解我們所載入的ui對象中到底有些什么東西?
print(self.ui) # 打印ui文件中的最頂層的對象
print(self.ui.__dict__) # 獲取ui文件中最頂層的對象中的所有的屬性
"""
以下是ui文件中最頂層的對象中的所有的屬性,以鍵值對的方式給出:
{'label': <PyQt5.QtWidgets.QLabel object at 0x0000014D5EE89750>,
'lineEdit': <PyQt5.QtWidgets.QLineEdit object at 0x0000014D5EE89870>,
'lineEdit_2': <PyQt5.QtWidgets.QLineEdit object at 0x0000014D5EE89900>,
'label_2': <PyQt5.QtWidgets.QLabel object at 0x0000014D5EE89990>,
'pushButton': <PyQt5.QtWidgets.QPushButton object at 0x0000014D5EE89A20>,
'pushButton_2': <PyQt5.QtWidgets.QPushButton object at 0x0000014D5EE89AB0>,
'textBrowser': <PyQt5.QtWidgets.QTextBrowser object at 0x0000014D5EE89B40>}
"""
print(self.ui.label.text()) # ui文件中最頂層的對象中的label對象的text()方法
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
# 展示窗口
# w.show() # 在這種情況下這是一個錯誤示范
w.ui.show() # 這才是正確的操作
sys.exit(app.exec_())
文章來源地址http://www.zghlxwxcb.cn/news/detail-851784.html
- 以我這里設(shè)計的這一份簡單的登錄界面為例:不同于以往的需要自己去定義各種各樣的UI控件,結(jié)合了Qt Designer的UI設(shè)計,我們只需要通過
self.ui = uic.loadUi("ui/login.ui")
載入我們已經(jīng)設(shè)計好的UI界面即可。 - 而UI界面中的控件,本質(zhì)上,其實我們是通過管理對象實例來對這些控件對象進(jìn)行操作的,從我們載入的UI對象中,我們可以根據(jù)我們在UI設(shè)計中對他們的命名來獲取這些UI對象,進(jìn)而可以像直接用代碼編輯UI的時候那樣,通過對對象的處理來實現(xiàn)各種業(yè)務(wù)邏輯。
7. PyQt5綜合案例
- 至此,關(guān)于
PyQt5的基本知識
已經(jīng)介紹完畢,有了這些基本知識,我們就可以著手去嘗試我們所需要的Python GUI程序的編寫了,當(dāng)然,只學(xué)會這些還是遠(yuǎn)遠(yuǎn)不夠的,如果想要更近一步的學(xué)習(xí),可以參考我列出的進(jìn)一步學(xué)習(xí)
中的學(xué)習(xí)鏈接,如果對于上面的知識想要進(jìn)一步的鞏固,可以嘗試去仔細(xì)閱讀和學(xué)習(xí)我在參考資料中列出的學(xué)習(xí)鏈接。 - 此外,在我的Gitee代碼倉庫中還有其它的未完全給出的學(xué)習(xí)代碼,其中還包含一個完整的登錄功能的綜合案例,歡迎大家進(jìn)一步學(xué)習(xí)。
- 最后的最后,誠摯感謝
王銘東老師
在B站的無私分享!
參考資料
- bilibili-王銘東-PyQt5快速入門
- Python的PyQt官方文檔
- Qt文檔
進(jìn)一步學(xué)習(xí)
- 英文版的PyQt學(xué)習(xí)資料——非常系統(tǒng)且全面
- 白月黑羽老師的B站教學(xué)視頻
- 白月黑羽老師的個人學(xué)習(xí)資料網(wǎng)站
- PyQt5界面美化 教程 QtDesigner
到了這里,關(guān)于[Python GUI PyQt] PyQt5快速入門的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!