PyQt5-使用可视化设计窗体的GUI程序

Qt提供了可视化界面设计工具Qt Designer,以及Qt Creator中内置的UIDesigner。可视化地设计UI窗体可以大大提高GUI应用程序开发的工作效率。

工作步骤如下:

(1)在UI Designer中可视化设计窗体。
(2)用工具软件pyuic5将窗体文件(.ui文件)转换为Python程序文件。
(3)使用转换后的窗体的Python类构建GUI应用程序。

用UI Designer可视化设计窗体

Qt Creator的位置在

D:\python\Lib\site-packages\qt5_applications\Qt\bin

汉化包下载地址:

https://pan.baidu.com/s/1jzkumqjw-3VQiJxvo4V6Xw

在Qt Creator中点击菜单项“File”→“New File or Project...”,窗体模板如下:

image

· Dialog模板,基于QDialog类的窗体,具有一般对话框的特性,如可以模态显示、具有返回值等。
· Main Window模板,基于QMainWindow类的窗体,具有主窗口的特性,窗口上有主菜单栏、工具栏、状态栏等。
· Widget模板,基于QWidget类的窗体。QWidget类是所有界面组件的基类,如QLabel、QPushButton等界面组件都是从QWidget类继承而来的。QWidget类也是QDialog和QMainWindow的父类,基于QWidget类创建的窗体可以作为独立的窗口运行,也可以嵌入到其他界面组件内显示。

选择Widget模板。点击“Next”按钮后,在出现的对话框里设置文件名为Form.untitled,可以进行保存文件,创建了窗体后就可以在Qt Creator内置的UI Designer里可视化设计窗体。

image

下图是在窗体上放置了标签和按钮,并设置好各种属性后的界面。

image

UI Designer窗口有以下一些功能区域。

· 组件面板。窗口左侧是界面设计组件面板,分为多个组,如Layouts、Buttons、Display Widgets等,界面设计的常用组件都可以在组件面板里找到。

· 中间区域是待设计的窗体。如果要将某个组件放置到窗体上,从组件面板上拖动一个组件放到窗体上即可。例如,放一个Label组件和一个PushButton组件到窗体上。

· 对象浏览器(Object Inspector)。窗口右上方是对象浏览器,用树状视图显示窗体上各组件之间的布局和包含关系,视图有两列,显示每个组件的对象名称(objectName)和类名称。

· 属性编辑器(Property Editor)。窗口右下方是属性编辑器,显示某个选中的组件或窗体的各种属性及其值,可以在属性编辑器里修改这些属性的值。

在设计窗体上用鼠标点选一个组件,在属性编辑器里会显示其各种属性,并且可以修改其属性。例如,下图是选中窗体上放置的标签组件后属性编辑器的内容。

image

窗体以及各组件的主要属性设置

image

修改完之后是这样

image

窗体设计完成后,将这个窗体保存为文件FormHello.ui。

窗体文件FormHello.ui实际上是一个XML文件,它记录了窗体上各组件的属性以及位置分布。FormHello.ui的XML文件内容不必去深入研究,它是由UI Designer根据可视化设计的窗体自动生成的。

将ui文件编译为py文件

使用UI Designer设计好窗体并保存为文件FormHello.ui后,要在Python里使用这个窗体,需要使用PyQt5的工具软件pyuic5.exe将这个ui文件编译转换为对应的Python语言程序文件

pyuic5.exe程序位于Python安装目录的Scripts子目录下,如“D:\Python37\Scripts”,这个路径在安装Python时被自动添加到了系统的PATH环境变量里,所以可以直接执行pyuic5命令。

在Windows的cmd窗口里用cd指令切换到文件FormHello.ui所在的目录,然后用pyuic5指令编译转换为Python文件。例如,假设文件FormHello.ui保存在目录“D:\Project_Encyclopedia\img”下,依次执行下面的指令:

先进入路径

image

然后输入下面的指令

pyuic5 -o ui_FormHello.py FormHello.ui

image

其中,pyuic5的作用是将文件FormHello.ui编译后输出为文件ui_FormHello.py。编译输出的文件名可以任意指定,在原来的文件名前面加“ui_”是个人命名习惯,表明ui_FormHello.py文件是从FormHello.ui文件转换来的。

编译后在FormHello.ui文件所在的同目录下生成了一个文件ui_FormHello.py,用IDLE的文件编辑器打开这个文件,其内容如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'FormHello.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(517, 382)
        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(180, 210, 81, 31))
        self.pushButton.setObjectName("pushButton")
        self.label = QtWidgets.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(140, 130, 171, 41))
        font = QtGui.QFont()
        font.setFamily("Agency FB")
        font.setPointSize(20)
        self.label.setFont(font)
        self.label.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
        self.label.setObjectName("label")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "显示文字"))
        self.pushButton.setText(_translate("Form", "关闭"))
        self.label.setText(_translate("Form", "Hello, by Qt designer"))

分析这个文件的代码,可以发现这个文件实际上定义了一个类Ui_FormHello,仔细分析一下这段代码,可以发现其原理和功能。

(1)Ui_FormHello类的父类是object,而不是QWidget。

(2)Ui_FormHello类定义了一个函数setupUi(),其接口定义为:

def setupUi(self, Form):

其传入的参数有两个,其中self是函数自己,Python中的self类似于C++语言中的this; FormHello是一个传入的参数,而不是在Ui_FormHello类里定义的一个变量。

setupUi()函数的前两行语句是:

 Form.setObjectName("Form")
 Form.resize(517, 382)

所以,FormHello是窗体,是一个QWidget对象,其名称就是在UI Designer里设计的窗体的objectName。但是这个FormHello不是在类Ui_FormHello里创建的,而是作为一个参数传入的。

(3)创建了一个QLabel类型的对象LabHello,创建的语句是:

self.label = QtWidgets.QLabel(Form)

LabHello定义为Ui_FormHello类的一个公共属性(类似于C++的公共变量),它的父容器是Form,所以LabHello在窗体Form上显示。后面的语句又设置了LabHello的显示位置、大小,以及字体属性。

(4)创建了一个QPushButton类型的对象btnClose,创建的语句是

self.pushButton = QtWidgets.QPushButton(Form)

btnClose也是Ui_FormHello类的一个公共属性,它的父容器是Form,所以在窗体上显示。

所以,经过pyuic5编译后,FormHello.ui文件转换为一个对应的Python的类定义文件ui_FormHello.py,类的名称是Ui_FormHello。有如下的特点和功能。

(1)Ui_FormHello.py文件里的类名称Ui_FormHello与FormHello.ui文件里窗体的objectName有关,是在窗体的objectName名称前面加“Ui_”自动生成的。

(2)Ui_FormHello类的函数setupUi()用于窗体的初始化,它创建了窗体上的所有组件并设置其属性。

(3)Ui_FormHello类并不创建窗体FormHello,窗体FormHello是由外部传入的,作为所有界面组件的父容器。


注意:

注意 ui_FormHello.py文件只是定义了一个类Ui_FormHello,这个文件并不能直接运行,而是需要在其他地方编程使用这个文件里定义的类Ui_FormHello。


使用Ui_FormHello类的GUI程序框架

将窗体UI文件FormHello.ui编译转换为Python的类定义文件ui_FormHello.py后,就可以使用其中的类Ui_FormHello创建GUI应用程序。编写一个程序文件appMain1.py,它演示了使用Ui_FormHello类创建GUI应用程序的基本框架,其代码如下:

## 使用ui_FormHello.py文件中的类Ui_FormHello创建app
import sys
from PyQt5 import  QtWidgets
import  ui_FormHello

app=QtWidgets.QApplication(sys.argv) #创建app,用QApplication类
baseWidge=QtWidgets.QWidget() #创建窗体的基类Qwidget的实例

ui=ui_FormHello.Ui_Form()
ui.setupUi(baseWidge) #以baseWidget作为传递参数,创建完整窗体

baseWidge.show()
##ui. LabHello.setText("Hello,被程序修改") 可以修改窗体上标签的文字
sys.exit(app.exec())

分析上面的代码,可以了解GUI程序创建和运行的过程。

(1)首先用QApplication类创建了应用程序实例app。

(2)创建了一个QWidget类的对象baseWidget,它是基本的QWidget窗体,没有做任何设置。

(3)使用ui_FormHello模块中的类Ui_FormHello创建了一个对象ui。

(4)调用了Ui_FormHello类的setupUi()函数,并且将baseWidget作为参数传入:

ui.setupUi(baseWidge) #以baseWidget作为传递参数,创建完整窗体

根据前面的分析,Ui_FormHello类的setupUi()函数只创建窗体上的其他组件,而作为容器的窗体是靠外部传入的,这里的baseWidget就是作为一个基本的QWidget窗体传入的。执行这条语句后,就在窗体baseWidget上创建了标签和按钮。

(5)显示窗体,使用的语句是:

baseWidge.show()

注意,这里不能使用ui.show(),因为ui是Ui_FormHello类的对象,而Ui_FormHello的父类是object,根本就不是Qt的窗体界面类。

显示的结果如下:

image

那么现在有个问题,窗体上的标签、按钮对象如何访问呢?例如,若需要修改标签的显示文字,该如何修改呢?

分析一下程序,窗体上的标签对象label是在Ui_FormHello类里定义的公共属性,所以在程序里可以通过ui对象访问label。

对appMain1.py文件稍作修改,在baseWidget.show()语句后加入一条语句,如下(省略了前后的语句):

baseWidge.show()
ui.label.setText("Hello,被程序修改")

再运行appMain1.py,结果窗口如下图所示,说明上面修改标签文字的语句是有效的。

image

在上面的修改标签文字的语句中,不能将ui替换为baseWidget,即下面的语句是错误的:

baseWidget.label.setText("Hello,被程序修改") #错误的

这是因为baseWidget是QWidget类型的对象,它只是LabHello的父容器,并没有定义公共属性label,所以运行时会出错。而ui是Ui_FormHello类的实例对象,窗体上的所有界面组件都是ui的实例属性。因此,访问窗体上的界面组件只能通过ui对象。

界面与逻辑分离的GUI程序框架

分析前面的程序appMain1.py,虽然它实现了对Ui_FormHello类的使用,生成了GUI程序,但是它是存在一些缺陷的,原因在于appMain1.py完全是一个过程化的程序。它创建了Ui_FormHello类的对象ui,通过这个对象可以访问界面上的所有组件,所以,ui可以用于界面交互,获取界面输入,将结果输出到界面上。程序创建的baseWidget是QWidget类的对象,它不包含任何处理逻辑,而仅仅是为了调用ui.setupUi()函数时作为一个传入的参数。一般的程序是从界面上读取输入数据,经过业务处理后再将结果输出到界面上,那么这些业务处理的代码放在哪里呢?

appMain1.py的应用程序框架只适合测试单个窗体的UI设计效果,也就是仅能显示窗体。若要基于UI窗体设计更多的业务逻辑,由于appMain1.py是一个过程化的程序,难以实现业务逻辑功能的有效封装。

界面与业务逻辑分离的设计方法不是唯一的,这里介绍两种方法,一种是多继承方法,另一种是单继承方法。

单继承与界面独立封装方法

编写另一个程序appMain.py,其代码如下:

##appMain.py单继承方法,能更好地进行界面与逻辑的分离
import sys
from PyQt5.QtWidgets import  QWidget,QApplication
from ui_FormHello import Ui_Form

class QmyWidget(QWidget):
    def __init__(self, parent=None) :
        super().__init__(parent)  #调用父类构造函数,创建QWidget窗体
        self.__ui=Ui_Form() #创建UI对象
        self.__ui.setupUi(self) #构造UI
        self.Lab="单继承的QmyWidget"
        self.__ui.label.setText(self.Lab)

    def setBtnText(self,aText):
        self.__ui.pushButton.setText(aText)

if __name__ == "__main__":
    app = QApplication(sys.argv) # 创建app,用QApplication类
    myWidget = QmyWidget()
    myWidget.show()
    myWidget.setBtnText("间接设置")
    sys.exit(app.exec_())

这个程序的运行结果如下图所示。分析这段代码,可以看到以下几点。

image

(1)新定义的窗体业务逻辑类QmyWidget只有一个基类QWidget。

(2)在QmyWidget的构造函数中,首先调用父类(也就是QWidget)的构造函数,这样self就是一个QWidget对象。

(3)显式地创建了一个Ui_FormHello类的私有属性self.__ui,即

 self.__ui=Ui_Form() #创建UI对象

私有属性self.__ui包含了可视化设计的UI窗体上的所有组件,所以,只有通过self.__ui才可以访问窗体上的组件,包括调用其创建界面组件的setupUi()函数。


提示 Python语言的类定义通过命名规则来限定元素对外的可见性,属性或方法名称前有两个下划线表示是私有的,一个下划线表示模块内可见,没有下划线的就是公共的。


(4)由于self.__ui是QmyWidget类的私有属性,因此在应用程序中创建的QmyWidget对象myWidget不能直接访问myWidget.__ui,也就无法直接访问窗体上的界面组件。

为了访问窗体上的组件,可以在QmyWidget类里定义接口函数,例如函数setBtnText()用于设置窗体上按钮的文字。在应用程序里创建QmyWidget对象的实例myWidget,通过调用setBtnText()函数间接修改界面上按钮的文字,即

 myWidget.setBtnText("间接设置")

仔细观察和分析这种单继承的方式,发现它有如下特点。

(1)可视化设计的窗体对象被定义为QmyWidget类的一个私有属性self.__ui,在QmyWidget类的内部对窗体上的组件的访问都通过这个属性实现,而外部无法直接访问窗体上的对象,这更符合面向对象封装隔离的设计思想。

(2)窗体上的组件不会与QmyWidget里定义的属性混淆。例如,下面的语句:

  self.__ui.label.setText(self.Lab)

self.__ui.label表示窗体上的标签对象label,它是self.__ui的一个属性;self.Lab是QmyWidget类里定义的一个属性。这样,窗体上的对象和QmyWidget类里新定义的属性不会混淆,有利于界面与业务逻辑的分离。

(3)当然,也可以定义界面对象为公共属性,即创建界面对象时用下面的语句:

self.ui=Ui_Form()

这里的ui就是个公共属性,在类的外部也可以通过属性ui直接访问界面上的组件。

单继承方法更有利于界面与业务逻辑分离。实际上,在Qt C++应用程序中默认就是采用的单继承方法。

在这个示例中,窗口上虽然放置了一个按钮并显示“关闭”,但是运行时点击这个按钮并不能关闭窗口,这是因为我们还没有编写任何代码。这个示例只是为了演示如何在UI Designer里可视化设计UI窗体,再编译转换为对应的Python类,然后使用PyQt5里相关的类创建GUI应用程序的过程,以及GUI程序的框架和工作原理,

posted @ 2021-06-11 09:46  司砚章  阅读(516)  评论(0编辑  收藏  举报