PyQt5 高级界面控制(多线程、网页交互、调用JavaScript)

2022-06-05 12:03:18 浏览数 (1)

文章目录
  • 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信号
代码语言:javascript复制
# _*_ 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, QApplication


class 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 sys
    app = QApplication(sys.argv)
    win = QtimerDemo()
    win.show()
    sys.exit(app.exec_())

一次性定时器

代码语言:javascript复制
# _*_ coding: utf-8 _*_
# @Time : 2022/5/29 23:56
# @Author : Michael
# @File : qtimer_demo2.py
# @desc :
import sys

from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QLabel

if __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()

代码语言:javascript复制
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:14
# @Author : Michael
# @File : qthread1.py
# @desc :
# -*- coding: utf-8 -*-

import sys

from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QWidget, QListWidget, QPushButton, QGridLayout, QApplication


class 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 = True
        self.num = 0

    def __del__(self):
        self.working = False
        self.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_())
界面卡住例子
代码语言:javascript复制
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:25
# @Author : Michael
# @File : thread_stuck.py
# @desc :
import sys

from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLCDNumber, QPushButton

global sec
sec = 0


def setTime():
    global sec
    sec  = 1
    # LED显示数字 1
    lcdNumber.display(sec)

def work():
    # 计时器每秒计数
    timer.start(1000)
    for i in range(2000000000):
        pass

    timer.stop()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = QWidget()
    win.resize(300, 120)

    # 垂直布局类QVBoxLayout
    layout = QVBoxLayout(win)
    # 加个显示屏
    lcdNumber = QLCDNumber()
    layout.addWidget(lcdNumber)
    button = QPushButton("测试")
    layout.addWidget(button)

    timer = QTimer()
    # 每次计时结束,触发setTime
    timer.timeout.connect(setTime)
    button.clicked.connect(work)

    win.show()
    sys.exit(app.exec_())

模拟下载,并计时

可以看到程序卡住了,计时器也没有走起来

PyQt 中所有的窗口都是在 UI 主线程中,这个线程中执行耗时的操作会阻塞 UI 线程,耗时的操作需要 开启新的线程 去执行

分离UI和工作线程
代码语言:javascript复制
# _*_ coding: utf-8 _*_
# @Time : 2022/5/30 0:37
# @Author : Michael
# @File : threadsplit_ui_work.py
# @desc :
import sys

from PyQt5.QtCore import QTimer, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLCDNumber, QPushButton

global sec
sec = 0


class 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 sec
    sec  = 1
    # LED显示数字 1
    lcdNumber.display(sec)


def work():
    # 计时器每秒计数
    timer.start(1000)
    # 计时开始
    workThread.start()
    # 当获得循环完毕的信号时,停止计数
    workThread.trigger.connect(timeStop)


def timeStop():
    timer.stop()
    print("运行结束用时", lcdNumber.value())
    global sec
    sec = 0


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = QWidget()
    win.resize(300, 120)

    # 垂直布局类QVBoxLayout
    layout = QVBoxLayout(win)
    # 加个显示屏
    lcdNumber = QLCDNumber()
    layout.addWidget(lcdNumber)
    button = QPushButton("测试")
    layout.addWidget(button)

    timer = QTimer()
    workThread = WorkThread()

    button.clicked.connect(work)
    # 每次计时结束,触发 countTime
    timer.timeout.connect(countTime)

    win.show()
    sys.exit(app.exec_())

1.3 事件处理

  • 可以使用 QApplication.processEvents() 刷新页面,给人感觉不卡顿

上面卡住的例子中添加一句就可以不卡了

代码语言:javascript复制
def work():
    # 计时器每秒计数
    timer.start(1000)
    for i in range(2000000000):
        QApplication.processEvents() # 添加这句刷新页面
        pass

    timer.stop()

2. 网页交互

pyqt5 使用 QWebEngineView 控件来展示 HTML ,其使用的 Chromium 内核

代码语言:javascript复制
# _*_ 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, QApplication


class 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 sys
    app = QApplication(sys.argv)
    win = MainWin()
    win.show()
    sys.exit(app.exec_())
显示本地 html
代码语言:javascript复制
url = QUrl("D:/gitcode/Python_learning/qt/ch5/index.html")
self.browser.load(url)
显示 html 代码
代码语言:javascript复制
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
代码语言: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 button
        function 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 页面进行双向的数据交互
代码语言:javascript复制
from PyQt5.QtCore import pyqtProperty

class 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 代码收集用户提交的数据
代码语言:javascript复制
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWebChannel import QWebChannel

channel = QWebChannel()
myObj = MySharedObject()
channel.registerObject("bridge", myObj)
view.page().setWebChannel(channel)
  • 然后,在 Web 页面中,JavaScript 通过桥连接方式传递数据给PyQt
  • 最后,PyQt 接收到页面传递的数据,经过业务处理后,还可以把处理过的数据返给Web页面

html 需要引入 <script src="qwebchannel.js"></script>

代码语言:javascript复制
<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>

0 人点赞