文章目录
- 1. 多线程
- 1.1 QTimer
- 1.2 QThread
- 界面卡住例子
- 分离UI和工作线程
- 1.3 事件处理
- 2. 网页交互
- 显示本地 html
- 显示 html 代码
- 调用 JavaScript
- JavaScript 调用 PyQt代码
learn from 《PyQt5 快速开发与实战》
https://doc.qt.io/qtforpython/index.html
https://www.riverbankcomputing.com/static/Docs/PyQt5
1. 多线程
1.1 QTimer
- 周期性的发出
timeout
信号
# _*_ coding: utf-8 _*_
# @Time : 2022/5/29 23:42
# @Author : Michael
# @File : qtimer_demo.py
# @desc :from PyQt5.QtCore import QTimer, QDateTime
from PyQt5.QtWidgets import QWidget, QListWidget, QLabel, QPushButton, QGridLayout, QApplicationclass QtimerDemo(QWidget):def __init__(self):super(QtimerDemo, self).__init__()self.setWindowTitle("QTimer Demo")self.listFile = QListWidget()self.label = QLabel('显示当前时间')self.startBtn = QPushButton('开始')self.stopBtn = QPushButton('停止')layout = QGridLayout()self.timer = QTimer()self.timer.timeout.connect(self.showTime)layout.addWidget(self.label, 0, 0, 1, 2)layout.addWidget(self.startBtn, 1, 0, 1, 2)layout.addWidget(self.stopBtn, 2, 0, 1, 2)self.startBtn.clicked.connect(self.startTimer)self.stopBtn.clicked.connect(self.stopTimer)self.setLayout(layout)def startTimer(self):self.timer.start(1000) # 每隔1秒触发一次self.startBtn.setEnabled(False)self.stopBtn.setEnabled(True)def stopTimer(self):self.timer.stop()self.startBtn.setEnabled(True)self.stopBtn.setEnabled(False)def showTime(self):time = QDateTime().currentDateTime()timedisplay = time.toString('yyyy-MM-dd hh:mm:ss')self.label.setText(timedisplay)
if __name__ == '__main__':import sysapp = QApplication(sys.argv)win = QtimerDemo()win.show()sys.exit(app.exec_())
一次性定时器
# _*_ coding: utf-8 _*_
# @Time : 2022/5/29 23:56
# @Author : Michael
# @File : qtimer_demo2.py
# @desc :
import sysfrom PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QLabelif __name__ == '__main__':app = QApplication(sys.argv)label = QLabel('<font color=red size=40>Hello World, 3秒后会消失</font>')label.setWindowFlags(Qt.SplashScreen | Qt.FramelessWindowHint) # 无边框窗口label.show()QTimer.singleShot(3000, app.quit) # 一次性定时器,可模仿程序启动画面sys.exit(app.exec_())
1.2 QThread
创建QThread
的子类,覆写 QThread.run()
,调用 线程的start()
函数后,会自动调用 run()
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:14
# @Author : Michael
# @File : qthread1.py
# @desc :
# -*- coding: utf-8 -*-import sysfrom PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QWidget, QListWidget, QPushButton, QGridLayout, QApplicationclass MainWidget(QWidget):def __init__(self, parent=None):super(MainWidget, self).__init__(parent)self.setWindowTitle("QThread 例子")self.thread = Worker()self.listFile = QListWidget()self.btnStart = QPushButton('开始')layout = QGridLayout(self)layout.addWidget(self.listFile, 0, 0, 1, 2)layout.addWidget(self.btnStart, 1, 1)self.btnStart.clicked.connect(self.slotStart)self.thread.sinOut.connect(self.slotAdd)def slotAdd(self, file_inf):self.listFile.addItem(file_inf)def slotStart(self):self.btnStart.setEnabled(False)self.thread.start()class Worker(QThread):sinOut = pyqtSignal(str)def __init__(self, parent=None):super(Worker, self).__init__(parent)self.working = Trueself.num = 0def __del__(self):self.working = Falseself.wait()def run(self):while self.working:file_str = 'File index {0}'.format(self.num)self.num += 1# 发出信号self.sinOut.emit(file_str)# 线程休眠2秒self.sleep(2)if __name__ == "__main__":app = QApplication(sys.argv)demo = MainWidget()demo.show()sys.exit(app.exec_())
界面卡住例子
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:25
# @Author : Michael
# @File : thread_stuck.py
# @desc :
import sysfrom PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLCDNumber, QPushButtonglobal sec
sec = 0def setTime():global secsec += 1# LED显示数字+1lcdNumber.display(sec)def work():# 计时器每秒计数timer.start(1000)for i in range(2000000000):passtimer.stop()if __name__ == "__main__":app = QApplication(sys.argv)win = QWidget()win.resize(300, 120)# 垂直布局类QVBoxLayoutlayout = QVBoxLayout(win)# 加个显示屏lcdNumber = QLCDNumber()layout.addWidget(lcdNumber)button = QPushButton("测试")layout.addWidget(button)timer = QTimer()# 每次计时结束,触发setTimetimer.timeout.connect(setTime)button.clicked.connect(work)win.show()sys.exit(app.exec_())
模拟下载,并计时
可以看到程序卡住了,计时器也没有走起来
PyQt 中所有的窗口都是在 UI 主线程中,这个线程中执行耗时的操作会阻塞 UI 线程,耗时的操作需要 开启新的线程 去执行
分离UI和工作线程
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:37
# @Author : Michael
# @File : threadsplit_ui_work.py
# @desc :
import sysfrom PyQt5.QtCore import QTimer, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLCDNumber, QPushButtonglobal sec
sec = 0class WorkThread(QThread):trigger = pyqtSignal()def __int__(self):super(WorkThread, self).__init__()def run(self):for i in range(2000000000):pass# 循环完毕后发出信号self.trigger.emit()def countTime():global secsec += 1# LED显示数字+1lcdNumber.display(sec)def work():# 计时器每秒计数timer.start(1000)# 计时开始workThread.start()# 当获得循环完毕的信号时,停止计数workThread.trigger.connect(timeStop)def timeStop():timer.stop()print("运行结束用时", lcdNumber.value())global secsec = 0if __name__ == "__main__":app = QApplication(sys.argv)win = QWidget()win.resize(300, 120)# 垂直布局类QVBoxLayoutlayout = QVBoxLayout(win)# 加个显示屏lcdNumber = QLCDNumber()layout.addWidget(lcdNumber)button = QPushButton("测试")layout.addWidget(button)timer = QTimer()workThread = WorkThread()button.clicked.connect(work)# 每次计时结束,触发 countTimetimer.timeout.connect(countTime)win.show()sys.exit(app.exec_())
1.3 事件处理
- 可以使用
QApplication.processEvents()
刷新页面,给人感觉不卡顿
上面卡住的例子中添加一句就可以不卡了
def work():# 计时器每秒计数timer.start(1000)for i in range(2000000000):QApplication.processEvents() # 添加这句刷新页面passtimer.stop()
2. 网页交互
pyqt5 使用 QWebEngineView
控件来展示 HTML ,其使用的 Chromium 内核
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:53
# @Author : Michael
# @File : web_load.py
# @desc :
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QMainWindow, QApplicationclass MainWin(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("load url")self.setGeometry(300, 300, 1000, 600)self.browser = QWebEngineView()self.browser.load(QUrl("https://michael.blog.csdn.net/"))self.setCentralWidget(self.browser)if __name__ == '__main__':import sysapp = QApplication(sys.argv)win = MainWin()win.show()sys.exit(app.exec_())
显示本地 html
url = QUrl("D:/gitcode/Python_learning/qt/ch5/index.html")
self.browser.load(url)
显示 html 代码
html = """<!DOCTYPE html><html><head><meta charset="UTF-8"><title></title></head><body><h1>Hello michael</h1><h1>Hello PyQt5 from setHtml</h1></body></html>"""
self.browser.setHtml(html)
调用 JavaScript
# _*_ coding: utf-8 _*_
# @Time : 2022/5/31 23:44
# @Author : Michael
# @File : webjs01.py
# @desc :
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
from PyQt5.QtWebEngineWidgets import QWebEngineView
import sys# 创建一个 application实例
app = QApplication(sys.argv)
win = QWidget()
win.setWindowTitle('Web页面中的JavaScript与 QWebEngineView交互例子')# 创建一个垂直布局器
layout = QVBoxLayout()
win.setLayout(layout)# 创建一个 QWebEngineView 对象
view = QWebEngineView()
view.setHtml('''<html><head><title>A Demo Page</title><script language="javascript">// Completes the full-name control and// shows the submit buttonfunction completeAndReturnName() {var fname = document.getElementById('fname').value;var lname = document.getElementById('lname').value;var full = fname + ' ' + lname;document.getElementById('fullname').value = full;document.getElementById('submit-btn').style.display = 'block';return full;}</script></head><body><form><label for="fname">First name:</label><input type="text" name="fname" id="fname"></input><br /><label for="lname">Last name:</label><input type="text" name="lname" id="lname"></input><br /><label for="fullname">Full name:</label><input disabled type="text" name="fullname" id="fullname"></input><br /><input style="display: none;" type="submit" id="submit-btn"></input></form></body></html>
''')# 创建一个按钮去调用 JavaScript代码
button = QPushButton('设置全名')def js_callback(result):print(result)def complete_name():view.page().runJavaScript('completeAndReturnName();', js_callback)# QWebEngineView 对象的 page()方法返回一个 QWebEnginePage 对象# QWebEnginePage 对象的 异步 runJavaScript()方法可以执行 JavaScript代码# 需要回调函数来处理结果# 按钮连接 'complete_name'槽,当点击按钮是会触发信号
button.clicked.connect(complete_name)# 把QWebView和button加载到layout布局中
layout.addWidget(view)
layout.addWidget(button)# 显示窗口和运行app
win.show()
sys.exit(app.exec_())
JavaScript 调用 PyQt代码
- PyQt 可以与加载的 Web 页面进行双向的数据交互
from PyQt5.QtCore import pyqtPropertyclass MySharedObject(QWidget):def __init__(self):super(MySharedObject, self).__init__()def _getStrValue(self):#return '100'def _setStrValue(self, str):#print('获得页面参数 :%s' % str)QMessageBox.information(self, "Information", '获得页面参数 :%s' % str)# 需要定义对外暴露的方法strValue = pyqtProperty(str, fget=_getStrValue, fset=_setStrValue)
- 首先,使用
QWebEngineView
对象加载 Web页面后,就可以获得页面中表单输入数据,在 Web 页面中通过 JavaScript 代码收集用户提交的数据
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWebChannel import QWebChannelchannel = QWebChannel()
myObj = MySharedObject()
channel.registerObject("bridge", myObj)
view.page().setWebChannel(channel)
- 然后,在 Web 页面中,JavaScript 通过
桥连接
方式传递数据给PyQt - 最后,PyQt 接收到页面传递的数据,经过业务处理后,还可以把处理过的数据返给Web页面
html 需要引入 <script src="qwebchannel.js"></script>
<html>
<head><title>A Demo Page</title><meta charset="UTF-8"><script src="qwebchannel.js"></script><script>document.addEventListener("DOMContentLoaded", function () {new QWebChannel(qt.webChannelTransport, function (channel) {window.bridge = channel.objects.bridge;alert('bridge=' + bridge + '\n从pyqt传来的参数=' + window.bridge.strValue);});});function onShowMsgBox() {if (window.bridge) {var fname = document.getElementById('fname').value;window.bridge.strValue = fname;}}</script>
</head><body>
<form><label for="姓名">user name:</label><input type="text" name="fname" id="fname"></input><br/><input type="button" value="传递参数到pyqt" onclick="onShowMsgBox()"><input type="reset" value='重置'/>
</form>
</body>
</html>