基于PyQt5的图形界面程序设计 以冒泡排序为例 - Python多线程

本书同名免费MOOC《Python编程基础及应用》在哔哩哔哩(B站)热播,作者带着你学。


版权声明:本文内容引用自作者的图书《Python编程基础及应用》(高等教育出版社)。本文可以在互联网上转载传播,但必须包含文中的版权声明;本文不可以以纸质出版为目的进行摘抄或改编。

附录A.3. 实践 - 冒泡及轻者上浮

Python自带有标准GUI-图形用户界面工具包Tkinter。但Tkinter的功能相对比较简单,界面也不够漂亮,对于规模大一点的GUI应用略显不足。在当前的Python生态圈,如果读者需要一种功能较齐全、能满足大多数项目实践需要的GUI工具包,作者认为,PyQt5是当前最好的选择。

本章向读者介绍基于PyQt5的图形应用程序的框架及开发过程,以及分时操作系统的消息循环机制,还有多线程程序设计的基本概念和方法。冒泡排序不是重点。

A.3.1 开发环境准备

A.3.1.1 Qt

Qt - https://www.qt.io/是久负盛名的跨平台C++ GUI开发包及集成开发环境。它独特的信号-槽机制屏蔽了不同操作系统间的差异,使得用C++语言书写的应用程序可以在不同的操作系统(Windows, Linux如Ubuntu)下运行。由于它本身就是C++语言书写的,所以运行速度相对较快。经过多年的发展,Qt已经发展得比较成熟了,即便开发Android App,也可以在Qt上用C++完成。

PyQt则是对Qt的Python封装,它是由英国的RiverBank公司- https://riverbankcomputing.com开发的。另外还有一款名为Eric的IDE软件,它把Python, PyQt集成得非常好。作者曾在树莓派卡片电脑上直接用Eric开发基于PyQt的应用程序,该卡片电脑运行一种Linux的发布版本 - Raspbian。

需要注意的是,Qt主要执行LGPL授权协议而PyQt执行 GPL授权协议。如果读者试图在一个私有代码的商业软件中应用上述组件,可能需要付费。

A.3.1.2 PyQt安装

进入Windows命令行或者Linux的终端,通过pip工具安装pyqt5以及pyqt5-tools两个包。安装需要联网,并持续好几分钟,因为被安装的包是从网络软件仓库中实时下载的。

pip install pyqt5
pip install pyqt5-tools
A.3.1.3 Visual Studio Code配置

Qt/PyQt中包括一系列的工具,其中:

工具名称 用途 可执行文件/模块名称
Qt Designer 用即见即所得的方式设计图形界面,成果表现为扩展名为ui的文件。 designer
UI Compiler 将上述ui文件“编译”成Python程序。该Python程序帮助构建ui文件所描述的图形界面。 pyuic5
Qt Linguist 语言学家,可以便捷的实现软件的国际化,即生成软件的法语、英语、日语或者其它语种版本。工作模式大致可以描述成:先用pylupdate5扫描源代码中全部可翻译的字符串,然后用linguist翻译相应的字符串至目标语言,接下来用lrelease工具发布。软件运行时,加载法语版本的语言学家文件,软件界面就是法语,加载日语版本的语言学家文件,软件界面就是日语。 linguist,
pylupdate5,
lrelease
Resource Compiler 资源编译器。UI文件设计过程中可能需要使用到各种图片,这些图片以资源文件的形式组织,扩展名为qrc;资源编译器负责将 qrc格式的资源文件编译成py文件,其中,图片被转换成bytes-字节流。 pyrcc5

为了使用这些Qt工具,我们需要在Visual Studio Code中安装下述扩展或者其它类似功能的扩展并对扩展进行配置。Visual Studio Code上的扩展安装方法请回顾第一章相关内容。

在这里插入图片描述

安装完成后,上述扩展还需要进行配置才能使用,该扩展在Visual Studio Code中的配置及基本使用方法请参考下述链接:https://codelearn.club/2019/06/pyqtconfig/

A.3.2 简单PyQt图形应用

A.3.2.1 创建PyQt应用

在计算机里创建一个名为BubbleSort的文件夹,比如d:\pylearn\BubbleSort。在Visual Source Code中打开这个文件夹,然后创建一个名为SimpleQtApp.py的程序文件,内容如下:

#SimpleQtApp.py
import sys
from PyQt5 import QtWidgets,QtCore

app = QtWidgets.QApplication(sys.argv)
wdMain = QtWidgets.QWidget()
wdMain.setGeometry(200,200,800,600)
wdMain.setWindowTitle("GUI, Let's embrace the world!")

btnExit = QtWidgets.QPushButton('EXIT',wdMain)
btnExit.resize(200,80)
btnExit.move(300,300)
btnExit.clicked.connect(QtCore.QCoreApplication.quit)

wdMain.show()
r = app.exec_()
print("message loop ended.")
exit(r)

执行,得到第一个Qt图形应用的运行界面,用鼠标点一下EXIT按钮,程序运行结束。

在这里插入图片描述

A.3.2.2 分时系统与消息循环

上面这个SimpleQtApp.py行数并不多,但要彻底理解它的工作原理却并不容易。我们得从操作系统说起。现代操作系统都是分时系统,你的计算机同时在做很多事情:浏览器、Word、音乐播放器… 读者如果打开Windows任务管理器,可以看到数十至数百个进程 - process在“同时”运行。而一个进程 ,可能又是由多个线程 - thread组成的。比如,当你的浏览器进程试图从网站上下载一个大文件时,它可能会创建一个单独的线程来下载文件,而原有的主线程则随时待命,及时处理你的命令:输入网址,点击超链接等等。

  • 线程竞争性地使用CPU资源

操作系统管理着CPU,将CPU的时间切割成非常小的时间片。它按照效率与公平兼顾的原则将时间片分配给线程,线程获得时间片后,将执行相应的运算或其它操作。时间片用完后, 操作系统会收回CPU,将时间片分给其它线程;上述时间片的分配和回收对于应用程序而言是透明的,也就是应用程序根本不知道也无法预测或者控制时间片的获得与丧失。当一个线程被被剥夺时间片时,操作系统会保存好执行现场,包括CPU内各个寄存器的值,然后线程就挂起 - suspended。当这个被挂起的线程重新获得时间片时,操作系统会先恢复执行现场,然后通过跳转指令恢复线程的执行。由于时间片的轮转速度非常快,所以,使用者一般感觉不到应用程序的这种间断执行,似乎应用程序拥有一个“专享”的CPU。

计算机通常只有一个键盘、一个鼠标。所以我们不能认为键盘和鼠标是属于WORD的,还是浏览器的。这些应用程序在共同使用这些外部设备,计算机的系统软件-操作系统在管理这些外设资源,包括输入设备如键盘鼠标,也包括输出设备,如显示器/显卡等。

  • 操作系统负责管理键盘鼠标并将键盘鼠标的输入分发给对应的进程

假设桌面上同时有两个窗口,如下图。这里操作者如果敲下一个键,比如c,那么首先获悉这个事件的,肯定是操作系统,因为操作系统监视着键盘输入。问题是,当操作系统获悉这个事件后,将这个事件分发给哪个应用程序呢? 是前面的浏览器还是后面的文字编辑器呢? 显然,操作系统遵循谁有焦点(focus),就分发给谁的规则。事实上,所有的应用程序,它的窗口大小、窗口位置等信息都是向操作系统登记备案的,依据这些信息,操作系统决定信息的去向。为了更好的分发这些消息,操作系统会为每一个进程创建消息队列,凡是有发往这个进程的消息,操作系统就把这个消息放在对应的队列里。而应用程序的进程,则不断地从队列获取消息并处理,作出恰当的反应。应用程序不断读取消息队列并处理消息的机制称为消息循环

在这里插入图片描述

​ 总结,在分时操作系统下,一个图形应用程序的执行框架可以大致用下图刻画。

在这里插入图片描述

可以看到,图形应用程序启动后,在完成初始化,向操作系统注册,显示主窗口等任务后,即进入一个消息循环:周而复始的从操作系统的消息队列中获取分发给自己的消息并进行处理。比如,用户按了某个按钮,应用程序在收到这个消息后将执行对应的处理函数,以响应用户的要求。如果用户按下的是主窗口的关闭按钮(即窗口右上角的X),应用程序在收到这个消息后通常会退出消息循环,执行结束。

A.3.2.3 示例解读

现在可以尝试解释本节的PyQt图形应用的代码了。

from PyQt5 import QtWidgets,QtCore

PyQt的包,我们主要用到三个,分别是:

包/模块名 说明
QtWidgets 包括一系列GUI部件,比如QMindow、QDialog等。
QtGui 包括窗口集成、事件处理、2D绘图、字体和文本等GUI元素。
QtCore 包括时间、文件及目录处理、数据类型、数据流、进程/线程等功能,属于非GUI的核心模块。

除此之外,还有一些模块:QtNetwork用于网络通信;QtSql用于关系数据库访问;QtWebKit则支持内置的网络浏览器;QtMultimedia则用于支持多媒体。

app = QtWidgets.QApplication(sys.argv)
...
r = app.exec_()
print("message loop ended.")
exit(r)

所有的Qt图形应用程序都需要创建一个QtWidgets.QApplication对象,这个对象将负责进程的消息循环和分发。sys.argv是程序启动时的命令行参数。app.exec_()函数的实质就是应用程序的消息循环,它周而复始地从操作系统消息队列中获取用户消息/指令,然后把这些消息/指令按照Qt特有的信号-槽( signal - slot)机制分发给对应的处理函数进行处理,并做作适当响应。通常,当应用程序的主窗口被关闭后,app.exec_()函数将退出消息循环,并返回一个值表明应用的执行结果,这个值通常表明程序在执行过程中有没有出错。

读者可以再运行一次这个简单的只有一个按钮的图形应用。请注意,只有当你点击EXIT按钮,主窗口关闭后,上述print(“message loop ended.”)消息才会输出到控制台。这证明,app.exec_()函数真的是在进行消息"死"循环,它将程序“卡”在这里。

app.exec_()执行结束后,exit()函数退出Python解释器,参数r被返回给操作系统表明程序运行结果。

wdMain = QtWidgets.QWidget()
wdMain.setGeometry(200,200,800,600)
wdMain.setWindowTitle("GUI, Let's embrace the world!")

btnExit = QtWidgets.QPushButton('EXIT',wdMain)
btnExit.resize(200,80)
btnExit.move(300,300)
btnExit.clicked.connect(QtCore.QCoreApplication.quit)

wdMain.show()

中间这段代码先是创建了一个QtWidgets.QWidget对象作为应用程序的主窗口。Widget这个词大致就是窗口(Window)的另一种写法。setGeometry()方法显然设置了这个窗口在桌面上的呈现位置(左上角坐标)和像素单位长宽尺寸。setWindowTitle()则设置了窗口的标题。大多数情况下,Qt的类名、函数都具有良好自解释特性,看到名字大概就能猜出其功能。

接下来,btnExit是一个QtWidgets.QPushButton对象,就是一个按钮控件。QtWidgets.QPushButton(‘EXIT’,wdMain)的第一个参数表示这个按钮的标题,而参数wdMain表明了这个按钮的父控件,也就是按钮的拥有者是wdMain主窗口。resize()函数设定了按钮的尺寸,move()函数将按钮移动到窗口内部坐标(300,300)的位置。请注意,在GUI应用中,坐标系通常是top-left坐标系,以窗口的左上角为原点,向右x为正,向下y为正。

btnExit.clicked.connect(QtCore.QCoreApplication.quit)这一行最为关键。clicked为btnExit对象的属性,它是一个信号(signal),而QtCore.QCoreApplication.quit可以认为是一个特殊的回调函数,称之为槽(slot),它的功能大致是结束应用程序的运行。clicked.connect()函数则将信号clicked与槽关联起来,结果就是:当主窗口wdMain内的btnExit按钮被点击时,操作系统监控到鼠标的动作,然后将这一事件打包成一个消息,放至该应用程序的消息队列;app.exec_()内部的消息循环得到这一消息后,在内部将其处理成btnExit的clicked信号,根据信号-槽的关联,QtCore.QCoreApplication.quit槽方法被执行,跳出消息循环,程序结束。

上述代码只是"徒手"创建了wdMain窗口,而wdMain.show()函数的执行才真正将其显示出来。接下来就是app.exec_()的消息循环。

A.3.3 世界主要工业国GDP排名

接下来,我们要编写一个图形应用程序,使用冒泡排序来对世界主要工业国的GDP进行排名,并演示冒泡排序的执行过程。在本书配套的网站上,你可以下载到本实践的全部代码和数据。在完成本章第一节的环境准备工作后,你应该可以打开并运行该实例。请对照代码阅读本章后续内容。 该实例的运行结果大致如下图。

在这里插入图片描述

A.3.4 数据及基础结构

A.3.4.1 数据文件

名为BubbleSort的项目目录内有一个名为countries.ini的文件,其中包括了2017年世界前15位的工业国家的GDP数据,下表列出了该文件的前几行。可见,基本数据包括国家名称,GDP值(以十亿(billion)美元为单位),以及这个国家的国旗图片文件名称。

[Countries]
countries.size = 15
countries[0].sName = United States
countries[0].fGdp = 19555.874
countries[0]
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值