4、PyQt5入门:Qt Designer界面设计、信号与槽机制、主窗口与布局管理

说实话,很多做电机控制的工程师,一听到「上位机界面开发」就头大。我以前也是这样,觉得写界面比写控制算法还麻烦。直到我用了PyQt5,才发现原来拖拖拽拽就能搞定一个专业的上位机。今天我就带你从零开始,把PyQt5的核心东西捋一遍。

4.1 Qt Designer:别手写界面了,拖拽它不香吗?

我刚开始学PyQt5的时候,傻乎乎地一行一行写代码布局。后来被老同事笑话了——「你写代码画界面,跟用记事本写PPT有啥区别?」他甩给我一个工具:Qt Designer。

Qt Designer是啥?说白了就是一个可视化的界面设计器。你拖一个按钮、拉一个文本框,它自动生成对应的.ui文件。然后你用PyQt5把这个.ui文件加载进来,界面就有了。

我的习惯: 我一般先用Qt Designer把界面布局搭好,再用代码去控制逻辑。这样界面改起来快,代码也干净。

具体操作流程是这样的:

  1. 打开Qt Designer,选择「Main Window」模板
  2. 从左侧工具箱拖拽控件到画布上(比如按钮、标签、下拉框)
  3. 在右侧属性面板调整控件的大小、文字、颜色等
  4. 保存为.ui文件

那怎么在Python里用这个.ui文件呢?有两种方式:

方式一:直接加载.ui文件(我推荐新手用这个)

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication

app = QApplication([])
window = uic.loadUi('main_window.ui')
window.show()
app.exec_()

方式二:把.ui转成.py文件(项目大了建议用这个)

# 在命令行执行:
pyuic5 main_window.ui -o main_window_ui.py

# 然后在代码里导入:
from main_window_ui import Ui_MainWindow
我曾经踩过的坑: 直接加载.ui文件虽然方便,但如果你改了.ui文件,程序会自动更新。这在调试时是好事,但发布程序时别忘了把.ui文件一起打包。否则用户打开就是一片空白。

4.2 信号与槽机制:让控件「活」起来

界面搭好了,但按钮按下去没反应,那跟一张图片有啥区别?这时候就要请出PyQt5的灵魂机制——信号与槽。

信号(Signal)就是控件发出的消息,比如「按钮被点击了」、「滑块被拖动了」。槽(Slot)就是处理这个消息的函数。你把信号和槽连接起来,控件就能「活」起来。

举个例子,你点一下「启动电机」按钮,控制台打印一条消息:

from PyQt5.QtWidgets import QPushButton, QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.btn = QPushButton('启动电机', self)
        # 连接信号与槽
        self.btn.clicked.connect(self.on_start_motor)

    def on_start_motor(self):
        print('电机已启动!转速:1500 RPM')

你看,clicked就是按钮的信号,on_start_motor就是槽函数。用connect把它们连起来,就这么简单。

核心要点: 信号可以带参数,槽函数也可以接收参数。比如QSlider的valueChanged信号会传递当前数值,你可以在槽函数里直接用它来更新显示。

我在项目中遇到过一个问题:多个按钮要连接同一个槽函数,但需要区分是哪个按钮触发的。解决办法是用lambda表达式传递参数:

self.btn_start.clicked.connect(lambda: self.on_motor_control('start'))
self.btn_stop.clicked.connect(lambda: self.on_motor_control('stop'))

def on_motor_control(self, command):
    if command == 'start':
        print('启动电机')
    elif command == 'stop':
        print('停止电机')

4.3 主窗口与布局管理:别让控件「乱跑」

你想想看,如果窗口一拉大,按钮还挤在左上角,那画面多难看。布局管理就是解决这个问题的。

PyQt5提供了几种布局方式:

布局类型 特点 适用场景
QHBoxLayout 水平排列,控件从左到右 工具栏、状态栏
QVBoxLayout 垂直排列,控件从上到下 参数设置面板
QGridLayout 网格排列,像Excel表格 仪表盘、数据显示
QFormLayout 表单排列,标签+输入框 参数配置界面

我一般用QGridLayout做电机监控界面,因为可以很直观地把电压、电流、转速这些参数排成网格。举个例子:

from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel, QLineEdit

class MotorMonitor(QWidget):
    def __init__(self):
        super().__init__()
        layout = QGridLayout()
        
        # 第一行:电压
        layout.addWidget(QLabel('电压 (V):'), 0, 0)
        self.voltage_edit = QLineEdit('220.0')
        layout.addWidget(self.voltage_edit, 0, 1)
        
        # 第二行:电流
        layout.addWidget(QLabel('电流 (A):'), 1, 0)
        self.current_edit = QLineEdit('5.0')
        layout.addWidget(self.current_edit, 1, 1)
        
        # 第三行:转速
        layout.addWidget(QLabel('转速 (RPM):'), 2, 0)
        self.speed_edit = QLineEdit('1500')
        layout.addWidget(self.speed_edit, 2, 1)
        
        self.setLayout(layout)
我的建议: 布局嵌套是常态。比如你可以在QGridLayout里再放一个QVBoxLayout,用来放一组按钮。别怕嵌套,只要逻辑清晰就行。

4.4 实战:做一个简单的电机控制面板

光说不练假把式。我们来做一个完整的例子:一个电机控制面板,包含启动/停止按钮、转速滑块、状态显示。

from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt

class MotorControlPanel(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('电机控制面板 v1.0')
        self.setGeometry(100, 100, 400, 300)
        
        # 中心控件
        central = QWidget()
        self.setCentralWidget(central)
        
        # 主布局:垂直
        main_layout = QVBoxLayout(central)
        
        # 状态显示
        self.status_label = QLabel('电机状态:停止')
        self.status_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(self.status_label)
        
        # 转速控制
        speed_layout = QHBoxLayout()
        speed_layout.addWidget(QLabel('目标转速:'))
        self.speed_slider = QSlider(Qt.Horizontal)
        self.speed_slider.setRange(0, 3000)
        self.speed_slider.setValue(1500)
        self.speed_display = QLabel('1500 RPM')
        speed_layout.addWidget(self.speed_slider)
        speed_layout.addWidget(self.speed_display)
        main_layout.addLayout(speed_layout)
        
        # 按钮
        btn_layout = QHBoxLayout()
        self.btn_start = QPushButton('启动')
        self.btn_stop = QPushButton('停止')
        btn_layout.addWidget(self.btn_start)
        btn_layout.addWidget(self.btn_stop)
        main_layout.addLayout(btn_layout)
        
        # 连接信号
        self.btn_start.clicked.connect(self.start_motor)
        self.btn_stop.clicked.connect(self.stop_motor)
        self.speed_slider.valueChanged.connect(self.update_speed)
    
    def start_motor(self):
        self.status_label.setText('电机状态:运行中')
        self.status_label.setStyleSheet('color: green;')
    
    def stop_motor(self):
        self.status_label.setText('电机状态:停止')
        self.status_label.setStyleSheet('color: red;')
    
    def update_speed(self, value):
        self.speed_display.setText(f'{value} RPM')

if __name__ == '__main__':
    app = QApplication([])
    window = MotorControlPanel()
    window.show()
    app.exec_()
嗯,这里要注意: 实际项目中,启动和停止电机需要调用底层通信接口(比如串口或CAN)。上面的例子只是演示界面逻辑,你替换成真实的控制函数就行。

好了,这一章的内容就这些。Qt Designer帮你省掉画界面的时间,信号与槽让控件有交互,布局管理让界面不乱跑。这三样东西掌握了,PyQt5你就入门了。下一章我们聊聊怎么用PyQt5做实时数据曲线显示,那才是电机监控的重头戏。