PyQt5多线程及布局
多线程
PyQt5 中常用的多线程方法主要是:QTimer、QThread 两种
QTimer
方法 | 含义 |
---|---|
timer.start(n) | 每 n 毫秒调用一次 |
QTimer.singleShot(n, app.quit) | 过 n 毫秒调用一次,且仅调用一次。 app.quit 可以替换成其他需要执行的事件 |
定时刷新时间
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ShowTime(QWidget):
def __init__(self):
super(ShowTime, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("动态显示当前时间")
self.label = QLabel("显示当前时间")
self.startBtn = QPushButton("开始")
self.endBtn = QPushButton("结束")
layout = QGridLayout()
self.timer = QTimer()
self.timer.timeout.connect(self.showTimer)
layout.addWidget(self.label, 0, 0, 1, 2)
layout.addWidget(self.startBtn, 1, 0)
layout.addWidget(self.endBtn, 1, 1)
self.startBtn.clicked.connect(self.startTimer)
self.endBtn.clicked.connect(self.endTimer)
self.setLayout(layout)
def showTimer(self):
print("show")
time = QDateTime.currentDateTime()
timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd")
self.label.setText(timeDisplay)
def startTimer(self):
# 让定时器以每 1 秒( 1000 毫秒)为周期进行循环调用其身上绑定的槽
self.timer.start(1000)
self.startBtn.setEnabled(False)
self.endBtn.setEnabled(True)
def endTimer(self):
self.timer.stop()
self.startBtn.setEnabled(True)
self.endBtn.setEnabled(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = ShowTime()
main.show()
sys.exit(app.exec_())
定时关闭窗口
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ShowTime(QWidget):
def __init__(self):
super(ShowTime, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("动态显示当前时间")
QTimer.singleShot(5000, app.quit)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = ShowTime()
main.show()
sys.exit(app.exec_())
QThread
如果使用单线程进行计算、刷新页面上某些值时(如更新 lcdNumber)。当事件仅执行一次时,没有问题,但是当事件是持续运行的循环事件,即使内部有 time.sleep() 之类的代码,其GUI也不会更新,而是期望等退出循环后再进行渲染,此期间程序的GUI将会未响应,但是程序依旧在后台运行
计数器及自动关闭
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
# 全局计数器
sec = 0
# 继承 QThread 并在其基础上实现新功能
class WorkThread(QThread):
# 创建自定义信号
# 每隔一秒发送一次信号
timer = pyqtSignal()
# 技术结束后发送一次信号
end = pyqtSignal()
def run(self):
while True:
# 睡眠一秒
self.sleep(1)
# 计数器到 5 时,关闭窗口
if sec == 5:
# 发送 end 信号,触发槽函数
self.end.emit()
# 结束该信号的持续监听
break
# 每隔一秒发送一次 timer 信号
self.timer.emit()
class Counter(QWidget):
def __init__(self):
super(Counter, self).__init__()
self.setWindowTitle("使用 QThread 编写计数器")
self.resize(300, 120)
layout = QVBoxLayout()
# 类似 led 的显示屏
self.lcdNumber = QLCDNumber()
layout.addWidget(self.lcdNumber)
button = QPushButton("开始计数")
layout.addWidget(button)
# 实例化继承了 Thread 的自定义子类,以开辟新线程进行某些工作
self.workThread = WorkThread()
# 持续发送 timer 信号
self.workThread.timer.connect(self.countTime)
# 到达 sec==5 触发 end 信号
self.workThread.end.connect(self.end)
button.clicked.connect(self.work)
self.setLayout(layout)
def countTime(self):
global sec
sec += 1
self.lcdNumber.display(sec)
def end(self):
QMessageBox.information(self, "消息", "计数结束", QMessageBox.Ok)
def work(self):
# 开启新线程,并发送自定义信号
self.workThread.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Counter()
main.show()
sys.exit(app.exec_())
布局
绝对布局
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class AbsoluteLayout(QWidget):
def __init__(self):
super(AbsoluteLayout, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("绝对布局")
self.label1 = QLabel("欢迎", self)
self.label1.move(15, 20)
self.label2 = QLabel("学习", self)
self.label2.move(35, 40)
self.label3 = QLabel("PyQT5", self)
self.label3.move(55, 80)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = AbsoluteLayout()
main.show()
sys.exit(app.exec_())
水平盒布局
import sys, math
from PyQt5.QtWidgets import *
class HBoxLayout(QWidget):
def __init__(self):
super(HBoxLayout, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("水平盒布局")
layout = QHBoxLayout()
layout.addWidget(QPushButton("按钮1"))
layout.addWidget(QPushButton("按钮2"))
layout.addWidget(QPushButton("按钮3"))
layout.addWidget(QPushButton("按钮4"))
layout.addWidget(QPushButton("按钮5"))
layout.addWidget(QPushButton("按钮6"))
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = HBoxLayout()
main.show()
sys.exit(app.exec_())
垂直盒布局
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class HBoxLayout(QWidget):
def __init__(self):
super(HBoxLayout, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("水平盒布局")
layout = QVBoxLayout()
layout.addWidget(QPushButton("按钮1"))
layout.addWidget(QPushButton("按钮2"))
layout.addWidget(QPushButton("按钮3"))
layout.addWidget(QPushButton("按钮4"))
layout.addWidget(QPushButton("按钮5"))
layout.addWidget(QPushButton("按钮6"))
layout.setSpacing(40)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = HBoxLayout()
main.show()
sys.exit(app.exec_())
设置控件对齐方式
通过调整 addWidget() 的参数来控制控件的位置
- stretch —— 设置伸缩量,即控件占用空间的比例
- alignment —— 对齐方式
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class HBoxLayout(QWidget):
def __init__(self):
super(HBoxLayout, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("水平盒布局")
layout = QHBoxLayout()
layout.addWidget(QPushButton("按钮1"), 1, Qt.AlignLeft | Qt.AlignTop)
layout.addWidget(QPushButton("按钮2"), 1, Qt.AlignLeft | Qt.AlignTop)
layout.addWidget(QPushButton("按钮3"), 1, Qt.AlignLeft | Qt.AlignTop)
layout.addWidget(QPushButton("按钮4"), 1, Qt.AlignLeft | Qt.AlignBottom)
layout.addWidget(QPushButton("按钮5"), 1, Qt.AlignLeft | Qt.AlignBottom)
layout.addWidget(QPushButton("按钮6"), 1, Qt.AlignLeft | Qt.AlignBottom)
layout.setSpacing(40)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = HBoxLayout()
main.show()
sys.exit(app.exec_())
让按钮永远在窗口某位置
import sys
from PyQt5.QtWidgets import *
class FixBtn(QWidget):
def __init__(self):
super(FixBtn, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("垂直盒布局")
okBtn = QPushButton("确定")
canBtn = QPushButton("取消")
layout = QHBoxLayout()
# 关键在这里,添加一个伸缩量,该伸缩量后方的控件都被挤到右侧了
layout.addStretch(1)
layout.addWidget(okBtn)
layout.addWidget(canBtn)
layout1 = QVBoxLayout()
btn1 = QPushButton("按钮1")
btn2 = QPushButton("按钮2")
btn3 = QPushButton("按钮3")
# 添加一个为 0 的伸缩量,在遇到第二个伸缩量前的控件,全部向顶部(如果是水平就是左侧)排列
layout1.addStretch(0)
layout1.addWidget(btn1)
layout1.addWidget(btn2)
layout1.addWidget(btn3)
# 添加一个伸缩量,让其下方的值全部靠底排列
layout1.addStretch(1)
layout1.addLayout(layout)
self.setLayout(layout1)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = FixBtn()
main.show()
sys.exit(app.exec_())
栅格布局实现计算器
import sys
from PyQt5.QtWidgets import *
class Cacl(QWidget):
def __init__(self):
super(Cacl, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("计算器")
layout = QGridLayout()
self.setLayout(layout)
names = [
"Cls", "Back", "", "Close",
"7", "8", "9", "/",
"6", "5", "4", "*",
"3", "2", "1", "-",
"0", "=", "+", "+",
]
# 生成网格坐标
position = [(i, j) for i in range(5) for j in range(4)]
# 绑定网格坐标和文本
for posi, name in zip(position, names):
# 不生成按钮
if name == "":
continue
btn = QPushButton(name)
layout.addWidget(btn, *posi)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Cacl()
main.show()
sys.exit(app.exec_())
栅格单元格跨列
import sys
from PyQt5.QtWidgets import *
class GridForm(QWidget):
def __init__(self):
super(GridForm, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("计算器")
layout = QGridLayout()
self.setLayout(layout)
tLabel = QLabel("标题")
aLabel = QLabel("作者")
cLabel = QLabel("内容")
tLineEdit = QLineEdit()
aLineEdit = QLineEdit()
cLineEdit = QTextEdit()
layout.addWidget(tLabel, 1, 0)
layout.addWidget(aLabel, 2, 0)
layout.addWidget(cLabel, 3, 0)
layout.addWidget(tLineEdit, 1, 1)
layout.addWidget(aLineEdit, 2, 1)
layout.addWidget(cLineEdit, 3, 1, 5, 1)
layout.setSpacing(10)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = GridForm()
main.show()
sys.exit(app.exec_())
表单布局
import sys
from PyQt5.QtWidgets import *
class FormForm(QWidget):
def __init__(self):
super(FormForm, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("计算器")
layout = QFormLayout()
self.setLayout(layout)
tLabel = QLabel("标题")
aLabel = QLabel("作者")
cLabel = QLabel("内容")
tLineEdit = QLineEdit()
aLineEdit = QLineEdit()
cLineEdit = QTextEdit()
layout.addRow(tLabel, tLineEdit)
layout.addRow(aLabel, aLineEdit)
layout.addRow(cLabel, cLineEdit)
layout.setSpacing(10)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = FormForm()
main.show()
sys.exit(app.exec_())
拖动控件边界
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Splitter(QWidget):
def __init__(self):
super(Splitter, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("拖动控件边界")
layout_hbox = QHBoxLayout()
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
textedit = QTextEdit()
# 拖动控件,将需要被拖动边界的两个控件放入到 splitter 内
splitter1 = QSplitter(Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(textedit)
# 设置左右两侧的尺寸
splitter1.setSizes([100, 200])
# 形成上下分割的布局
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
layout_hbox.addWidget(splitter2)
self.setLayout(layout_hbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Splitter()
main.show()
sys.exit(app.exec_())