开源 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: 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 确保背景填充整个窗口

  1. 串口管理器实例化

    SerialPortManager {
    id: serialPort
    }

创建 C++ SerialPortManager 类的 QML 实例

id: serialPort 用于在 QML 中引用该对象

  1. 主布局结构

ColumnLayout 主容器

复制代码
ColumnLayout {
    anchors.fill: parent
    anchors.margins: 15
    spacing: 12

垂直布局管理器,包含所有界面元素

anchors.fill: parent 填充整个窗口

设置边距和组件间距

  1. 串口配置区域详细分析

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++ 函数

空值检查:确保选择了端口

  1. 发送数据区域

模式选择 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()
    }
}

发送按钮:传递文本内容和格式模式

清空按钮:清除输入框内容

  1. 状态显示

    Label {
    text: "状态: " + serialPort.statusMessage
    color: {
    if (serialPort.isConnected) return successColor
    else if (serialPort.statusMessage.includes("错误") ||
    serialPort.statusMessage.includes("失败")) return errorColor
    else return textColor
    }
    }

动态状态文本:显示当前操作状态

智能颜色切换:

连接成功:绿色

错误/失败:红色

其他状态:默认颜色

  1. 接收数据区域

接收显示区域

复制代码
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("保存数据功能待实现")
        }
    }
}

字节统计:实时显示接收数据长度

自动滚动:控制是否自动滚动到最新内容

保存功能:预留数据保存接口

  1. 状态栏

    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"
    }
    }
    }

应用信息:显示版本和状态

实时时钟:显示当前时间

主色调背景:与整体设计保持一致

  1. 数据绑定和交互机制

属性绑定

复制代码
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(); // 初始刷新串口列表

    }

功能:初始化所有成员变量,建立信号槽连接,启动定时刷新。

  1. 属性读取函数

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;  // 返回状态消息
}
  1. 核心功能函数

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更新显示
}
  1. 槽函数

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();  // 通知连接状态改变
        }
    }
}
  1. 主函数 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
            }
        }
    }
}

三、效果演示

相关推荐
Janspran2 小时前
监控系统2 - framebuffer
c++
代码AC不AC3 小时前
【C++】AVL树的模拟实现
c++·avl树·底层原理
_w_z_j_3 小时前
C++----bitmap位图的使用
开发语言·c++
BingeBlog3 小时前
[01] Qt的UI框架选择和对比
开发语言·c++·笔记·qt·ui·开源软件
greentea_20134 小时前
Codeforces Round 173 B. Digits(2043)
c++·算法
刚子编程4 小时前
ZKEACMS:基于ASP.Net Core开发的开源免费内容管理系统
开源
xiejava10185 小时前
开源安全管理平台wazuh-文件完整性监控FIM
安全·开源·wazuh
Q741_1475 小时前
C++ 位运算 高频面试考点 力扣 面试题 17.19. 消失的两个数字 题解 每日一题
c++·算法·leetcode·面试·位运算
初圣魔门首席弟子6 小时前
C++ STL string(字符串)学习笔记
c++·笔记·学习