文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
开源 C++ QT QML 开发(十二)通讯--TCP客户端
推荐链接:
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:实现了一个串口调试工具,可以对串口参数进行配置,可以接收和发送文本和十六进制数据。
1.代码分析
2.所有源码
3.效果演示
一、代码分析
- qml代码分析
 
ApplicationWindow 根元素
ApplicationWindow {
    id: window
    width: 1000
    height: 800
    title: "串口调试工具 - 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: parent
    color: backgroundColor
}
        设置整个窗口的背景颜色
anchors.fill: parent 确保背景填充整个窗口
- 
串口管理器实例化
SerialPortManager {
id: serialPort
} 
创建 C++ SerialPortManager 类的 QML 实例
id: serialPort 用于在 QML 中引用该对象
- 主布局结构
 
ColumnLayout 主容器
ColumnLayout {
    anchors.fill: parent
    anchors.margins: 15
    spacing: 12
        垂直布局管理器,包含所有界面元素
anchors.fill: parent 填充整个窗口
设置边距和组件间距
- 串口配置区域详细分析
 
GroupBox 容器
GroupBox {
    title: "📡 串口配置"
    Layout.fillWidth: true
    background: Rectangle {
        color: cardColor
        border.color: borderColor
        border.width: 1
        radius: 8
    }
    label: Label {
        text: parent.title
        color: primaryColor
        font.bold: true
        font.pixelSize: 14
        padding: 5
    }
        创建分组框,包含所有串口配置控件
自定义背景和边框样式
自定义标题标签样式
GridLayout 网格布局
GridLayout {
    columns: 6  // 6列网格
    width: parent.width
    rowSpacing: 8
    columnSpacing: 8
        6列网格布局,整齐排列配置控件
设置行间距和列间距
端口选择 ComboBox
ComboBox {
    id: portComboBox
    Layout.fillWidth: true
    model: serialPort.portList  // 绑定到C++的端口列表
    background: Rectangle {
        color: cardColor
        border.color: borderColor
        border.width: 1
        radius: 4
    }
    onModelChanged: {
        if (model.length > 0 && currentIndex === -1) {
            currentIndex = 0  // 自动选择第一个可用端口
        }
    }
}
        动态绑定到 C++ 的端口列表
自动选择第一个可用端口
自定义样式参数选择 ComboBox 组
// 波特率选择
ComboBox {
    id: baudRateComboBox
    model: ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]
    currentIndex: 3 // 默认选择9600
}
// 校验位选择(包含值映射)
ComboBox {
    id: parityComboBox
    model: ["无", "奇校验", "偶校验", "标记", "空格"]
    property var parityValues: [0, 3, 2, 1, 4]  // 映射到C++枚举值
}
        预定义常用参数选项
使用属性存储枚举值映射
设置合理的默认值
连接按钮
Button {
    id: connectButton
    text: serialPort.isConnected ? "🔌 断开连接" : "🔗 连接"
    Layout.columnSpan: 2
    Layout.fillWidth: true
    background: Rectangle {
        color: serialPort.isConnected ? accentColor : secondaryColor
        radius: 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++ 函数
空值检查:确保选择了端口
- 发送数据区域
 
模式选择 CheckBox
CheckBox {
    id: sendHexCheckBox
    text: "十六进制发送"
    contentItem: Text {
        text: sendHexCheckBox.text
        color: textColor
        font.bold: true
    }
}
        控制发送数据的格式(文本/十六进制)
自定义文本样式
发送文本区域
ScrollView {
    Layout.fillWidth: true
    Layout.preferredHeight: 80
    background: Rectangle {
        color: cardColor
        border.color: borderColor
        border.width: 1
        radius: 4
    }
    TextArea {
        id: sendTextArea
        placeholderText: "请输入要发送的数据..."
        placeholderTextColor: subTextColor
        wrapMode: TextEdit.Wrap
        color: textColor
    }
}
        可滚动的文本输入区域
设置占位符文本
支持自动换行
发送控制按钮组
RowLayout {
    Button {
        text: "🚀 发送"
        onClicked: {
            serialPort.sendData(sendTextArea.text, sendHexCheckBox.checked)
        }
    }
    Button {
        text: "🗑️ 清空发送"
        onClicked: sendTextArea.clear()
    }
}
        发送按钮:传递文本内容和格式模式
清空按钮:清除输入框内容
- 
状态显示
Label {
text: "状态: " + serialPort.statusMessage
color: {
if (serialPort.isConnected) return successColor
else if (serialPort.statusMessage.includes("错误") ||
serialPort.statusMessage.includes("失败")) return errorColor
else return textColor
}
} 
动态状态文本:显示当前操作状态
智能颜色切换:
连接成功:绿色
错误/失败:红色
其他状态:默认颜色
- 接收数据区域
 
接收显示区域
ScrollView {
    Layout.fillWidth: true
    Layout.fillHeight: true
    background: Rectangle {
        color: "#2c3e50"  // 深色背景便于阅读
        border.color: borderColor
        border.width: 1
        radius: 4
    }
    TextArea {
        id: receiveTextArea
        text: serialPort.receivedData  // 绑定到C++接收数据
        wrapMode: TextEdit.Wrap
        readOnly: true
        font.family: "Consolas, 'Courier New', monospace"  // 等宽字体
        font.pixelSize: 12
        selectByMouse: true  // 允许选择文本
        color: "#ecf0f1"     // 浅色文字
    }
}
        数据绑定:自动更新显示接收到的数据
等宽字体:便于对齐和阅读
深色主题:减少长时间使用的视觉疲劳
文本选择:允许用户复制接收到的数据
接收统计和控制
RowLayout {
    Label {
        text: "📊 接收字节数: " + receiveTextArea.text.length
    }
    CheckBox {
        id: autoScrollCheckBox
        text: "自动滚动"
        checked: true  // 默认开启自动滚动
    }
    Button {
        text: "💾 保存数据"
        onClicked: {
            console.log("保存数据功能待实现")
        }
    }
}
        字节统计:实时显示接收数据长度
自动滚动:控制是否自动滚动到最新内容
保存功能:预留数据保存接口
- 
状态栏
footer: ToolBar {
background: Rectangle {
color: primaryColor
}
RowLayout {
anchors.fill: parent
Label {
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"
}
}
} 
应用信息:显示版本和状态
实时时钟:显示当前时间
主色调背景:与整体设计保持一致
- 数据绑定和交互机制
 
属性绑定
model: serialPort.portList           // 自动更新串口列表
text: serialPort.receivedData        // 自动更新接收数据
checked: serialPort.isConnected      // 自动更新连接状态
        条件渲染
text: serialPort.isConnected ? "断开连接" : "连接"
color: serialPort.isConnected ? accentColor : secondaryColor
        事件处理
onClicked: { /* 处理点击 */ }
onModelChanged: { /* 响应模型变化 */ }
        SerialPortManager.cpp分析
- 
构造函数 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(); // 初始刷新串口列表}
 
功能:初始化所有成员变量,建立信号槽连接,启动定时刷新。
- 属性读取函数
 
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;  // 返回状态消息
}
        - 核心功能函数
 
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更新显示
}
        - 槽函数
 
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();  // 通知连接状态改变
        }
    }
}
        - 
主函数 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_OBJECT
    Q_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++类到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();
}
        main.qml文件源码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import SerialPort 1.0
ApplicationWindow {
    id: window
    width: 1000
    height: 800
    title: "串口调试工具"
    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: parent
        color: backgroundColor
    }
    // 串口管理器
    SerialPortManager {
        id: serialPort
    }
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 15
        spacing: 12
        // 串口配置区域
        GroupBox {
            title: "📡 串口配置"
            Layout.fillWidth: true
            background: Rectangle {
                color: cardColor
                border.color: borderColor
                border.width: 1
                radius: 8
            }
            label: Label {
                text: parent.title
                color: primaryColor
                font.bold: true
                font.pixelSize: 14
                padding: 5
            }
            GridLayout {
                columns: 6
                width: parent.width
                rowSpacing: 8
                columnSpacing: 8
                // 第一行
                Label {
                    text: "端口:"
                    color: textColor
                    font.bold: true
                }
                ComboBox {
                    id: portComboBox
                    Layout.fillWidth: true
                    model: serialPort.portList
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                    onModelChanged: {
                        if (model.length > 0 && currentIndex === -1) {
                            currentIndex = 0
                        }
                    }
                }
                Label {
                    text: "波特率:"
                    color: textColor
                    font.bold: true
                }
                ComboBox {
                    id: baudRateComboBox
                    Layout.fillWidth: true
                    model: ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]
                    currentIndex: 3 // 9600
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                }
                Label {
                    text: "数据位:"
                    color: textColor
                    font.bold: true
                }
                ComboBox {
                    id: dataBitsComboBox
                    Layout.fillWidth: true
                    model: ["5", "6", "7", "8"]
                    currentIndex: 3 // 8
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                }
                // 第二行
                Label {
                    text: "校验位:"
                    color: textColor
                    font.bold: true
                }
                ComboBox {
                    id: parityComboBox
                    Layout.fillWidth: true
                    model: ["无", "奇校验", "偶校验", "标记", "空格"]
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                    property var parityValues: [0, 3, 2, 1, 4]
                }
                Label {
                    text: "停止位:"
                    color: textColor
                    font.bold: true
                }
                ComboBox {
                    id: stopBitsComboBox
                    Layout.fillWidth: true
                    model: ["1", "1.5", "2"]
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                    property var stopBitsValues: [1, 3, 2]
                }
                Label {
                    text: "流控制:"
                    color: textColor
                    font.bold: true
                }
                ComboBox {
                    id: flowControlComboBox
                    Layout.fillWidth: true
                    model: ["无", "硬件", "软件"]
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                    property var flowControlValues: [0, 1, 2]
                }
                // 第三行 - 按钮
                Button {
                    id: connectButton
                    text: serialPort.isConnected ? "🔌 断开连接" : "🔗 连接"
                    Layout.columnSpan: 2
                    Layout.fillWidth: true
                    background: Rectangle {
                        color: serialPort.isConnected ? accentColor : secondaryColor
                        radius: 6
                    }
                    contentItem: Text {
                        text: connectButton.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.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: true
                    background: Rectangle {
                        color: primaryColor
                        radius: 6
                    }
                    contentItem: Text {
                        text: parent.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.bold: true
                    }
                    onClicked: serialPort.refreshPorts()
                }
            }
        }
        // 发送区域
        GroupBox {
            title: "📤 发送数据"
            Layout.fillWidth: true
            Layout.preferredHeight: 180
            background: Rectangle {
                color: cardColor
                border.color: borderColor
                border.width: 1
                radius: 8
            }
            label: Label {
                text: parent.title
                color: primaryColor
                font.bold: true
                font.pixelSize: 14
                padding: 5
            }
            ColumnLayout {
                width: parent.width
                spacing: 8
                RowLayout {
                    CheckBox {
                        id: sendHexCheckBox
                        text: "十六进制发送"
                        Layout.alignment: Qt.AlignLeft
                    }
                    CheckBox {
                        id: receiveHexCheckBox
                        text: "十六进制显示"
                        Layout.alignment: Qt.AlignLeft
                    }
                    Item { Layout.fillWidth: true }
                    Button {
                        text: "🧹 清空接收"
                        background: Rectangle {
                            color: warningColor
                            radius: 6
                        }
                        contentItem: Text {
                            text: parent.text
                            color: "white"
                            horizontalAlignment: Text.AlignHCenter
                            verticalAlignment: Text.AlignVCenter
                            font.bold: true
                        }
                        onClicked: serialPort.clearReceivedData()
                    }
                }
                ScrollView {
                    Layout.fillWidth: true
                    Layout.preferredHeight: 80
                    background: Rectangle {
                        color: cardColor
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                    TextArea {
                        id: sendTextArea
                        placeholderText: "请输入要发送的数据..."
                        placeholderTextColor: subTextColor
                        wrapMode: TextEdit.Wrap
                        color: textColor
                        background: Rectangle {
                            color: "transparent"
                        }
                    }
                }
            }
        }
        RowLayout {
            Button {
                text: "🚀 发送"
                background: Rectangle {
                    color: secondaryColor
                    radius: 6
                }
                contentItem: Text {
                    text: parent.text
                    color: "white"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    font.bold: true
                }
                onClicked: {
                    serialPort.sendData(sendTextArea.text, sendHexCheckBox.checked)
                }
            }
            Button {
                text: "🗑️ 清空发送"
                background: Rectangle {
                    color: subTextColor
                    radius: 6
                }
                contentItem: Text {
                    text: parent.text
                    color: "white"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    font.bold: true
                }
                onClicked: sendTextArea.clear()
            }
            Item { Layout.fillWidth: true }
            Label {
                text: "状态: " + serialPort.statusMessage
                color: {
                    if (serialPort.isConnected) return successColor
                    else if (serialPort.statusMessage.includes("错误") ||
                             serialPort.statusMessage.includes("失败")) return errorColor
                    else return textColor
                }
                font.bold: true
                padding: 8
                background: Rectangle {
                    color: backgroundColor
                    radius: 4
                    border.color: borderColor
                    border.width: 1
                }
            }
        }
        // 接收区域
        GroupBox {
            title: "📥 接收数据"
            Layout.fillWidth: true
            Layout.fillHeight: true
            background: Rectangle {
                color: cardColor
                border.color: borderColor
                border.width: 1
                radius: 8
            }
            label: Label {
                text: parent.title
                color: primaryColor
                font.bold: true
                font.pixelSize: 14
                padding: 5
            }
            ColumnLayout {
                width: parent.width
                height: parent.height
                spacing: 8
                ScrollView {
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    background: Rectangle {
                        color: "#2c3e50"
                        border.color: borderColor
                        border.width: 1
                        radius: 4
                    }
                    TextArea {
                        id: receiveTextArea
                        text: serialPort.receivedData
                        wrapMode: TextEdit.Wrap
                        readOnly: true
                        font.family: "Consolas, 'Courier New', monospace"
                        font.pixelSize: 12
                        selectByMouse: true
                        color: "#ecf0f1"
                        background: Rectangle {
                            color: "transparent"
                        }
                    }
                }
                RowLayout {
                    Label {
                        text: "📊 接收字节数: " + receiveTextArea.text.length
                        color: textColor
                        font.bold: true
                        padding: 6
                        background: Rectangle {
                            color: backgroundColor
                            radius: 4
                            border.color: borderColor
                            border.width: 1
                        }
                    }
                    Item { Layout.fillWidth: true }
                    CheckBox {
                        id: autoScrollCheckBox
                        text: "自动滚动"
                        checked: true
                        contentItem: Text {
                            text: autoScrollCheckBox.text
                            color: textColor
                            font.bold: true
                        }
                    }
                    Button {
                        text: "💾 保存数据"
                        background: Rectangle {
                            color: primaryColor
                            radius: 6
                        }
                        contentItem: Text {
                            text: parent.text
                            color: "white"
                            horizontalAlignment: Text.AlignHCenter
                            verticalAlignment: Text.AlignVCenter
                            font.bold: true
                        }
                        onClicked: {
                            // 这里可以添加保存数据的功能
                            console.log("保存数据功能待实现")
                        }
                    }
                }
            }
        }
    }
    // 状态栏
    footer: ToolBar {
        background: Rectangle {
            color: primaryColor
        }
        RowLayout {
            anchors.fill: parent
            Label {
                text: "🔧 串口调试工具 v1.0 | 就绪"
                color: "white"
                font.bold: true
                padding: 8
            }
            Item { Layout.fillWidth: true }
            Label {
                text: new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss")
                color: "white"
                padding: 8
            }
        }
    }
}
        三、效果演示
