3、PyQt5基础:Qt Designer界面设计、信号与槽机制、常用控件与布局管理
好,咱们今天聊聊PyQt5。说实话,做BMS上位机开发,PyQt5几乎是绕不开的选择。我最早接触它的时候,也觉得有点懵——又是设计器又是信号槽的,东西不少。但用顺手了你会发现,它真的很适合做工业级的桌面应用。
这一章,我带你过一遍最核心的几个点:Qt Designer怎么用、信号与槽到底是个啥、常用控件怎么玩、布局怎么摆。嗯,都是实战中天天要碰的东西。
3.1 Qt Designer:别手写界面了,拖拽它不香吗?
我个人习惯,从来不用纯代码写界面。为啥?效率太低。你想想看,调一个按钮的位置,改代码、运行、看效果,来回折腾。Qt Designer就是干这个的——拖拖拽拽,界面就出来了。
打开Qt Designer,你会看到左侧是一堆控件面板,中间是画布,右侧是属性编辑器。操作很简单:
- 从左侧拖一个QPushButton到画布上
- 双击按钮改文字,比如改成“读取电压”
- 右侧属性栏里可以调大小、颜色、字体
设计好之后,保存为.ui文件。然后在Python里加载它:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
ui = uic.loadUi("main_window.ui")
ui.show()
sys.exit(app.exec_())
就这么几行,界面就出来了。我在项目里经常把.ui文件和业务逻辑分开,界面改起来特别方便,不用动代码。
3.2 信号与槽:这才是PyQt5的灵魂
信号与槽,说白了就是“发生了什么事,然后干什么事”。比如你点了一个按钮(信号),程序就去读取电池电压(槽函数)。
我刚开始学的时候,总觉得这概念很玄乎。其实你把它理解成“事件监听”就行。PyQt5里几乎所有的交互都靠这个机制。
看个最简单的例子:
from PyQt5.QtWidgets import QPushButton, QApplication
import sys
def on_click():
print("按钮被点击了!")
app = QApplication(sys.argv)
btn = QPushButton("点我")
btn.clicked.connect(on_click) # 信号连接到槽
btn.show()
sys.exit(app.exec_())
clicked是按钮的信号,on_click是槽函数。connect把它们连起来。就这么简单。
我曾经踩过一个坑:信号连接了槽,但槽函数里用了time.sleep(),结果界面卡死了。为什么会这样?因为槽函数是在主线程里跑的,阻塞了事件循环。后来我改用QTimer和QThread才解决。
3.3 常用控件:QWidget、QPushButton、QLabel、QLineEdit
这几个控件,你写BMS上位机几乎天天用。我一个个说。
QWidget —— 万物的基石
所有控件的基类。说白了,它就是一个空白的矩形区域。你可以往里面放别的控件。我习惯用它做容器,把相关的控件分组。
widget = QWidget()
widget.setWindowTitle("电池监控面板")
widget.resize(800, 600)
QPushButton —— 按钮,交互的起点
最常用的控件。除了点击,还可以设置快捷键、图标、样式。
btn = QPushButton("启动采集")
btn.setShortcut("Ctrl+S") # 快捷键
btn.setStyleSheet("background-color: #4CAF50; color: white;")
我在项目里经常用setEnabled(False)来禁用按钮,防止用户重复点击。比如采集过程中,把“启动”按钮禁用,采集完再启用。
QLabel —— 显示文本和图片
用来显示静态信息,比如电压值、状态提示。也可以显示图片。
label = QLabel("当前电压: 3.7V")
label.setStyleSheet("font-size: 16px; color: #333;")
嗯,这里要注意:QLabel默认不接收鼠标事件。如果你想让用户点击它,需要开启setWordWrap(True)或者设置setTextInteractionFlags。
QLineEdit —— 单行输入框
用来让用户输入文本,比如设置阈值、输入IP地址。
line_edit = QLineEdit()
line_edit.setPlaceholderText("请输入过压阈值")
line_edit.textChanged.connect(on_text_changed) # 实时监听输入
我常用的一个技巧:用setValidator限制输入格式,比如只允许输入数字:
from PyQt5.QtGui import QDoubleValidator
validator = QDoubleValidator(0.0, 100.0, 2) # 范围0-100,两位小数
line_edit.setValidator(validator)
3.4 布局管理:别再用绝对定位了
很多人刚开始写界面,喜欢用move(x, y)来定位控件。我当年也这么干过。结果窗口一缩放,控件全乱套了。
布局管理就是解决这个问题的。PyQt5提供了几种布局:
| 布局类型 | 说明 | 适用场景 |
|---|---|---|
| QHBoxLayout | 水平排列 | 一行放几个按钮 |
| QVBoxLayout | 垂直排列 | 一列放几个控件 |
| QGridLayout | 网格排列 | 表格形式的数据展示 |
| QFormLayout | 表单排列 | 标签+输入框的配对 |
看个实际例子,BMS上位机里常见的参数设置面板:
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget
# 创建主布局
main_layout = QVBoxLayout()
# 第一行:标签+输入框
row1 = QHBoxLayout()
row1.addWidget(QLabel("过压阈值:"))
row1.addWidget(QLineEdit())
main_layout.addLayout(row1)
# 第二行:标签+输入框
row2 = QHBoxLayout()
row2.addWidget(QLabel("欠压阈值:"))
row2.addWidget(QLineEdit())
main_layout.addLayout(row2)
# 按钮行
btn_layout = QHBoxLayout()
btn_layout.addWidget(QPushButton("保存"))
btn_layout.addWidget(QPushButton("取消"))
main_layout.addLayout(btn_layout)
# 应用到窗口
widget = QWidget()
widget.setLayout(main_layout)
你想想看,如果用绝对定位,窗口一拉伸,这些控件就挤在一起了。但用了布局,它们会自动适应。
setContentsMargins()和setSpacing()控制内外边距,界面会精致很多。
3.5 实战:做一个简单的电压监控面板
说了这么多,咱们动手做一个。这个例子包含了今天讲的所有知识点:
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit,
QPushButton, QTextEdit)
from PyQt5.QtCore import QTimer
import random
class VoltageMonitor(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
# 布局
layout = QVBoxLayout()
# 显示区域
self.display = QTextEdit("等待数据...")
self.display.setReadOnly(True)
layout.addWidget(self.display)
# 控制按钮
btn_layout = QHBoxLayout()
self.start_btn = QPushButton("开始采集")
self.stop_btn = QPushButton("停止采集")
self.stop_btn.setEnabled(False)
# 信号连接
self.start_btn.clicked.connect(self.start_collection)
self.stop_btn.clicked.connect(self.stop_collection)
btn_layout.addWidget(self.start_btn)
btn_layout.addWidget(self.stop_btn)
layout.addLayout(btn_layout)
# 定时器
self.timer = QTimer()
self.timer.timeout.connect(self.update_voltage)
self.setLayout(layout)
self.setWindowTitle("BMS电压监控")
self.resize(400, 300)
def start_collection(self):
self.start_btn.setEnabled(False)
self.stop_btn.setEnabled(True)
self.timer.start(1000) # 每秒采集一次
def stop_collection(self):
self.start_btn.setEnabled(True)
self.stop_btn.setEnabled(False)
self.timer.stop()
def update_voltage(self):
voltage = round(random.uniform(3.0, 4.2), 2)
self.display.append(f"当前电压: {voltage}V")
if __name__ == "__main__":
app = QApplication(sys.argv)
monitor = VoltageMonitor()
monitor.show()
sys.exit(app.exec_())
这个例子虽然简单,但包含了:布局管理、信号与槽、控件交互、定时器。你跑一下看看,点“开始采集”,电压值会每秒更新一次。点“停止”就停了。
下一章,咱们会深入讲讲多线程——毕竟真正的BMS采集可不能把界面卡死,对吧?