python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例


Posted in Python onFebruary 26, 2020

在做界面开发时,无论是移动端的Android,还是我们这里讲的PyQt5,经常会有一个界面开发准则,那就是UI主线程与耗时子线程一定要分开,主线程负责刷新界面,耗时操作,如网络交互、磁盘IO等,都应该放在子线程里执行,它们各司其职,保证系统正常运行,提升整体用户体验。

软硬件环境

windows 10 64bit

PyQt5

Anaconda3 with python 3.6.5

实例代码

首先看下工程目录结构

python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例

main.py,这是工程入口文件,它负责创建app

# -*- coding: utf-8 -*-

import sys

from PyQt5.QtWidgets import QApplication

from gui.mainwindow import MainWindow

if __name__ == '__main__':

  app = QApplication(sys.argv)
  main_window = MainWindow()
  main_window.show()
  sys.exit(app.exec_())

ui_mainwindow.py,负责界面的绘制,这个文件通过designer图形化工具作图然后使用pyuic工具生成对应的python代码

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file '.\mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
  def setupUi(self, MainWindow):
    MainWindow.setObjectName("MainWindow")
    MainWindow.resize(800, 600)
    MainWindow.setMinimumSize(QtCore.QSize(800, 600))
    MainWindow.setMaximumSize(QtCore.QSize(800, 600))
    self.centralwidget = QtWidgets.QWidget(MainWindow)
    self.centralwidget.setObjectName("centralwidget")
    self.button_ok = QtWidgets.QPushButton(self.centralwidget)
    self.button_ok.setGeometry(QtCore.QRect(260, 220, 230, 140))
    self.button_ok.setMinimumSize(QtCore.QSize(230, 140))
    self.button_ok.setMaximumSize(QtCore.QSize(230, 140))
    font = QtGui.QFont()
    font.setPointSize(50)
    self.button_ok.setFont(font)
    self.button_ok.setFocusPolicy(QtCore.Qt.TabFocus)
    self.button_ok.setObjectName("button_ok")
    MainWindow.setCentralWidget(self.centralwidget)
    self.statusbar = QtWidgets.QStatusBar(MainWindow)
    self.statusbar.setObjectName("statusbar")
    MainWindow.setStatusBar(self.statusbar)
    self.menubar = QtWidgets.QMenuBar(MainWindow)
    self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23))
    self.menubar.setObjectName("menubar")
    self.menuFile = QtWidgets.QMenu(self.menubar)
    self.menuFile.setObjectName("menuFile")
    self.menuHelp = QtWidgets.QMenu(self.menubar)
    self.menuHelp.setObjectName("menuHelp")
    MainWindow.setMenuBar(self.menubar)
    self.actionExit = QtWidgets.QAction(MainWindow)
    self.actionExit.setObjectName("actionExit")
    self.actionCopy = QtWidgets.QAction(MainWindow)
    self.actionCopy.setObjectName("actionCopy")
    self.actionPaste = QtWidgets.QAction(MainWindow)
    self.actionPaste.setObjectName("actionPaste")
    self.actionCut = QtWidgets.QAction(MainWindow)
    self.actionCut.setObjectName("actionCut")
    self.actionHelp = QtWidgets.QAction(MainWindow)
    self.actionHelp.setObjectName("actionHelp")
    self.actionAbout = QtWidgets.QAction(MainWindow)
    self.actionAbout.setObjectName("actionAbout")
    self.action_query = QtWidgets.QAction(MainWindow)
    self.action_query.setObjectName("action_query")
    self.action_backupDB = QtWidgets.QAction(MainWindow)
    self.action_backupDB.setObjectName("action_backupDB")
    self.action_reset_mac = QtWidgets.QAction(MainWindow)
    self.action_reset_mac.setObjectName("action_reset_mac")
    self.menuFile.addSeparator()
    self.menuFile.addAction(self.actionExit)
    self.menuFile.addSeparator()
    self.menuHelp.addSeparator()
    self.menuHelp.addSeparator()
    self.menuHelp.addAction(self.actionAbout)
    self.menuHelp.addSeparator()
    self.menubar.addAction(self.menuFile.menuAction())
    self.menubar.addAction(self.menuHelp.menuAction())

    self.retranslateUi(MainWindow)
    QtCore.QMetaObject.connectSlotsByName(MainWindow)

  def retranslateUi(self, MainWindow):
    _translate = QtCore.QCoreApplication.translate
    MainWindow.setWindowTitle(_translate("MainWindow", "分离UI主线程和工作线程"))
    self.button_ok.setText(_translate("MainWindow", "确定"))
    self.menuFile.setTitle(_translate("MainWindow", "File"))
    self.menuHelp.setTitle(_translate("MainWindow", "Help"))
    self.actionExit.setText(_translate("MainWindow", "退出"))
    self.actionHelp.setText(_translate("MainWindow", "软件使用说明"))
    self.actionAbout.setText(_translate("MainWindow", "关于"))

mainwindow.py,主要负责界面上控件的事件处理

import time

from PyQt5.QtWidgets import QMainWindow

from gui.ui_mainwindow import *


class MainWindow(QMainWindow, Ui_MainWindow):

  def __init__(self, parent=None):
    super(MainWindow, self).__init__(parent)
    self.setupUi(self)

    # 绑定点击事件
    self.button_ok.clicked.connect(self.button_start)

  def button_start(self):

    self.button_ok.setChecked(True)
    self.button_ok.setDisabled(True)

    time.sleep(20)

这里我们使用time.sleep(20)来模拟耗时任务,执行python main.py后一会,界面就会出现无响应,假死的现象,等到20秒过后,界面又恢复了正常,用户体验非常差。

python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例

其实要解决这个问题,也非常简单。我们将UI主线程中的time.sleep(20)移动到子线程中就可以了。PyQt5中提供了线程类QThread,我们继承它并重写它的run方法,新建一个新的文件threads.py

# -*- coding: utf-8 -*-
import time

from PyQt5.QtCore import QThread, pyqtSignal

class WorkThread(QThread):

  # 使用信号和UI主线程通讯,参数是发送信号时附带参数的数据类型,可以是str、int、list等
  finishSignal = pyqtSignal(str)

  # 带参数示例
  def __init__(self, ip, port, parent=None):
    super(WorkThread, self).__init__(parent)

    self.ip = ip
    self.port = port

  def run(self):
    '''
    重写
    '''

    print('=============sleep======ip: {}, port: {}'.format(self.ip, self.port))
    time.sleep(20)

    self.finishSignal.emit('This is a test.')
    return

注意到这里我们使用了pyqtSignal,我们使用它来跟UI主线程通讯,一般用于界面元素的刷新,在子线程的最后,我们发送这个信号。

对应的mainwindow.py,需要进行如下修改

from gui.threads import WorkThread

# 其它部分省略
def button_start(self):

  print('button_start clicked.')

  # 设置按钮不可用
  self.button_ok.setChecked(True)
  self.button_ok.setDisabled(True)

  self.th = WorkThread(ip='192.168.1.1', port=4000)

  # 将线程th的信号finishSignal和UI主线程中的槽函数button_finish进行连接
  self.th.finishSignal.connect(self.button_finish)

  # 启动线程
  self.th.start()

def button_finish(self, msg):

  print('msg: {}'.format(msg))

  # 设置按钮可用
  self.button_ok.setChecked(False)
  self.button_ok.setDisabled(False)

一顿操作之后,再次执行python main.py,界面就再也不会出现No Resonding的提示了,可以在子线程执行过程中可以随意操作界面上的其它控件

更多相关知道请查看下面的相关链接

Python 相关文章推荐
使用Python装饰器在Django框架下去除冗余代码的教程
Apr 16 Python
Google开源的Python格式化工具YAPF的安装和使用教程
May 31 Python
用Python解决计数原理问题的方法
Aug 04 Python
通过5个知识点轻松搞定Python的作用域
Sep 09 Python
TensorFlow在MAC环境下的安装及环境搭建
Nov 14 Python
Python实现基于KNN算法的笔迹识别功能详解
Jul 09 Python
python适合人工智能的理由和优势
Jun 28 Python
Python pandas库中的isnull()详解
Dec 26 Python
基于Django集成CAS实现流程详解
Nov 28 Python
matplotlib部件之矩形选区(RectangleSelector)的实现
Feb 01 Python
Python中全局变量和局部变量的理解与区别
Feb 07 Python
python中 .npy文件的读写操作实例
Apr 14 Python
python——全排列数的生成方式
Feb 26 #Python
python GUI库图形界面开发之pyinstaller打包python程序为exe安装文件
Feb 26 #Python
python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例
Feb 26 #Python
python自动点赞功能的实现思路
Feb 26 #Python
python GUI库图形界面开发之PyQt5时间控件QTimer详细使用方法与实例
Feb 26 #Python
python GUI库图形界面开发之PyQt5窗口控件QWidget详细使用方法
Feb 26 #Python
python GUI库图形界面开发之PyQt5窗口类QMainWindow详细使用方法
Feb 26 #Python
You might like
PHP 替换模板变量实现步骤
2009/08/24 PHP
一个加载js文件的小脚本
2007/06/28 Javascript
js自定义事件及事件交互原理概述(二)
2013/02/01 Javascript
JavaScript实现QueryString获取GET参数的方法
2013/07/02 Javascript
jquery 倒计时效果实现秒杀思路
2013/09/11 Javascript
jquery实现浮动的侧栏实例
2015/06/25 Javascript
JavaScript实现简单获取当前网页网址的方法
2015/11/09 Javascript
浅谈JSON.stringify()和JOSN.parse()方法的不同
2016/08/29 Javascript
CSS3 media queries结合jQuery实现响应式导航
2016/09/30 Javascript
微信小程序 image组件binderror使用例子与js中的onerror区别
2017/02/15 Javascript
浅谈Vue路由快照实现思路及其问题
2018/06/07 Javascript
webpack + vue 打包生成公共配置文件(域名) 方便动态修改
2019/08/29 Javascript
python获取本地计算机名字的方法
2015/04/29 Python
python基于右递归解决八皇后问题的方法
2015/05/25 Python
python 捕获 shell/bash 脚本的输出结果实例
2017/01/04 Python
Python的标准模块包json详解
2017/03/13 Python
Python自动生产表情包
2017/03/17 Python
Python Tkinter实现简易计算器功能
2018/01/30 Python
python面向对象实现名片管理系统文件版
2019/04/26 Python
Python将文字转成语音并读出来的实例详解
2019/07/15 Python
Flask框架学习笔记之消息提示与异常处理操作详解
2019/08/15 Python
python实现把二维列表变为一维列表的方法分析
2019/10/08 Python
Python装饰器使用你可能不知道的几种姿势
2019/10/25 Python
PyQt5连接MySQL及QMYSQL driver not loaded错误解决
2020/04/29 Python
简单了解如何封装自己的Python包
2020/07/08 Python
python中二分查找法的实现方法
2020/12/06 Python
python 使用xlsxwriter循环向excel中插入数据和图片的操作
2021/01/01 Python
美国护肤咨询及美容产品电商:Askderm
2017/02/24 全球购物
全球最大的在线橄榄球商店:Lovell Rugby
2018/05/20 全球购物
社区学雷锋活动策划方案
2014/01/30 职场文书
中药学专业毕业生推荐信
2014/07/10 职场文书
劳动仲裁撤诉申请书
2015/05/18 职场文书
谢师宴学生答谢词
2015/09/30 职场文书
导游词之张家口
2019/12/13 职场文书
2019年12月24日平安夜祝福语集锦
2019/12/24 职场文书
Python中Matplotlib的点、线形状、颜色以及绘制散点图
2022/04/07 Python