开源 C++ QT QML 开发(十)通讯--串口
文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(一)基本介绍
开源 C++ QT QML 开发(二)工程结构
开源 C++ QT QML 开发(三)常用控件
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(六)自定义控件--波形图
开源 C++ QT QML 开发(七)自定义控件--仪表盘
开源 C++ QT QML 开发(八)自定义控件--圆环
开源 C++ QT QML 开发(九)文件--文本和二进制
开源 C++ QT QML 开发(十)通讯--串口
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
开源 C++ QT QML 开发(十二)通讯--TCP客户端
推荐链接:
开源 C# 快速开发(一)基础知识
开源 C# 快速开发(二)基础控件
开源 C# 快速开发(三)复杂控件
开源 C# 快速开发(四)自定义控件--波形图
开源 C# 快速开发(五)自定义控件--仪表盘
开源 C# 快速开发(六)自定义控件--圆环
开源 C# 快速开发(七)通讯--串口
开源 C# 快速开发(八)通讯--Tcp服务器端
开源 C# 快速开发(九)通讯--Tcp客户端
开源 C# 快速开发(十)通讯--http客户端
开源 C# 快速开发(十一)线程
开源 C# 快速开发(十二)进程监控
开源 C# 快速开发(十三)进程--管道通讯
开源 C# 快速开发(十四)进程--内存映射
开源 C# 快速开发(十五)进程--windows消息
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:实现了一个串口调试工具,可以对串口参数进行配置,可以接收和发送文本和十六进制数据。
1.代码分析
2.所有源码
3.效果演示
一、代码分析
1. qml代码分析
ApplicationWindow 根元素
ApplicationWindow {id: windowwidth: 1000height: 800title: "串口调试工具 - Qt 5.12"visible: true
创建主应用程序窗口
设置固定尺寸 1000x800
定义窗口标题和可见性
颜色主题系统
// 定义颜色主题
property color primaryColor: "#3498db" // 主色调 - 蓝色
property color secondaryColor: "#2ecc71" // 次要色调 - 绿色
property color accentColor: "#e74c3c" // 强调色 - 红色
property color backgroundColor: "#f8f9fa" // 背景色
property color cardColor: "#ffffff" // 卡片背景色
property color textColor: "#2c3e50" // 主要文字颜色
property color subTextColor: "#7f8c8d" // 次要文字颜色
property color borderColor: "#bdc3c7" // 边框颜色
property color successColor: "#27ae60" // 成功颜色
property color warningColor: "#f39c12" // 警告颜色
property color errorColor: "#e74c3c" // 错误颜色
统一的设计系统,便于维护和修改
语义化颜色命名,提高代码可读性
背景设置
Rectangle {anchors.fill: parentcolor: backgroundColor
}
设置整个窗口的背景颜色
anchors.fill: parent 确保背景填充整个窗口
2. 串口管理器实例化
SerialPortManager {id: serialPort
}
创建 C++ SerialPortManager 类的 QML 实例
id: serialPort 用于在 QML 中引用该对象
3. 主布局结构
ColumnLayout 主容器
ColumnLayout {anchors.fill: parentanchors.margins: 15spacing: 12
垂直布局管理器,包含所有界面元素
anchors.fill: parent 填充整个窗口
设置边距和组件间距
4. 串口配置区域详细分析
GroupBox 容器
GroupBox {title: "📡 串口配置"Layout.fillWidth: truebackground: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 8}label: Label {text: parent.titlecolor: primaryColorfont.bold: truefont.pixelSize: 14padding: 5}
创建分组框,包含所有串口配置控件
自定义背景和边框样式
自定义标题标签样式
GridLayout 网格布局
GridLayout {columns: 6 // 6列网格width: parent.widthrowSpacing: 8columnSpacing: 8
6列网格布局,整齐排列配置控件
设置行间距和列间距
端口选择 ComboBox
ComboBox {id: portComboBoxLayout.fillWidth: truemodel: serialPort.portList // 绑定到C++的端口列表background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}onModelChanged: {if (model.length > 0 && currentIndex === -1) {currentIndex = 0 // 自动选择第一个可用端口}}
}
动态绑定到 C++ 的端口列表
自动选择第一个可用端口
自定义样式参数选择 ComboBox 组
// 波特率选择
ComboBox {id: baudRateComboBoxmodel: ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]currentIndex: 3 // 默认选择9600
}// 校验位选择(包含值映射)
ComboBox {id: parityComboBoxmodel: ["无", "奇校验", "偶校验", "标记", "空格"]property var parityValues: [0, 3, 2, 1, 4] // 映射到C++枚举值
}
预定义常用参数选项
使用属性存储枚举值映射
设置合理的默认值
连接按钮
Button {id: connectButtontext: serialPort.isConnected ? "🔌 断开连接" : "🔗 连接"Layout.columnSpan: 2Layout.fillWidth: truebackground: Rectangle {color: serialPort.isConnected ? accentColor : secondaryColorradius: 6}onClicked: {if (serialPort.isConnected) {serialPort.disconnectSerialPort()} else {if (portComboBox.currentText) {serialPort.connectSerialPort(portComboBox.currentText,parseInt(baudRateComboBox.currentText),parseInt(dataBitsComboBox.currentText),parityComboBox.parityValues[parityComboBox.currentIndex],stopBitsComboBox.stopBitsValues[stopBitsComboBox.currentIndex],flowControlComboBox.flowControlValues[flowControlComboBox.currentIndex])}}}
}
动态文本:根据连接状态改变按钮文字
动态颜色:连接/断开状态使用不同颜色
参数传递:收集所有配置参数传递给 C++ 函数
空值检查:确保选择了端口
5. 发送数据区域
模式选择 CheckBox
CheckBox {id: sendHexCheckBoxtext: "十六进制发送"contentItem: Text {text: sendHexCheckBox.textcolor: textColorfont.bold: true}
}
控制发送数据的格式(文本/十六进制)
自定义文本样式
发送文本区域
ScrollView {Layout.fillWidth: trueLayout.preferredHeight: 80background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}TextArea {id: sendTextAreaplaceholderText: "请输入要发送的数据..."placeholderTextColor: subTextColorwrapMode: TextEdit.Wrapcolor: textColor}
}
可滚动的文本输入区域
设置占位符文本
支持自动换行
发送控制按钮组
RowLayout {Button {text: "🚀 发送"onClicked: {serialPort.sendData(sendTextArea.text, sendHexCheckBox.checked)}}Button {text: "🗑️ 清空发送"onClicked: sendTextArea.clear()}
}
发送按钮:传递文本内容和格式模式
清空按钮:清除输入框内容
6. 状态显示
Label {text: "状态: " + serialPort.statusMessagecolor: {if (serialPort.isConnected) return successColorelse if (serialPort.statusMessage.includes("错误") ||serialPort.statusMessage.includes("失败")) return errorColorelse return textColor}
}
动态状态文本:显示当前操作状态
智能颜色切换:
连接成功:绿色
错误/失败:红色
其他状态:默认颜色
7. 接收数据区域
接收显示区域
ScrollView {Layout.fillWidth: trueLayout.fillHeight: truebackground: Rectangle {color: "#2c3e50" // 深色背景便于阅读border.color: borderColorborder.width: 1radius: 4}TextArea {id: receiveTextAreatext: serialPort.receivedData // 绑定到C++接收数据wrapMode: TextEdit.WrapreadOnly: truefont.family: "Consolas, 'Courier New', monospace" // 等宽字体font.pixelSize: 12selectByMouse: true // 允许选择文本color: "#ecf0f1" // 浅色文字}
}
数据绑定:自动更新显示接收到的数据
等宽字体:便于对齐和阅读
深色主题:减少长时间使用的视觉疲劳
文本选择:允许用户复制接收到的数据
接收统计和控制
RowLayout {Label {text: "📊 接收字节数: " + receiveTextArea.text.length}CheckBox {id: autoScrollCheckBoxtext: "自动滚动"checked: true // 默认开启自动滚动}Button {text: "💾 保存数据"onClicked: {console.log("保存数据功能待实现")}}
}
字节统计:实时显示接收数据长度
自动滚动:控制是否自动滚动到最新内容
保存功能:预留数据保存接口
8. 状态栏
footer: ToolBar {background: Rectangle {color: primaryColor}RowLayout {anchors.fill: parentLabel {text: "🔧 串口调试工具 v1.0 | 就绪"color: "white"font.bold: true}Item { Layout.fillWidth: true } // 占位空间Label {text: new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss")color: "white"}}
}
应用信息:显示版本和状态
实时时钟:显示当前时间
主色调背景:与整体设计保持一致
9. 数据绑定和交互机制
属性绑定
model: serialPort.portList // 自动更新串口列表
text: serialPort.receivedData // 自动更新接收数据
checked: serialPort.isConnected // 自动更新连接状态
条件渲染
text: serialPort.isConnected ? "断开连接" : "连接"
color: serialPort.isConnected ? accentColor : secondaryColor
事件处理
onClicked: { /* 处理点击 */ }
onModelChanged: { /* 响应模型变化 */ }
SerialPortManager.cpp分析
1. 构造函数 SerialPortManager::SerialPortManager
SerialPortManager::SerialPortManager(QObject *parent): QObject(parent), m_serialPort(new QSerialPort(this)) // 创建串口对象, m_hexDisplay(false) // 初始化显示模式为文本
{// 初始化定时器,定时刷新串口列表m_portRefreshTimer = new QTimer(this);connect(m_portRefreshTimer, &QTimer::timeout, this, &SerialPortManager::refreshPorts);m_portRefreshTimer->start(2000); // 每2秒刷新一次// 连接串口信号到槽函数connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPortManager::onReadyRead);connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPortManager::onErrorOccurred);refreshPorts(); // 初始刷新串口列表
}
功能:初始化所有成员变量,建立信号槽连接,启动定时刷新。
2. 属性读取函数
portList()
QStringList SerialPortManager::portList() const
{return m_portList; // 返回当前串口列表
}
isConnected()
bool SerialPortManager::isConnected() const
{return m_serialPort->isOpen(); // 返回串口打开状态
}
receivedData()
QString SerialPortManager::receivedData() const
{return m_receivedData; // 返回接收到的数据
}
statusMessage()
QString SerialPortManager::statusMessage() const
{return m_statusMessage; // 返回状态消息
}
3. 核心功能函数
refreshPorts() - 刷新串口列表
void SerialPortManager::refreshPorts()
{QStringList newPortList;const auto infos = QSerialPortInfo::availablePorts(); // 获取系统可用串口for (const QSerialPortInfo &info : infos) {newPortList << info.portName(); // 提取端口名}// 只有列表发生变化时才更新并发射信号if (newPortList != m_portList) {m_portList = newPortList;emit portListChanged(); // 通知QML更新}
}
connectSerialPort() - 连接串口
bool SerialPortManager::connectSerialPort(const QString &portName, int baudRate, int dataBits,int parity, int stopBits, int flowControl)
{if (m_serialPort->isOpen()) {m_serialPort->close(); // 如果已连接,先关闭}// 设置串口参数m_serialPort->setPortName(portName);m_serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRate));m_serialPort->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));m_serialPort->setParity(static_cast<QSerialPort::Parity>(parity));m_serialPort->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));m_serialPort->setFlowControl(static_cast<QSerialPort::FlowControl>(flowControl));// 尝试以读写模式打开串口if (m_serialPort->open(QIODevice::ReadWrite)) {m_statusMessage = QString("已连接到 %1").arg(portName);emit statusMessageChanged();emit connectionChanged();return true;} else {m_statusMessage = QString("连接失败: %1").arg(m_serialPort->errorString());emit statusMessageChanged();return false;}
}
disconnectSerialPort() - 断开连接
void SerialPortManager::disconnectSerialPort()
{if (m_serialPort->isOpen()) {m_serialPort->close(); // 关闭串口m_statusMessage = "串口已断开";emit statusMessageChanged();emit connectionChanged(); // 通知连接状态改变}
}
sendData() - 发送数据
void SerialPortManager::sendData(const QString &data, bool hexMode)
{if (!m_serialPort->isOpen()) {m_statusMessage = "串口未连接";emit statusMessageChanged();return;}QByteArray sendArray;if (hexMode) {// 十六进制发送模式QString cleanData = data.trimmed();cleanData.remove(' '); // 移除空格// 验证十六进制数据长度if (cleanData.length() % 2 != 0) {m_statusMessage = "十六进制数据长度必须为偶数";emit statusMessageChanged();return;}// 逐字节转换十六进制字符串为字节数据for (int i = 0; i < cleanData.length(); i += 2) {bool ok;QString byteStr = cleanData.mid(i, 2);char byte = static_cast<char>(byteStr.toInt(&ok, 16));if (ok) {sendArray.append(byte);} else {m_statusMessage = "十六进制数据格式错误";emit statusMessageChanged();return;}}} else {// 文本发送模式sendArray = data.toUtf8(); // 转换为UTF-8编码}// 发送数据并检查结果qint64 bytesWritten = m_serialPort->write(sendArray);if (bytesWritten == -1) {m_statusMessage = "发送失败";emit statusMessageChanged();} else {m_statusMessage = QString("已发送 %1 字节").arg(bytesWritten);emit statusMessageChanged();}
}
clearReceivedData() - 清空接收数据
void SerialPortManager::clearReceivedData()
{m_receivedData.clear(); // 清空数据缓冲区emit receivedDataChanged(); // 通知QML更新显示
}
4. 槽函数
onReadyRead() - 数据接收处理
void SerialPortManager::onReadyRead()
{QByteArray data = m_serialPort->readAll(); // 读取所有可用数据// 问题:这里硬编码为false,应该从QML获取设置bool hexDisplay = false;if (hexDisplay) {// 十六进制显示模式QString hexString;for (char byte : data) {// 每个字节格式化为两位十六进制数,用空格分隔hexString += QString("%1 ").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();}m_receivedData += hexString;} else {// 文本显示模式QString text;for (char byte : data) {if (byte >= 32 && byte <= 126) {// 可打印字符直接显示text += QChar(byte);} else if (byte == '\r' || byte == '\n' || byte == '\t') {// 特殊控制字符显示text += QChar(byte);} else {// 不可见字符显示为十六进制格式text += QString("[%1]").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();}}m_receivedData += text;}emit receivedDataChanged(); // 通知数据更新
}
onErrorOccurred() - 错误处理
void SerialPortManager::onErrorOccurred(QSerialPort::SerialPortError error)
{if (error != QSerialPort::NoError) {// 生成错误信息m_statusMessage = QString("串口错误: %1").arg(m_serialPort->errorString());emit statusMessageChanged();// 如果串口处于打开状态,关闭它if (m_serialPort->isOpen()) {m_serialPort->close();emit connectionChanged(); // 通知连接状态改变}}
}
5. 主函数 main()
int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);// 注册C++类到QML系统qmlRegisterType<SerialPortManager>("SerialPort", 1, 0, "SerialPortManager");QQmlApplicationEngine engine;engine.load(QUrl(QStringLiteral("qrc:/main.qml")));if (engine.rootObjects().isEmpty())return -1;return app.exec();
}
二、所有源码
SerialPortManager.h文件源码
#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QDebug>class SerialPortManager : public QObject
{Q_OBJECTQ_PROPERTY(QStringList portList READ portList NOTIFY portListChanged)Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionChanged)Q_PROPERTY(QString receivedData READ receivedData NOTIFY receivedDataChanged)Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)public:explicit SerialPortManager(QObject *parent = nullptr);QStringList portList() const;bool isConnected() const;QString receivedData() const;QString statusMessage() const;Q_INVOKABLE void refreshPorts();Q_INVOKABLE bool connectSerialPort(const QString &portName, int baudRate, int dataBits,int parity, int stopBits, int flowControl);Q_INVOKABLE void disconnectSerialPort();Q_INVOKABLE void sendData(const QString &data, bool hexMode);Q_INVOKABLE void clearReceivedData();signals: // 添加signals关键字void portListChanged();void connectionChanged();void receivedDataChanged();void statusMessageChanged();private slots:void onReadyRead();void onErrorOccurred(QSerialPort::SerialPortError error);private:QSerialPort *m_serialPort;QStringList m_portList;QString m_receivedData;QString m_statusMessage;bool m_hexDisplay;QTimer *m_portRefreshTimer;
};#endif // SERIALPORTMANAGER_H
SerialPortManager.cpp文件源码
#include "SerialPortManager.h"SerialPortManager::SerialPortManager(QObject *parent): QObject(parent), m_serialPort(new QSerialPort(this)), m_hexDisplay(false)
{// 初始化定时器,定时刷新串口列表m_portRefreshTimer = new QTimer(this);connect(m_portRefreshTimer, &QTimer::timeout, this, &SerialPortManager::refreshPorts);m_portRefreshTimer->start(2000); // 每2秒刷新一次// 连接串口信号connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPortManager::onReadyRead);connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPortManager::onErrorOccurred);refreshPorts();
}QStringList SerialPortManager::portList() const
{return m_portList;
}bool SerialPortManager::isConnected() const
{return m_serialPort->isOpen();
}QString SerialPortManager::receivedData() const
{return m_receivedData;
}QString SerialPortManager::statusMessage() const
{return m_statusMessage;
}void SerialPortManager::refreshPorts()
{QStringList newPortList;const auto infos = QSerialPortInfo::availablePorts();for (const QSerialPortInfo &info : infos) {newPortList << info.portName();}if (newPortList != m_portList) {m_portList = newPortList;emit portListChanged();}
}bool SerialPortManager::connectSerialPort(const QString &portName, int baudRate, int dataBits,int parity, int stopBits, int flowControl)
{if (m_serialPort->isOpen()) {m_serialPort->close();}m_serialPort->setPortName(portName);m_serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRate));m_serialPort->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));m_serialPort->setParity(static_cast<QSerialPort::Parity>(parity));m_serialPort->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));m_serialPort->setFlowControl(static_cast<QSerialPort::FlowControl>(flowControl));if (m_serialPort->open(QIODevice::ReadWrite)) {m_statusMessage = QString("已连接到 %1").arg(portName);emit statusMessageChanged();emit connectionChanged();return true;} else {m_statusMessage = QString("连接失败: %1").arg(m_serialPort->errorString());emit statusMessageChanged();return false;}
}void SerialPortManager::disconnectSerialPort()
{if (m_serialPort->isOpen()) {m_serialPort->close();m_statusMessage = "串口已断开";emit statusMessageChanged();emit connectionChanged();}
}void SerialPortManager::sendData(const QString &data, bool hexMode)
{if (!m_serialPort->isOpen()) {m_statusMessage = "串口未连接";emit statusMessageChanged();return;}QByteArray sendArray;if (hexMode) {// 十六进制发送QString cleanData = data.trimmed();cleanData.remove(' ');if (cleanData.length() % 2 != 0) {m_statusMessage = "十六进制数据长度必须为偶数";emit statusMessageChanged();return;}for (int i = 0; i < cleanData.length(); i += 2) {bool ok;QString byteStr = cleanData.mid(i, 2);char byte = static_cast<char>(byteStr.toInt(&ok, 16));if (ok) {sendArray.append(byte);} else {m_statusMessage = "十六进制数据格式错误";emit statusMessageChanged();return;}}} else {// 文本发送sendArray = data.toUtf8();}qint64 bytesWritten = m_serialPort->write(sendArray);if (bytesWritten == -1) {m_statusMessage = "发送失败";emit statusMessageChanged();} else {m_statusMessage = QString("已发送 %1 字节").arg(bytesWritten);emit statusMessageChanged();}
}void SerialPortManager::clearReceivedData()
{m_receivedData.clear();emit receivedDataChanged();
}void SerialPortManager::onReadyRead()
{QByteArray data = m_serialPort->readAll();// 临时设置十六进制显示,实际应该从QML传递这个设置bool hexDisplay = false; // 这里需要从QML获取设置if (hexDisplay) {// 十六进制显示QString hexString;for (char byte : data) {hexString += QString("%1 ").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();}m_receivedData += hexString;} else {// 文本显示// 过滤不可见字符,保留可打印字符QString text;for (char byte : data) {if (byte >= 32 && byte <= 126) {text += QChar(byte);} else if (byte == '\r' || byte == '\n' || byte == '\t') {text += QChar(byte);} else {text += QString("[%1]").arg(static_cast<quint8>(byte), 2, 16, QLatin1Char('0')).toUpper();}}m_receivedData += text;}emit receivedDataChanged();
}void SerialPortManager::onErrorOccurred(QSerialPort::SerialPortError error)
{if (error != QSerialPort::NoError) {m_statusMessage = QString("串口错误: %1").arg(m_serialPort->errorString());emit statusMessageChanged();if (m_serialPort->isOpen()) {m_serialPort->close();emit connectionChanged();}}
}
main.cpp文件源码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "SerialPortManager.h"int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);// 注册C++类到QMLqmlRegisterType<SerialPortManager>("SerialPort", 1, 0, "SerialPortManager");QQmlApplicationEngine engine;engine.load(QUrl(QStringLiteral("qrc:/main.qml")));if (engine.rootObjects().isEmpty())return -1;return app.exec();
}
main.qml文件源码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import SerialPort 1.0ApplicationWindow {id: windowwidth: 1000height: 800title: "串口调试工具"visible: true// 定义颜色主题property color primaryColor: "#3498db" // 主色调 - 蓝色property color secondaryColor: "#2ecc71" // 次要色调 - 绿色property color accentColor: "#e74c3c" // 强调色 - 红色property color backgroundColor: "#f8f9fa" // 背景色property color cardColor: "#ffffff" // 卡片背景色property color textColor: "#2c3e50" // 主要文字颜色property color subTextColor: "#7f8c8d" // 次要文字颜色property color borderColor: "#bdc3c7" // 边框颜色property color successColor: "#27ae60" // 成功颜色property color warningColor: "#f39c12" // 警告颜色property color errorColor: "#e74c3c" // 错误颜色// 设置窗口背景Rectangle {anchors.fill: parentcolor: backgroundColor}// 串口管理器SerialPortManager {id: serialPort}ColumnLayout {anchors.fill: parentanchors.margins: 15spacing: 12// 串口配置区域GroupBox {title: "📡 串口配置"Layout.fillWidth: truebackground: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 8}label: Label {text: parent.titlecolor: primaryColorfont.bold: truefont.pixelSize: 14padding: 5}GridLayout {columns: 6width: parent.widthrowSpacing: 8columnSpacing: 8// 第一行Label {text: "端口:"color: textColorfont.bold: true}ComboBox {id: portComboBoxLayout.fillWidth: truemodel: serialPort.portListbackground: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}onModelChanged: {if (model.length > 0 && currentIndex === -1) {currentIndex = 0}}}Label {text: "波特率:"color: textColorfont.bold: true}ComboBox {id: baudRateComboBoxLayout.fillWidth: truemodel: ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]currentIndex: 3 // 9600background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}}Label {text: "数据位:"color: textColorfont.bold: true}ComboBox {id: dataBitsComboBoxLayout.fillWidth: truemodel: ["5", "6", "7", "8"]currentIndex: 3 // 8background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}}// 第二行Label {text: "校验位:"color: textColorfont.bold: true}ComboBox {id: parityComboBoxLayout.fillWidth: truemodel: ["无", "奇校验", "偶校验", "标记", "空格"]background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}property var parityValues: [0, 3, 2, 1, 4]}Label {text: "停止位:"color: textColorfont.bold: true}ComboBox {id: stopBitsComboBoxLayout.fillWidth: truemodel: ["1", "1.5", "2"]background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}property var stopBitsValues: [1, 3, 2]}Label {text: "流控制:"color: textColorfont.bold: true}ComboBox {id: flowControlComboBoxLayout.fillWidth: truemodel: ["无", "硬件", "软件"]background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}property var flowControlValues: [0, 1, 2]}// 第三行 - 按钮Button {id: connectButtontext: serialPort.isConnected ? "🔌 断开连接" : "🔗 连接"Layout.columnSpan: 2Layout.fillWidth: truebackground: Rectangle {color: serialPort.isConnected ? accentColor : secondaryColorradius: 6}contentItem: Text {text: connectButton.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}onClicked: {if (serialPort.isConnected) {serialPort.disconnectSerialPort()} else {if (portComboBox.currentText) {serialPort.connectSerialPort(portComboBox.currentText,parseInt(baudRateComboBox.currentText),parseInt(dataBitsComboBox.currentText),parityComboBox.parityValues[parityComboBox.currentIndex],stopBitsComboBox.stopBitsValues[stopBitsComboBox.currentIndex],flowControlComboBox.flowControlValues[flowControlComboBox.currentIndex])}}}}Button {text: "🔄 刷新端口"Layout.fillWidth: truebackground: Rectangle {color: primaryColorradius: 6}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}onClicked: serialPort.refreshPorts()}}}// 发送区域GroupBox {title: "📤 发送数据"Layout.fillWidth: trueLayout.preferredHeight: 180background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 8}label: Label {text: parent.titlecolor: primaryColorfont.bold: truefont.pixelSize: 14padding: 5}ColumnLayout {width: parent.widthspacing: 8RowLayout {CheckBox {id: sendHexCheckBoxtext: "十六进制发送"Layout.alignment: Qt.AlignLeft}CheckBox {id: receiveHexCheckBoxtext: "十六进制显示"Layout.alignment: Qt.AlignLeft}Item { Layout.fillWidth: true }Button {text: "🧹 清空接收"background: Rectangle {color: warningColorradius: 6}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}onClicked: serialPort.clearReceivedData()}}ScrollView {Layout.fillWidth: trueLayout.preferredHeight: 80background: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 4}TextArea {id: sendTextAreaplaceholderText: "请输入要发送的数据..."placeholderTextColor: subTextColorwrapMode: TextEdit.Wrapcolor: textColorbackground: Rectangle {color: "transparent"}}}}}RowLayout {Button {text: "🚀 发送"background: Rectangle {color: secondaryColorradius: 6}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}onClicked: {serialPort.sendData(sendTextArea.text, sendHexCheckBox.checked)}}Button {text: "🗑️ 清空发送"background: Rectangle {color: subTextColorradius: 6}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}onClicked: sendTextArea.clear()}Item { Layout.fillWidth: true }Label {text: "状态: " + serialPort.statusMessagecolor: {if (serialPort.isConnected) return successColorelse if (serialPort.statusMessage.includes("错误") ||serialPort.statusMessage.includes("失败")) return errorColorelse return textColor}font.bold: truepadding: 8background: Rectangle {color: backgroundColorradius: 4border.color: borderColorborder.width: 1}}}// 接收区域GroupBox {title: "📥 接收数据"Layout.fillWidth: trueLayout.fillHeight: truebackground: Rectangle {color: cardColorborder.color: borderColorborder.width: 1radius: 8}label: Label {text: parent.titlecolor: primaryColorfont.bold: truefont.pixelSize: 14padding: 5}ColumnLayout {width: parent.widthheight: parent.heightspacing: 8ScrollView {Layout.fillWidth: trueLayout.fillHeight: truebackground: Rectangle {color: "#2c3e50"border.color: borderColorborder.width: 1radius: 4}TextArea {id: receiveTextAreatext: serialPort.receivedDatawrapMode: TextEdit.WrapreadOnly: truefont.family: "Consolas, 'Courier New', monospace"font.pixelSize: 12selectByMouse: truecolor: "#ecf0f1"background: Rectangle {color: "transparent"}}}RowLayout {Label {text: "📊 接收字节数: " + receiveTextArea.text.lengthcolor: textColorfont.bold: truepadding: 6background: Rectangle {color: backgroundColorradius: 4border.color: borderColorborder.width: 1}}Item { Layout.fillWidth: true }CheckBox {id: autoScrollCheckBoxtext: "自动滚动"checked: truecontentItem: Text {text: autoScrollCheckBox.textcolor: textColorfont.bold: true}}Button {text: "💾 保存数据"background: Rectangle {color: primaryColorradius: 6}contentItem: Text {text: parent.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}onClicked: {// 这里可以添加保存数据的功能console.log("保存数据功能待实现")}}}}}}// 状态栏footer: ToolBar {background: Rectangle {color: primaryColor}RowLayout {anchors.fill: parentLabel {text: "🔧 串口调试工具 v1.0 | 就绪"color: "white"font.bold: truepadding: 8}Item { Layout.fillWidth: true }Label {text: new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss")color: "white"padding: 8}}}
}
三、效果演示