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脚本实现查找webshell的方法
Jul 31 Python
总结Python编程中函数的使用要点
Mar 20 Python
Python书单 不将就
Jul 11 Python
Python2.7下安装Scrapy框架步骤教程
Dec 22 Python
python奇偶行分开存储实现代码
Mar 19 Python
Python assert语句的简单使用示例
Jul 28 Python
python 读取数据库并绘图的实例
Dec 03 Python
Python内建序列通用操作6种实现方法
Mar 26 Python
解决numpy矩阵相减出现的负值自动转正值的问题
Jun 03 Python
python 最简单的实现适配器设计模式的示例
Jun 30 Python
python+selenium 简易地疫情信息自动打卡签到功能的实现代码
Aug 22 Python
python开发入门——set的使用
Sep 03 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
ThinkPHP中处理表单中的注意事项
2014/11/22 PHP
php获取图片信息的方法详解
2015/12/10 PHP
javascript CSS画图之基础篇
2009/07/29 Javascript
页面只有一个text的时候,回车自动submit的解决方法
2010/08/12 Javascript
javascript 兼容所有浏览器的DOM扩展功能
2012/08/01 Javascript
扩展js对象数组的OrderByAsc和OrderByDesc方法实现思路
2013/05/17 Javascript
无限树Jquery插件zTree的常用功能特性总结
2014/09/11 Javascript
SuperSlide标签切换、焦点图多种组合插件
2015/03/14 Javascript
jQuery实现根据类型自动显示和隐藏表单
2015/03/18 Javascript
Ajax清除浏览器js、css、图片缓存的方法
2015/08/06 Javascript
JavaScript判断按钮被点击的方法
2015/12/13 Javascript
JavaScript对Json的增删改属性详解
2016/06/02 Javascript
微信小程序 合法域名校验出错详解及解决办法
2017/03/09 Javascript
JavaScript多线程运行库Nexus.js详解
2017/12/22 Javascript
实例讲解v-if和v-show的区别
2019/01/31 Javascript
基于Vue插入视频的2种方法小结
2019/04/02 Javascript
微信小程序HTTP请求从0到1封装
2019/09/09 Javascript
Vue实现浏览器打印功能的代码
2020/04/17 Javascript
[04:38]完美世界携手游戏风云打造 卡尔工作室饰品系统篇
2013/04/25 DOTA
[02:36]DOTA2英雄基础教程 帕格纳
2014/01/20 DOTA
Python天气预报采集器实现代码(网页爬虫)
2012/10/07 Python
Python实现国外赌场热门游戏Craps(双骰子)
2015/03/31 Python
python获取本机外网ip的方法
2015/04/15 Python
使用pip发布Python程序的方法步骤
2018/10/11 Python
python使用response.read()接收json数据的实例
2018/12/19 Python
python3 实现函数写文件路径的正确方法
2019/11/27 Python
Perricone MD裴礼康美国官网:抗衰老护肤品
2016/09/26 全球购物
REISS美国官网:伦敦最受欢迎的时尚品牌
2019/08/16 全球购物
商业融资计划书
2014/04/29 职场文书
汽车广告策划方案
2014/05/31 职场文书
工作目标责任书
2014/07/23 职场文书
python实现调用摄像头并拍照发邮箱
2021/04/27 Python
Oracle 区块链表创建过程详解
2021/05/15 Oracle
修改并编译golang源码的操作步骤
2021/07/25 Golang
「睡美人」爱洛公主粘土人开订
2022/03/22 日漫
Java由浅入深通关抽象类与接口(上篇)
2022/04/26 Java/Android