痞子衡开博客至今已有好几年,一直以嵌入式开发相关主题的文章为主线,偶尔穿插一些其他技术或工具的介绍,前段时间因为要做一个跟恩智浦 MCU 启动相关的上位机工具 NXP-MCUBootUtility,网上搜索对比了几个 Python 下的 GUI 框架,最终选择了 wxPython 这个成熟稳定的 GUI 库,从而接触到 wxFormBuilder 这个配套 wxPython 使用的 GUI 构建工具。

 

苦于网上关于该构建工具的中文资料不多,所以根据自己使用经验写了一篇 《极易上手的可视化 wxPython GUI 构建工具(wxFormBuilder)》,没想到该篇博客很受欢迎,居然目前是痞子衡博客里阅读量最高的一篇博客,而且也是搜索 wxFormBuilder 关键字出来的中文结果排名第二位的链接,真是万万没想到。

  

wxPython 框架虽然成熟稳定,但是相对最近更火的 PyQt 框架来说,还是显得古老了一些,控件风格不符合现代审美观,因此痞子衡决定学习一下 PyQt 的用法,感受下 PyQt 做出来的界面效果到底如何。根据 wxPython 学习经验,当然首先要从 PyQt 的可视化 GUI 构建工具 Qt Designer 开始下手,因此便有了本篇博客。

 

一、Qt Designer 工具背景

Qt Designer 从名字上来看显然就是久负盛名的跨平台 GUI 库 Qt 的配套设计工具。Qt 库本身是 C++语言实现的;Riverbank 公司用 Python 语言对 Qt 做了一层封装,封装后便成了 Python 版 GUI 库 PyQt(目前最新的版本是 PyQt5);下面是这两个 GUI 库的官方主页:

 

Qt 项目官方网站: https://www.qt.io/


PyQt 项目官方主页: https://www.riverbankcomputing.com/software/pyqt/intro
  

Qt 的各种 UI 控件功能均是通过 class 来实现的,这个链接 https://doc.qt.io/qt-5/classes.html 列出了 Qt 里的所有 class。PyQt5 其用法基本与 Qt 一致,这个链接 https://www.riverbankcomputing.com/static/Docs/PyQt5/module_index.html#ref-module-index 列出了 PyQt5 里所有的 Modules,其中用于设计界面最常用的便是 QtWidgets 模块。

  

在 Qt 官网的 Tools 下面可以看到所有 Qt 相关的工具,在 UI design tools 下面可以找到 Qt Designer,可见 Qt Designer 是用于设计 GUI 界面的工具之一。由于痞子衡介绍的 PyQt5 下的 GUI 构建工具,因此本文的 Qt Designer 并不是直接在 Qt 官网下载安装的,具体安装方法详见下一章节。

 

 

二、Qt Designer 快速上手

使用 Qt Designer 去设计 GUI 界面可以不用掌握 PyQt5 里的各个控件 class 的具体用法,你只需要在 Qt Designer 软件里添加这些控件即可,下面痞子衡将简介 Qt Designer 的用法:

 

2.1 软件安装

简单了解 PyQt5 的 module 和 class 便可以开始设计 GUI 界面,首先得安装 Qt Designer,在安装完 Python3 之后(痞子衡安装的是 Python 3.6),借助 \Python36\Scripts\ 下的 pip.exe 工具来分别安装 PyQt5 和 Qt Designer,命令见如下主页:

 

PyQt5 安装: https://pypi.org/project/PyQt5/


Qt Designer 安装: https://pypi.org/project/pyqt5-tools/
  

安装完成之后打开 \Python36\Lib\site-packages\pyqt5_tools\designer.exe,这便是 Qt Designer。

 

2.2 软件界面

打开 Qt Designer 可见到如下界面,界面主要分为四大区:项目区、控件区、编辑区、属性区。软件使用起来非常简单,就是在【控件区】里点击添加需要的控件,这些控件的效果会在【编辑区】里实时显示,并在【属性区】这些控件的属性,【项目区】用于显示控件间的层级关系。

 

 

2.3 基础布局

让我们开始创建一个 GUI 的基础框架,基础框架包括:Container(局部外围轮廓)、Layout(内部控件区)、menubar(顶部菜单栏)、statusbar(底部状态栏)。
  

第一步是添加一个 Container(此处选择常用的 Frame),这是 GUI 的轮廓基础,有了 Frame 之后还需要在 Frame 里添加 Layout(此处选择竖排样式),用于规范后续控件的排列样式。默认 GUI 即有 menubar 和 statusbar。

 

 

2.4 多种控件

基础布局搞定之后,接下来便是在 Layout 里添加控件,PyQt5 支持的控件非常丰富,其中比较常用的是如下几个:各种 Button(按钮)、Label(静态显示文本框)、Text Edit(输入输出文本框)、Check Box(选中框)、各种 Slider(滑动条)等。由于前面痞子衡选择的是 verticalLayout,因此你会看到控件们都是竖着排的。

 

 

2.5 控件属性

添加了所有控件之后,下一步便是分别设置控件的属性,进一步调整控件。痞子衡以 Push Button 属性为例,痞子衡勾选了如下 3 项比较重要的属性设置,分别是 objectName(button 在后续 python 代码的对象名,一般需要按其功能修改,修改后使得代码阅读 / 修改起来更直观)、geometry(设置 button 的尺寸与位置,如果是放在 Layout 里,则受限于 Layout 不可设置)、text(button 在 GUI 里显示的标签名,此处是 PushButton,也需要按其功能修改,方便用户使用软件)。

 

 

2.6 保存为 xml 代码(工程文件)

当 GUI 界面布局全部完成之后,需选择 File->Save As 保存为 .ui 文件,该文件既是 Qt Designer 的工程文件也是最终生成的 GUI xml 代码文件,痞子衡保存在了 my_win.ui 文件里。

 

 

2.7 转换成 python 代码

虽然保存的 my_win.ui 文件里是可以直接在 python 代码里被加载使用的,但是更好的办法是直接将 .ui 文件转换成相应的 .py 文件。需要借助 \Python36\Scripts\pyuic5.exe 工具,命令如下:

 

pyuic5 - o my_win.py my_win.ui

  

转换成功后,让我们打开 my_win.py 文件,可以简单看一下这个 my_win.py 里的内容,代码里首先 import 了 PyQt5 相关库,并定义了名为 Ui_MainWindow 的 class,这个 class 主要包含两个函数 setupUi()和 retranslateUi()。setupUi()里初始化了各个控件成员 self.xx,这与我们在 Qt Designer 里添加控件是对应的。

 

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

 

# Form implementation generated from reading ui file '.\my_win.ui'

 

#

 

# Created by: PyQt5 UI code generator 5.11.3

 

#

 

# 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(603, 448)
       

self.centralwidget = QtWidgets.QWidget(MainWindow)
       

self.centralwidget.setObjectName("centralwidget")
       

self.frame = QtWidgets.QFrame(self.centralwidget)
       

self.frame.setGeometry(QtCore.QRect(100, 80, 361, 211))
       

self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
       

self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
       

self.frame.setObjectName("frame")
       

self.verticalLayoutWidget = QtWidgets.QWidget(self.frame)
       

self.verticalLayoutWidget.setGeometry(QtCore.QRect(30, 20, 160, 172))
       

self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
       

self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
       

self.verticalLayout.setContentsMargins(0, 0, 0, 0)
       

self.verticalLayout.setObjectName("verticalLayout")
       

self.pushButton = QtWidgets.QPushButton(self.verticalLayoutWidget)
       

self.pushButton.setEnabled(True)
       

self.pushButton.setObjectName("pushButton")
       

self.verticalLayout.addWidget(self.pushButton)
       

self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
       

self.label.setObjectName("label")
       

self.verticalLayout.addWidget(self.label)
       

self.textEdit = QtWidgets.QTextEdit(self.verticalLayoutWidget)
       

self.textEdit.setObjectName("textEdit")
       

self.verticalLayout.addWidget(self.textEdit)
       

self.checkBox = QtWidgets.QCheckBox(self.verticalLayoutWidget)
       

self.checkBox.setObjectName("checkBox")
       

self.verticalLayout.addWidget(self.checkBox)
       

self.horizontalSlider = QtWidgets.QSlider(self.verticalLayoutWidget)
       

self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
       

self.horizontalSlider.setObjectName("horizontalSlider")
       

self.verticalLayout.addWidget(self.horizontalSlider)
       

MainWindow.setCentralWidget(self.centralwidget)
       

self.menubar = QtWidgets.QMenuBar(MainWindow)
       

self.menubar.setGeometry(QtCore.QRect(0, 0, 603, 21))
       

self.menubar.setObjectName("menubar")
       

MainWindow.setMenuBar(self.menubar)
       

self.statusbar = QtWidgets.QStatusBar(MainWindow)
       

self.statusbar.setObjectName("statusbar")
       

MainWindow.setStatusBar(self.statusbar)

       

self.retranslateUi(MainWindow)
       

QtCore.QMetaObject.connectSlotsByName(MainWindow)

   

def retranslateUi(self, MainWindow):
       

_translate = QtCore.QCoreApplication.translate
       

MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
       

self.pushButton.setText(_translate("MainWindow", "PushButton"))
       

self.label.setText(_translate("MainWindow", "TextLabel"))
       

self.checkBox.setText(_translate("MainWindow", "CheckBox"))

 

三、使用 Qt Designer 生成的代码

前面已经使用 Qt Designer 生成 GUI 界面类 Ui_MainWindow 并保存在 my_win.py 文件中,此时需要创建一个主函数文件去调用 Ui_MainWindow,下面是痞子衡创建的 main_win.py 中的代码:

 

import sys


from PyQt5.QtWidgets import QApplication, QMainWindow


# 导入 my_win.py 中内容


from my_win import *

 

# 创建 mainWin 类并传入 Ui_MainWindow

 

class mainWin(QMainWindow, Ui_MainWindow):
   

def __init__(self, parent=None):
       

super(mainWin, self).__init__(parent)
       

self.setupUi(self)

 

if __name__ == '__main__':
   

# 下面是使用 PyQt5 的固定用法
   

app = QApplication(sys.argv)
   

main_win = mainWin()
   

main_win.show()
   

sys.exit(app.exec_())


3.1 触发事件与响应

有了 Button,我们肯定希望其能与一个响应函数相联系起来,此处痞子衡定义了 showMessage()函数,并且将 showMessage()与 PushButton 绑定起来,点击 Button 便会执行一次这个 showMessage()函数。代码如下:

 

class mainWin(QMainWindow, Ui_MainWindow):
   

def __init__(self, parent=None):
       

super(mainWin, self).__init__(parent)
       

self.setupUi(self)
       

# 将响应函数绑定到指定 Button
     

self.pushButton.clicked.connect(self.showMessage)

   

# Button 响应函数
   

def showMessage(self):
       

self.textEdit.setText('hello world')
  

最后让我们测试一下这个 GUI 软件,在命令行下运行 main_win.py

 

PS D:\my_git_repo\> python .\main_win.py


  

 

至此,PyQt5 GUI 构建工具 Qt Designer 痞子衡便介绍完毕了,掌声在哪里~~~