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之画圈还不简单吗?
Sep 20 Python
举例详解Python中yield生成器的用法
Aug 05 Python
在Python中使用正则表达式的方法
Aug 13 Python
Python函数式编程
Jul 20 Python
python Crypto模块的安装与使用方法
Dec 21 Python
分析Python读取文件时的路径问题
Feb 11 Python
计算机二级python学习教程(2) python语言基本语法元素
May 16 Python
docker-py 用Python调用Docker接口的方法
Aug 30 Python
Transpose 数组行列转置的限制方式
Feb 11 Python
Python requests模块session代码实例
Apr 14 Python
Python如何实现的二分查找算法
May 27 Python
python3 sqlite3限制条件查询的操作
Apr 07 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用户指南-cookies部分
2006/10/09 PHP
解析php中获取url与物理路径的总结
2013/06/21 PHP
在PHP上显示JFreechart画的统计图方法
2013/11/03 PHP
php导入大量数据到mysql性能优化技巧
2014/12/29 PHP
PHP环境中Memcache的安装和使用
2015/11/05 PHP
php实现当前页面点击下载文件的实例代码
2016/11/16 PHP
php脚本守护进程原理与实现方法详解
2017/07/20 PHP
PHP大文件切割上传功能实例分析
2019/07/01 PHP
使一个函数作为另外一个函数的参数来运行的javascript代码
2007/08/13 Javascript
javascript delete 使用示例代码
2010/03/29 Javascript
javascript验证身份证号
2015/03/03 Javascript
javascript结合Flexbox简单实现滑动拼图游戏
2016/02/18 Javascript
jQuery实现简单弹窗遮罩效果
2017/02/27 Javascript
详解vue2路由vue-router配置(懒加载)
2017/04/08 Javascript
JS ES6中setTimeout函数的执行上下文示例
2017/04/27 Javascript
jQuery validata插件实现方法
2017/06/25 jQuery
原生JavaScript来实现对dom元素class的操作方法(推荐)
2017/08/16 Javascript
Vue实现数字输入框中分割手机号码的示例
2017/10/10 Javascript
详解如何使用webpack在vue项目中写jsx语法
2017/11/08 Javascript
jQuery实现下拉菜单动态添加数据点击滑出收起其他功能
2018/06/14 jQuery
JavaScript正则表达式验证登录实例
2020/03/18 Javascript
Vue 解决父组件跳转子路由后当前导航active样式消失问题
2020/07/21 Javascript
vue项目开启Gzip压缩和性能优化操作
2020/10/26 Javascript
[01:35]2014DOTA2西雅图邀请赛 专访狐狸妈青春献给刀塔
2014/07/08 DOTA
python快速建立超简单的web服务器的实现方法
2018/02/17 Python
Pyecharts 动态地图 geo()和map()的安装与用法详解
2020/03/25 Python
html5 postMessage前端跨域并前端监听的方法示例
2018/11/01 HTML / CSS
英国家电直销:Appliances Direct
2016/09/22 全球购物
大学生个人简历中的自我评价
2013/12/27 职场文书
优秀教师的感人事迹
2014/02/04 职场文书
收银员岗位职责
2015/02/03 职场文书
综合办公室主任岗位职责
2015/04/01 职场文书
文艺部部长竞选稿
2015/11/21 职场文书
2019年世界儿童日宣传标语
2019/11/22 职场文书
MySQL查询学习之基础查询操作
2021/05/08 MySQL
python文本处理的方案(结巴分词并去除符号)
2021/05/26 Python