3、PyQt5基础:Qt Designer介绍、信号与槽机制、事件循环、主窗口与部件
好,咱们进入第三章。说实话,很多初学者学PyQt5,一上来就被各种类和方法搞晕了。我当年也是。但后来我发现,只要搞懂四个核心概念——Qt Designer、信号与槽、事件循环、主窗口与部件——剩下的就是拼积木。这一章,我就带你把这四块积木摸透。
3.1 Qt Designer:别手写界面了,拖拽它不香吗?
我个人习惯,做工业HMI界面,从来不会纯手写代码去布局。为什么?因为效率太低。你想想看,一个按钮的位置调来调去,改代码、运行、看效果,再改……浪费时间。
Qt Designer 就是解决这个问题的。它是一个可视化的界面设计工具。你拖拽控件,调整大小,设置属性,最后生成一个 .ui 文件。这个文件本质上是XML格式,PyQt5可以直接加载它。
怎么用?很简单。你安装好PyQt5后,在Python的 Scripts 目录下就能找到 designer.exe。双击打开,选择“Main Window”模板,然后就可以拖拽左侧的控件到画布上了。
生成 .ui 文件后,有两种方式加载:
- 方法一:用
uic.loadUi()直接加载,适合快速原型。 - 方法二:用
pyuic5工具把.ui转成.py文件,适合正式项目。
我个人推荐方法一,尤其是做工业HMI时,界面经常要改,直接加载 .ui 文件,改完界面不用重新编译代码。
# 方法一:直接加载 .ui 文件
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication
app = QApplication([])
window = uic.loadUi('main_window.ui')
window.show()
app.exec_()
pyuic5 转成 .py 文件,每次改完界面都要重新转一次。我曾经有个项目,就因为忘了重新转换,调试了半天才发现界面没更新。嗯,血的教训。
3.2 信号与槽机制:这才是PyQt5的灵魂
好,界面搭好了,怎么让按钮点击后执行某个函数?这就引出了PyQt5最核心的机制——信号与槽。
说白了,信号就是一个事件发生的通知。比如你点击按钮,按钮会发出一个 clicked 信号。槽就是一个函数,专门用来响应这个信号。你把信号和槽连接起来,事件发生时就自动调用函数。
为什么会这样设计?因为GUI程序是事件驱动的。你不能像写控制台程序那样顺序执行。你想想看,用户什么时候点击按钮,你根本不知道。所以你需要一个机制,让程序“监听”事件,然后响应。
# 信号与槽的经典例子
from PyQt5.QtWidgets import QPushButton, QApplication, QWidget
app = QApplication([])
window = QWidget()
button = QPushButton('点击我', window)
def on_button_clicked():
print('按钮被点击了!')
# 连接信号与槽
button.clicked.connect(on_button_clicked)
window.show()
app.exec_()
这里 button.clicked 就是信号,on_button_clicked 就是槽。用 connect 方法把它们连起来。
QLineEdit.textChanged 信号会携带当前文本内容。槽函数可以接收这个参数。
我在项目中遇到过一个问题:多个按钮连接同一个槽函数,怎么区分是哪个按钮触发的?答案是使用 lambda 表达式或者 sender() 方法。
# 使用 sender() 区分信号来源
def on_any_button_clicked():
sender = window.sender()
print(f'{sender.text()} 被点击了')
button1.clicked.connect(on_any_button_clicked)
button2.clicked.connect(on_any_button_clicked)
3.3 事件循环:程序为什么不会自己退出?
你有没有想过一个问题:为什么 app.exec_() 之后,程序就一直运行,直到你关闭窗口?这就是事件循环的功劳。
事件循环,说白了就是一个无限循环。它不停地从系统消息队列里取事件,然后分发到对应的对象去处理。比如鼠标移动、键盘按下、窗口重绘,这些都是事件。
我刚开始学的时候,总觉得事件循环很玄乎。后来我把它想象成一个邮局:事件就是信件,事件循环就是邮递员,不停地送信到各个窗口。
# 事件循环的伪代码
while True:
event = get_next_event() # 获取下一个事件
if event == QUIT:
break
dispatch_event(event) # 分发事件
嗯,这里要注意:如果你的槽函数里写了死循环或者耗时操作,事件循环就会被阻塞。界面会卡死,因为没空处理鼠标点击和重绘事件了。
QTimer 定时器,或者把耗时操作放到子线程里,问题就解决了。
3.4 主窗口与部件:搭积木的艺术
PyQt5里,主窗口是 QMainWindow,它提供了菜单栏、工具栏、状态栏等标准框架。部件就是各种控件,比如按钮、标签、输入框。
主窗口和部件的关系,就像画布和画上去的元素。主窗口提供一个容器,部件放在里面。
常用的部件有这些:
| 部件类 | 用途 | 我在项目中常用场景 |
|---|---|---|
QLabel |
显示文本或图片 | 显示设备状态、报警信息 |
QPushButton |
按钮 | 启动、停止、复位操作 |
QLineEdit |
单行文本输入 | 输入IP地址、参数值 |
QComboBox |
下拉选择框 | 选择配方、模式切换 |
QTableWidget |
表格 | 显示生产数据、历史记录 |
QGroupBox |
分组框 | 把相关控件分组,界面更清晰 |
布局管理也很重要。你总不能把按钮随便扔在窗口上吧?PyQt5提供了 QHBoxLayout(水平布局)、QVBoxLayout(垂直布局)、QGridLayout(网格布局)等。
# 使用布局管理器
from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QWidget
layout = QVBoxLayout()
layout.addWidget(QPushButton('按钮1'))
layout.addWidget(QPushButton('按钮2'))
layout.addWidget(QPushButton('按钮3'))
window = QWidget()
window.setLayout(layout)
3.5 综合示例:一个简单的HMI启动界面
好了,理论讲完了,咱们来点实战。下面是一个简单的工业HMI启动界面,包含一个启动按钮、一个停止按钮、一个状态显示标签。
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
class HMIWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('工业HMI - 启动界面')
self.setGeometry(100, 100, 400, 300)
# 创建部件
self.status_label = QLabel('设备状态:停止')
self.start_button = QPushButton('启动')
self.stop_button = QPushButton('停止')
# 布局
layout = QVBoxLayout()
layout.addWidget(self.status_label)
layout.addWidget(self.start_button)
layout.addWidget(self.stop_button)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
# 信号与槽
self.start_button.clicked.connect(self.start_device)
self.stop_button.clicked.connect(self.stop_device)
def start_device(self):
self.status_label.setText('设备状态:运行中')
print('设备已启动')
def stop_device(self):
self.status_label.setText('设备状态:停止')
print('设备已停止')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = HMIWindow()
window.show()
sys.exit(app.exec_())
这个例子虽然简单,但包含了主窗口、部件、信号与槽、事件循环的全部要素。你运行一下,点击按钮,标签文本会变化,控制台也会输出信息。
下一章,咱们会深入讲布局管理,以及如何用样式表让HMI界面更好看。嗯,到时候见。