0%

PyQt

PyQt 是 Qt 的Python版本,Qt是基于C++的GUI,在Python的UI组件库中,PyQt功能强大,提供QT Designer设计UI。PyQt 可与 C++ Qt无缝整合,另有QtWebEngine结合web前端实现Electron的功能。

题外话:Tkinter 是Python解决UI的原生库,优点在于没有其他依赖。但内容基础功能简单 比其他UI库都有不及

PyQt5类分为很多模块

QtCore 包含了核心的非GUI的功能。主要和时间、文件与文件夹、各种数据、流、URLs、mime类文件、进程与线程一起使用。
QtGui 包含了窗口系统、事件处理、2D图像、基本绘画、字体和文字类。QtWidgets类包含了一系列创建桌面应用的UI元素。 QtMultimedia包含了处理多媒体的内容和调用摄像头API的类。 QtNetwork包含了网络编程的类,这些工具能让TCP/IP和UDP开发变得更加方便和可靠。QtWebSockets包含了WebSocket协议的类。 QtWebKit包含了一个基WebKit2的web浏览器。

安装

1
2
pip install pyqt5
pip install pyqt5-tools

使用国内pip源

1
pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pyqt5

添加pyqt5-tools路径到环境变量Path

配置PyCharm外部工具
PyCharm -> 文件 -> 设置 -> 工具 -> 外部工具

填入QT designer路径
image.png

另添加 Pyuic 用于将ui文件转为py文件

须填入参数$FileName$ -o $FileNameWithoutExtension$.py
v2-7039dac81f16e3567988b1a16b745067_720w.jpg

类似的 可添加PyRCC

PyQt Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
from PyQt5.QtWidgets import QApplication, QWidget


if __name__ == '__main__':

app = QApplication(sys.argv)

w = QWidget()
w.resize(250, 150)
w.setFixedSize(250, 150) # 固定大小
w.move(300, 300)
w.setWindowTitle('HelloWorld!')
w.show()

sys.exit(app.exec_())

QT designer

完成如上配置 可在PyCharm的外部工具打开 QT designer,
使用QT designer生成UI文件

从.ui文件到.py文件

1
pyuic demo.ui -o demo.py

生成的py文件是UI的类定义文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralWidget = QtWidgets.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.btn_import = QtWidgets.QToolButton(self.centralWidget)
self.btn_import.setGeometry(QtCore, QRect(610, 20, 71, 22))
self.btn_import.setObjectName("btn_import")
self.btn_import.clicked.connect(self.on_clicked_btn_import)

def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.btn_import.setText(_translate("MainWindow", "import"))

def on_clicked_btn_import(self):
print("btn_import clicked")

.....

运行该UI需要在程序入口(__main__)实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox

from ui.mainWindow import Ui_MainWindow # 引入UI的类定义


class Startup(QMainWindow, Ui_MainWindow):
def __init__(self):
super(Startup, self).__init__()
self.setupUi(self)

def importImg(self):
fname = QFileDialog.getOpenFileName(self, 'import Image', './')

if fname[0]:
QMessageBox.information(self, 'import success', fname[0], QMessageBox.Yes)
print("import img is here")


if __name__ == '__main__':
app = QApplication(sys.argv)
startup = Startup()
startup.show()
sys.exit(app.exec_())


可见这里的Startup类即 Ui_MainWindow 实例化参数object 两个类是相互引用的;Startup类函数即主窗体的槽函数

事件、信号和槽函数

在控件层面 事件如点击按钮(clicked)选择菜单(triggered),事件可以直接connect到响应函数(槽函数)

1
2
3
4
btn = QPushButton('test', self)
btn.resize(btn.sizeHint())
btn.move(50, 50)
btn.clicked.connect(self.showDlg)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# MenuBar -> Menu -> Action -> Slot
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 842, 26))
self.menubar.setObjectName("menubar")
self.menu = QtWidgets.QMenu(self.menubar)
self.menu.setObjectName("menu")
MainWindow.setMenuBar(self.menubar)

self.actionimport = QtWidgets.QAction(MainWindow)
self.actionimport.setObjectName("actionimport")
self.menu.addAction(self.actionimport)
self.menubar.addAction(self.menu.menuAction())

self.retranslateUi(MainWindow)
self.actionimport.triggered.connect(MainWindow.importImg)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

或者,不同事件发送某信号,而某个模块监听到该信号而触发响应
1
2
3
4
5
6
7
8
9
10
11
# 创建一个信号closeApp。当触发鼠标点击事件时信号会被发射。信号连接到QMainWindow的close()方法。
class Communicate(QObject):
closeApp = pyqtSignal()
# 信号使用了pyqtSignal()方法创建,并且成为外部类Communicate类的属性。
self.c = Communicate()
self.c.closeApp.connect(self.close)
# 把自定义的closeApp信号连接到QMainWindow的close()槽上。
def mousePressEvent(self, event):

self.c.closeApp.emit()
# 当我们在窗口上点击一下鼠标,closeApp信号会被发射。应用中断。

页面间的信号

子页面发射信号 槽函数在父页面
子页面:

1
2
3
4
5
6
class Child_Widget(QWidget):
status_signal = pyqtSignal(str)
def __init__(self):
super(Child_Widget, self).__init__()
def updateStatus(self):
self.status_signal.emit('update')

父页面:
1
2
3
4
5
6
7
8
class Parent_Widget(QWidget):
...
def child_init(self):
self.child_widget = Child_Widget()
self.child_widget.status_signal.connect(self.update_child_status)
self.child_widget.show()
def update_child_status(self):
self.line.setText('status updated')

ui控件

Basic Widget
输入框QLineEdit
按钮QPushButton
整数调整器QPinBox

对话框

dialog class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PyQt5.QtWidgets import QDialog

class Ui_ModeEditor(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.__mainWindow = parent
.....

def setupUi(self):
self.buttonsLayout = QtWidgets.QHBoxLayout(self)
self.buttonsLayout.setObjectName("buttonsLayout")
btn_save = QtWidgets.QToolButton(self)
btn_save.setObjectName("btn_save")
btn_save.setText("保存")
btn_save.clicked.connect(self.accept)
btn_cancel = QtWidgets.QToolButton(self)
btn_cancel.setObjectName("btn_cancel")
btn_cancel.setText("取消")
btn_cancel.clicked.connect(self.reject)
self.buttonsLayout.addWidget(btn_save)
self.buttonsLayout.addWidget(btn_cancel)

use dialog
1
2
3
4
5
6
7
8
self.dialog = Ui_ModeEditor(self)
self.modeEditor.setWindowTitle("编辑模式")

retVal = self.dialog.exec()
if retVal == QDialog.Accepted:
# return accepted
else:
# close without accept

封装

pyinstaller vs Nuitka

Nuitka(音妞卡)是将python程序转换成C语言的可执行elf文件。这样在运行时就可以享受到C语言处理过程中的优化,提高速度。

1
pip install -U nuitka

编译依赖Python和C compiler,c compiler需支持C11或C++03 这意味着需安装MinGW64 C11编译器 基于gcc11.2或更高版本,或visual studio

1
2
pip install pyinstaller
pyinstaller -F -w main.py -n "测量工具"