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中用memcached来减少数据库查询次数的教程
Apr 07 Python
Python3写入文件常用方法实例分析
May 22 Python
编写Python脚本把sqlAlchemy对象转换成dict的教程
May 29 Python
Python利用multiprocessing实现最简单的分布式作业调度系统实例
Nov 14 Python
Python解决抛小球问题 求小球下落经历的距离之和示例
Feb 01 Python
Python爬虫的两套解析方法和四种爬虫实现过程
Jul 20 Python
Python Unittest根据不同测试环境跳过用例的方法
Dec 16 Python
python使用knn实现特征向量分类
Dec 26 Python
python实现QQ邮箱/163邮箱的邮件发送
Jan 22 Python
为什么python比较流行
Jun 19 Python
tensorflow 2.0模式下训练的模型转成 tf1.x 版本的pb模型实例
Jun 22 Python
python库sklearn常用操作
Aug 23 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 zend解密软件绿色版测试可用
2008/04/14 PHP
遭遇php的in_array低性能问题
2013/09/17 PHP
php mysql_real_escape_string函数用法与实例教程
2013/09/30 PHP
PHP使用ODBC连接数据库的方法
2015/07/18 PHP
php微信公众平台交互与接口详解
2016/11/28 PHP
php在windows环境下获得cpu内存实时使用率(推荐)
2018/02/08 PHP
php实现等比例压缩图片
2018/07/26 PHP
Jquery带搜索框的下拉菜单
2013/05/06 Javascript
js自动查找select下拉的菜单并选择(示例代码)
2014/02/26 Javascript
基于jQuery实现的旋转彩圈实例
2015/06/26 Javascript
微信小程序-图片、录音、音频播放、音乐播放、视频、文件代码实例
2016/11/22 Javascript
基于jQuery实现弹幕APP
2017/02/10 Javascript
Vuejs 组件——props数据传递的实例代码
2017/03/07 Javascript
jQuery插件FusionCharts绘制的2D条状图效果【附demo源码】
2017/05/13 jQuery
JQuery 获取Dom元素的实例讲解
2017/07/08 jQuery
通过JavaScript下载文件到本地的方法(单文件)
2019/03/17 Javascript
使用 Vue cli 3.0 构建自定义组件库的方法
2019/04/30 Javascript
详解Vue中CSS样式穿透问题
2019/09/12 Javascript
转换layUI的数据表格中的日期格式方法
2019/09/19 Javascript
JavaScript事件冒泡机制原理实例解析
2020/01/14 Javascript
Python 深入理解yield
2008/09/06 Python
python解析html提取数据,并生成word文档实例解析
2018/01/22 Python
对python添加模块路径的三种方法总结
2018/10/16 Python
python批量修改文件夹及其子文件夹下的文件内容
2019/03/15 Python
python3实现斐波那契数列(4种方法)
2019/07/15 Python
Python搭建代理IP池实现检测IP的方法
2019/10/27 Python
python邮件中附加文字、html、图片、附件实现方法
2021/01/04 Python
Canvas globalCompositeOperation
2018/12/18 HTML / CSS
日本小田急百货官网:Odakyu
2018/07/19 全球购物
澳大利亚玩具剧场:Toy Playhouse
2019/03/03 全球购物
学校消防演习方案
2014/02/19 职场文书
校企合作协议书
2014/04/16 职场文书
小学生运动会报道稿
2014/09/12 职场文书
村干部四风问题整改措施
2014/09/30 职场文书
销售会议开幕词
2015/01/28 职场文书
运动会报道稿大全
2015/07/23 职场文书