购买与学习资源
- 京东购买链接:Yocto项目实战教程:高效定制嵌入式Linux系统
- B站配套视频:嵌入式Jerry
用 PyQt5 快速开发专业桌面工具 —— 从 PyCharm 到界面设计全流程详解
1. PyQt5:Python 的强大桌面开发利器
PyQt5 是 Python 的 Qt 框架绑定,拥有丰富的界面控件、跨平台(Windows/Linux/Mac 都支持)、学习曲线平缓,非常适合自动化、数据可视化、硬件调试工具等桌面开发场景。
2. 环境与工具准备
2.1 Python 环境
- 推荐使用 Python 3.7 及以上,建议用 PyCharm 作为开发环境(写 Python GUI,PyCharm 功能非常强大)。
2.2 安装核心包
pip install pyqt5 pyqt5-tools
pyqt5-tools
里包含 Qt Designer(可视化界面编辑工具)。
3. PyCharm 的核心使用技巧
- 项目结构清晰:左侧 Project 面板管理文件,便于拆分模块(如 main.py、ui.py、worker.py)。
- 智能补全:输入类名或方法,自动补齐、跳转,提升开发速度。
- 终端集成:右下角 Terminal,支持 pip、git、pyuic5 命令,不必离开 PyCharm。
- 可视化调试:断点、变量观察,调试界面事件和多线程异常都很方便。
4. Qt Designer —— 所见即所得的界面设计
4.1 打开 Designer
- 终端输入
designer
(Windows下pyqt5-tools
的安装目录下有 Designer.exe)。
4.2 拖拽控件
- 拖拽按钮、输入框、标签等控件,布局完毕后保存为
xxx.ui
文件。
4.3 用 pyuic5 转换
pyuic5 xxx.ui -o xxx_ui.py
- 得到标准 Python 界面类,便于代码调用。
5. PyQt5 核心开发模式
5.1 典型结构
- 界面代码:由
.ui
转.py
或自己写。 - 主逻辑:主窗口/主逻辑类(继承自 QWidget/QMainWindow)。
- 数据/任务线程:如后台监听、串口/USB等,放在线程 worker 中。
- 信号与槽:事件响应,界面与业务解耦。
5.2 最常用控件/类
控件/类 | 作用 | 用法简述 |
---|---|---|
QPushButton | 按钮 | 触发事件 |
QLabel | 文本/图片显示 | 显示状态、标签 |
QLineEdit | 单行输入框 | 输入命令、参数等 |
QTextEdit | 多行日志显示 | 显示数据、日志 |
QComboBox | 下拉选择框 | 设备、端口、模式选择 |
QTableWidget | 表格控件 | 批量数据显示 |
QThread | 线程 | 后台监听或数据处理 |
pyqtSignal | 自定义信号 | 线程/界面通信 |
6. 实用案例:USB 设备监听工具界面(主程序精简版)
实现:设备选择、接口选择、端点选择、监听数据、发送数据,全流程演示。
import usb.core
import usb.util
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QTextEdit, QLabel, QLineEdit
from PyQt5.QtCore import QThread, pyqtSignal
# Worker 线程用于数据监听(防止界面卡死)
class USBReceiver(QThread):
data_received = pyqtSignal(str)
def __init__(self, dev, ep):
super().__init__()
self.dev = dev
self.ep = ep
self.running = True
def run(self):
self.data_received.emit("✅ 开始监听...")
while self.running:
try:
data = self.dev.read(self.ep.bEndpointAddress, self.ep.wMaxPacketSize, timeout=1000)
hex_str = " ".join(f"{b:02X}" for b in data)
self.data_received.emit(f"📥 数据: {hex_str}")
except Exception:
continue
def stop(self):
self.running = False
self.wait()
class USBMonitor(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("USB监听调试工具")
self.resize(680, 480)
self.device_combo = QComboBox()
self.ep_combo = QComboBox()
self.scan_btn = QPushButton("刷新设备")
self.start_btn = QPushButton("监听")
self.stop_btn = QPushButton("停止")
self.send_edit = QLineEdit("01 02 02 02 ...") # 默认发送数据
self.send_btn = QPushButton("发送")
self.log_view = QTextEdit()
self.log_view.setReadOnly(True)
layout = QVBoxLayout()
layout.addWidget(QLabel("设备选择:"))
layout.addWidget(self.device_combo)
layout.addWidget(self.scan_btn)
layout.addWidget(QLabel("端点选择:"))
layout.addWidget(self.ep_combo)
layout.addWidget(self.start_btn)
layout.addWidget(self.stop_btn)
layout.addWidget(QLabel("发送数据(16进制空格分隔):"))
layout.addWidget(self.send_edit)
layout.addWidget(self.send_btn)
layout.addWidget(self.log_view)
self.setLayout(layout)
self.devices = []
self.endpoints = []
self.reader = None
self.scan_btn.clicked.connect(self.scan_devices)
self.device_combo.currentIndexChanged.connect(self.refresh_endpoints)
self.start_btn.clicked.connect(self.start_listen)
self.stop_btn.clicked.connect(self.stop_listen)
self.send_btn.clicked.connect(self.send_data)
self.scan_devices()
def log(self, msg):
self.log_view.append(msg)
def scan_devices(self):
self.device_combo.clear()
self.devices = []
for dev in usb.core.find(find_all=True):
vid, pid = dev.idVendor, dev.idProduct
try:
manu = usb.util.get_string(dev, dev.iManufacturer) or "N/A"
prod = usb.util.get_string(dev, dev.iProduct) or "N/A"
except Exception:
manu, prod = "N/A", "N/A"
label = f"{manu} - {prod} (VID:0x{vid:04X}, PID:0x{pid:04X})"
self.device_combo.addItem(label)
self.devices.append(dev)
self.refresh_endpoints(0)
def refresh_endpoints(self, idx):
self.ep_combo.clear()
self.endpoints = []
if idx < 0 or idx >= len(self.devices): return
dev = self.devices[idx]
try:
cfg = dev.get_active_configuration()
except Exception:
self.log("无法获取设备配置")
return
for intf in cfg:
for ep in intf:
if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
self.ep_combo.addItem(f"端点0x{ep.bEndpointAddress:02X}, 长度:{ep.wMaxPacketSize}")
self.endpoints.append(ep)
def start_listen(self):
idx = self.device_combo.currentIndex()
ep_idx = self.ep_combo.currentIndex()
if idx < 0 or ep_idx < 0: return
dev = self.devices[idx]
ep = self.endpoints[ep_idx]
if self.reader: self.reader.stop()
self.reader = USBReceiver(dev, ep)
self.reader.data_received.connect(self.log)
self.reader.start()
def stop_listen(self):
if self.reader:
self.reader.stop()
self.log("🛑 已停止监听")
def send_data(self):
idx = self.device_combo.currentIndex()
ep_idx = self.ep_combo.currentIndex()
if idx < 0 or ep_idx < 0: return
dev = self.devices[idx]
ep = self.endpoints[ep_idx]
hex_str = self.send_edit.text().strip()
try:
data = [int(x, 16) for x in hex_str.split()]
sent = dev.write(ep.bEndpointAddress, data, timeout=1000)
self.log(f"✅ 已发送 {sent} 字节: {hex_str}")
except Exception as e:
self.log(f"❌ 发送失败: {e}")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = USBMonitor()
win.show()
sys.exit(app.exec_())
可扩展:后台线程监听数据,界面实时显示和发送。更复杂功能可拆分为独立 py 文件和模块。
7. 小结:PyQt5 + PyCharm 实战经验精髓
- 界面设计靠 Designer,交互逻辑用 Python
- 信号/槽让界面与后台分离,线程防止卡顿
- PyCharm 提升开发体验:调试、补全、终端一体化
- 可扩展性强,适合工程化管理
- 写工具,不必重复造轮子,善用布局和信号机制,维护省心
8. 推荐进阶资源
购买与学习资源
- 京东购买链接:Yocto项目实战教程:高效定制嵌入式Linux系统
- B站配套视频:嵌入式Jerry