2.5 资源文件的使用
2.5.1 功能概述
到目前为止,我们已经介绍了窗体可视化设计的PyQt5 GUI应用程序框架,以及Qt核心技术信号与槽的使用方法,熟悉窗体可视化设计和常用界面组件使用的读者完全可以开始编写自己的GUI应用程序了。但还有一个技术点需要解决,就是资源文件的使用。例如,在示例Demo2_3的窗体上(图2-10)的按钮都没有设置图标,而图标的使用是GUI程序不可或缺的一项功能。
本节介绍如何在PyQt5 GUI应用中使用资源文件,包括如何在Qt Creator中创建和管理资源文件,如何在窗体可视化设计时为按钮设置图标,以及如何将Qt的资源文件通过pyrcc5工具软件编译为Python文件。
本节仍然结合2.4节的程序文件human.py创建一个示例Demo2_5,示例运行时界面如图2-27所示。通过这个示例可以掌握资源文件的使用方法,加深对GUI应用程序的设计流程、窗体布局可视化设计、自定义信号与槽函数的使用等内容的理解。
图2-27 示例Demo2_5运行时界面
掌握这个应用程序的设计方法后,就基本掌握了PyQt5设计GUI应用程序的基本流程和关键技术了,再继续学习就是学习更多的PyQt5的类的使用方法了。就如同你已经精通了英语语法,剩下的只是增加单词量的问题了。
2.5.2 窗体可视化设计
在Demo2_5的项目目录下新建一个Qt Widgets Application项目QtApp,创建窗体时选择窗体基类为QWidget,新建窗体类的名称设置为默认的Widegt。创建项目后,对窗体Widget.ui进行可视化设计,设计好的窗体如图2-28所示。
图2-28 可视化设计完成的窗体Widget.ui
该窗体在设计时采用了布局管理方法。“年龄设置”分组框用的是组件面板Container分组里的GroupBox组件,其内部组件按网格状布局;“姓名设置”分组框里的组件也按网格状布局;最下方的按钮和多个空格组件使用了组件面板Container分组里的Frame组件作为容器,采用水平布局。窗口的主布局采用垂直布局。
窗体上所有组件的层次关系如图2-29所示,图2-29还显示了各个组件的objectName及其所属的类。用于设置年龄的是一个QSlider组件,在属性编辑器里设置其minimum属性为0, maximum属性为100。
图2-29 窗体上的组件的层次关系、objectName及其所属类
2.5.3 创建和使用资源文件
在Qt Creator里单击“File”→“New File or Project…”菜单项,在新建文件与项目对话框里选择“Qt Resource File”,然后按照向导的指引设置资源文件的文件名,并添加到当前项目里。
本项目创建的资源文件名为res.qrc。在项目文件目录树里,会自动创建一个与Headers、Sources、Forms并列的Resources文件组,在Resources组里有res.qrc节点。在res.qrc节点上点击鼠标右键,在弹出的快捷菜单中选择“Open in Editor”打开资源文件编辑器(如图2-30所示)。
图2-30 资源文件编辑
资源文件最主要的功能是存储图标和图片文件,以便在程序里使用。在资源文件里首先建一个前缀(Prefix),例如icons,方法是在图2-30窗口右下方的功能区单击“Add”按钮下的“Add Prefix”,设置一个前缀名为icons,前缀就是资源的分组。
然后再单击“Add”按钮下的“Add Files”选择图标文件。如果所选的图标文件不在本项目的子目录里,会提示复制文件到项目下的子目录里。在QtApp项目的目录下建一个子目录\images,将所有图标文件放置在这个文件夹里。在图2-30的前缀和图标文件目录已设置的情况下,如果要在代码里使用其中的app.ico图标文件,其引用名称是“:/icons/images/app.ico”。
将图标导入到资源文件里后,就可以在窗体设计时使用图标了。例如,在图2-28中要设置“关闭”按钮的图标,在属性编辑器中有icon属性,点击右侧下拉菜单中的“Choose Resource...”,就可以在项目的资源文件里为按钮选择图标了(如图2-31所示)。
图2-31 为按钮在资源文件里选择图标
2.5.4 窗体文件和资源文件的编译
在Qt Creator里设计的资源文件要在Python程序里使用,需要使用pyrcc5.exe工具软件将资源文件res.qrc编译为一个对应的Python文件res_rc.py。在Demo2_5目录下执行编译的指令如下:
pyrcc5 .\QtApp\res.qrc -o res_rc.py
该指令将\QtApp目录下的res.qrc进行编译,输出文件res_rc.py到Demo2_5目录下,编译后的资源文件名必须是原文件名后面加“_rc”。
不能先将文件QtApp\res.qrc复制到Demo2_5目录下之后再编译,因为res.qrc需要查找其子目录\images下的文件,复制后相对位置变化了,编译时会找不到图标文件。
同样还需要编译窗体文件Widget.ui。于是在Demo2_5目录下建一个批处理文件uic.bat,用于执行这两条编译指令,文件uic.bat内容如下:
echo off rem将子目录QtApp下的.ui文件复制到当前目录下,并且编译 copy .\QtApp\Widget.ui \Widget.ui pyuic5-o ui_Widget.py \Widget.ui rem编译并复制资源文件 pyrcc5 .\QtApp\res.qrc -o res_rc.py
双击执行这个批处理文件,就同时编译了窗体文件和资源文件。
可以打开资源文件res.qrc编译后的文件res_rc.py,查看其内容。res_rc.py文件里存储了图标的十六进制编码数据,以及相关的管理代码。
窗体文件编译后的文件是ui_Widget.py,由于这个窗体使用了资源文件,在此文件的最后自动加入了一行import语句,即
import res_rc
文件ui_Widget.py里定义了一个类Ui_Widget,其setupUi()函数是构建窗体界面的代码。如果有兴趣研究代码化构建窗体界面的原理,或需要参考其中的代码,例如布局管理的代码、使用图标的代码,可以查看此文件的内容。
2.5.5 窗体业务逻辑类的设计
将示例Demo2_4创建的文件human.py复制到本示例目录下。采用单继承方法设计一个窗体业务逻辑类QmyWidget,保存为文件myWidget.py,该文件的完整内容如下:
import sys from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtCore import pyqtSlot from PyQt5.QtGui import QIcon from ui_Widget import Ui_Widget from human import Human class QmyWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数 self.ui=Ui_Widget() #创建UI对象 self.ui.setupUi(self) #构造UI self.boy=Human("Boy",16) self.boy.nameChanged.connect(self.do_nameChanged) self.boy.ageChanged.connect(self.do_ageChanged_int) self.boy.ageChanged[str].connect(self.do_ageChanged_str) ##=====由connectSlotsByName() 自动与组件的信号关联的槽函数===== def on_sliderSetAge_valueChanged(self, value): self.boy.setAge(value) def on_btnSetName_clicked(self): hisName=self.ui.editNameInput.text() self.boy.setName(hisName) ##=======自定义槽函数======= def do_nameChanged(self, name): self.ui.editNameHello.setText("Hello, "+name) @pyqtSlot(int) def do_ageChanged_int(self, age): self.ui.editAgeInt.setText(str(age)) @pyqtSlot(str) def do_ageChanged_str(self, info): self.ui.editAgeStr.setText(info) if __name__ == "__main__": ##用于当前窗体测试 app = QApplication(sys.argv) icon = QIcon(":/icons/images/app.ico") app.setWindowIcon(icon) form=QmyWidget() form.show() sys.exit(app.exec_())
QmyWidget的构造函数创建了一个Human类的实例self.boy,并且将其3个信号分别与3个自定义槽函数关联,这3个自定义槽函数的功能是在窗体界面上显示相关信息。
QmyWidget类还定义了两个可以由connectSlotsByName()自动创建连接的槽函数。一个是界面组件sliderSetAge的valueChanged(int)信号的槽函数,其函数名称是on_sliderSetAge_valueChanged(),这个函数名是在UI Designer里用Go to slot对话框自动生成的(生成方法参见2.3.7节)。窗体上的组件sliderSetAge的滑块滑动时触发执行这个槽函数,其响应代码self.boy.setAge (value)又会使self.boy发射两个ageChanged()信号,与其关联的两个自定义槽函数do_ageChanged_int()和do_ageChanged_str()会被执行,从而在窗体上显示信息。另一个是组件btnSetName的clicked()信号的槽函数on_btnSetName_clicked(),其执行过程类似。
2.5.6 为应用程序设置图标
可以为应用程序设置一个图标,这样,应用程序的每个窗体将自动使用这个图标作为窗体的图标。在myWidget.py文件的测试程序部分添加设置应用程序图标的代码:
app = QApplication(sys.argv) #创建GUI应用程序 icon = QIcon(":/icons/images/app.ico") app.setWindowIcon(icon)
就是从资源文件里提取了一个图标作为应用程序的图标。当然,也可以使用QWidget的setWindowIcon()函数为一个窗体单独设置图标。