【Qt原创开源项目】多功能串口调试助手2.1

概要

Qt版本6.81,项目类型:QMainWindow,用到的库有:QFileDialog、QMessageBox、QDebug、QString、QTimer、QSerialPort、QSerialPortInfo、QFile、QElapsedTimer、QtConcurrent、 QMutex,都是Qt自带的库,项目运行结果如下:

在这里插入图片描述
功能说明:参考项目运行界面显示的功能。

注:本项目在作者另一篇文章【Qt原创开源项目】多功能串口调试助手1.0 的基础上增加了以下功能:

1. 软件窗口大小可自由缩放。
2. 解决高速率刷新显示大量数据时文本框卡顿导致程序崩溃的问题。
3. 捕获数据功能,在状态栏显示捕获信息。
4. 可选择是否在数据接收区显示数据。

本项目为原创,代码免费开源,贴在最下方
本项目提供完整项目压缩包资源,可自行下载,同时欢迎交流

项目组成

在这里插入图片描述
main.cpp文件不需要修改,Sport.pro需要加一行代码,见技术细节第一条。mainwindow.h和mainwindow.cpp的完整代码贴在最后面。

技术细节

基础功能实现的相关技术细节见1.0版本文章:【Qt原创开源项目】多功能串口调试助手1.0

  • 高速率刷新显示大量数据时文本框卡顿导致程序崩溃问题的解决

开始以为是波特率过高(460800)的原因,后来发现是因为接收的数据频率过高(100Hz)、数据量较大(35字节),导致显示接收数据的QTextBrowser组件刷新不过来,导致在卡顿几秒后程序直接崩溃。后面换成QPlainTextEdit组件仍然崩溃。我仔细研究后决定采用以下策略:不把接收数据全部显示在文本框中,当显示超过一定行数时进行清屏。因为对于需要高速率接收大量数据的用户不会有在串口助手的文本框中显示全部数据的需求,只要保证文本框能实时保留显示最新接收的部分数据即可。如果要查看或保留全部接收数据,用户应通过新增加的捕获数据功能将接收到的数据保存到本地文件中再进行后续操作。

// 在MainWindow类声明中添加
private:
    const int MAX_DISPLAY_LINES = 100; // 最大显示行数
    // 自动清理旧内容
    int lineCount = ui->plainTextEdit->document()->lineCount();
    if (lineCount > MAX_DISPLAY_LINES) {
        ui->plainTextEdit->clear();
        QTextCursor trimCursor(ui->plainTextEdit->document());
        trimCursor.setPosition(0);
        trimCursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor,
                                lineCount - MAX_DISPLAY_LINES/2);
        trimCursor.removeSelectedText();
    }
  • 数据捕获功能的实现
  1. 功能设计思路
    状态切换:通过按钮控制捕获状态(开始/停止)
    文件写入:实时将接收数据写入用户指定文件
    性能优化:异步写入防止界面卡顿
  2. 代码实现步骤
    2.1 .pro文件包含模块和头文件声明(mainwindow.h)
QT       += core gui serialport concurrent //.pro文件第一行
#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QString>
#include <QTimer>
#include <QFile>
#include <QElapsedTimer> // 高精度计时模块
#include <QtConcurrent>  //并发编程模块
#include <QMutex> // 互斥锁

class MainWindow : public QMainWindow
{
private slots:
    void on_Key_capture_COM_clicked();
private:
    bool isCapturing = false;       // 捕获状态标志
    QFile captureFile;              // 捕获文件对象
    QTextStream fileStream;         // 文件写入流
    QElapsedTimer captureTimer;     // 计时统计
    QString FilePath;               // 捕获数据保存路径
    qint64 totalCaptured = 0;       // 已捕获字节数
    QMutex fileMutex;               // 互斥锁成员变量

    void startCapture(const QString &fileName);
    void stopCapture();
};
  1. 2捕获控制核心代码(mainwindow.cpp)
// 点击捕获按钮
void MainWindow::on_btnCapture_clicked()
{
    if (!isCapturing) {
        // 首次点击:选择文件并开始捕获
        QString fileName = QFileDialog::getSaveFileName(this,
            tr("选择保存文件"), "", tr("所有文件 (*)"));
        
        if (!fileName.isEmpty()) {
            startCapture(fileName);
        }
    } else {
        // 再次点击:停止捕获
        stopCapture();
    }
}

// 开始捕获
void MainWindow::startCapture(const QString &fileName)
{
    captureFile.setFileName(fileName);
    if (!captureFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::critical(this, "错误", "无法创建文件!");
        return;
    }

    isCapturing = true;
    fileStream.setDevice(&captureFile);
    captureTimer.start();
    totalCaptured = 0;
    
    ui->btnCapture->setText("停止捕获");
    ui->statusBar->showMessage("正在捕获到: " + fileName);
}

// 停止捕获
void MainWindow::stopCapture()
{
    if (captureFile.isOpen()) {
        fileStream.flush();
        captureFile.close();
    }

    isCapturing = false;
    qint64 elapsed = captureTimer.elapsed();
    
    QString msg = QString("捕获完成!总计 %1 字节,耗时 %2 秒")
                  .arg(totalCaptured).arg(elapsed/1000.0, 0, 'f', 1);
    
    ui->btnCapture->setText("开始捕获");
    ui->statusBar->showMessage(msg);
}

// 数据接收处理
void MainWindow::handleDataReceived(QByteArray data)
{
    if (isCapturing) {
        // 异步写入(使用QtConcurrent防止阻塞UI)
        QtConcurrent::run([this, data]() {
            QMutexLocker locker(&fileMutex);
            fileStream << data.toHex(' ') << "\n";
            totalCaptured += data.size();
        });
    }
    
    // 原始数据显示逻辑(保持不变)
    // ui->textBrowser->append(...);
}
  1. 3性能优化关键点

异步写入

// 数据接收处理
void MainWindow::handleDataReceived(QByteArray data)
{
    if (isCapturing) {
        // 异步写入(使用QtConcurrent防止阻塞UI)
        QtConcurrent::run([this, data]() {
            QMutexLocker locker(&fileMutex);
            fileStream << data.toHex(' ') << "\n";
            totalCaptured += data.size();
        });
    }
    
    // 原始数据显示逻辑(保持不变)
    // ui->textBrowser->append(...);
}

缓冲控制(可选,本程序未使用)

// 在MainWindow类中添加缓冲成员
QByteArray writeBuffer;
const int BUFFER_SIZE = 4096; // 4KB缓冲

// 修改handleDataReceived
if (isCapturing) {
    writeBuffer.append(data);
    if (writeBuffer.size() >= BUFFER_SIZE) {
        QtConcurrent::run([this]() {
            QMutexLocker locker(&fileMutex);
            captureFile.write(writeBuffer);
            writeBuffer.clear();
        });
    }
}
  1. 功能扩展建议(本程序未使用)
    3.1 实时统计显示
// 添加定时刷新统计
QTimer *statTimer = new QTimer(this);
connect(statTimer, &QTimer::timeout, [this]() {
    if (isCapturing) {
        QString speed = QString("%1 KB/s")
                       .arg(totalCaptured*1000.0/captureTimer.elapsed()/1024, 0, 'f', 1);
        ui->labelSpeed->setText(speed);
    }
});
statTimer->start(1000); // 每秒更新
  1. 2 自动分割文件
// 在MainWindow类中添加
const qint64 MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB

// 修改handleDataReceived
if (captureFile.size() > MAX_FILE_SIZE) {
    stopCapture();
    startCapture(generateNewFileName()); // 实现自动生成新文件名
}
  1. 3 二进制模式支持
// 修改startCapture中的文件打开方式
if (ui->checkBinary->isChecked()) {
    captureFile.open(QIODevice::WriteOnly); // 二进制模式
} else {
    captureFile.open(QIODevice::WriteOnly | QIODevice::Text); // 文本模式
}
  1. 4 磁盘空间监控
qint64 freeSpace = QStorageInfo(QDir(capturePath)).bytesAvailable();
if (freeSpace < 10 * 1024 * 1024) { // 剩余空间小于10MB
    QMessageBox::warning(this, "警告", "磁盘空间不足!");
    stopCapture();
}
  1. 5 写入异常处理
qint64 written = captureFile.write(data);
if (written != data.size()) {
    QMessageBox::critical(this, "错误", 
                         QString("写入失败!错误码: %1").arg(captureFile.error()));
    stopCapture();
}
  • QTextBrowser组件和QPlainTextEdit组件的对比

QTextBrowser(特定场景使用)

优势

  1. 富文本支持:可显示不同颜色、字体、超链接等,支持HTML/CSS格式化(如错误信息标红)
  2. 交互功能:内置超链接跳转、支持文本搜索(Ctrl+F)

缺点

  1. 性能瓶颈: 处理1万行文本时,内存占用比QPlainTextEdit高2-3倍;频繁更新时(>50次/秒)易导致界面冻结;
  2. 行数统计不直观;lineCount()返回的是段落数而非视觉行数,需要手动处理自动换行的行数计算

适用场景:
需要颜色标记关键数据(如错误帧标红)
数据包含交互元素(如点击特定字段执行操作)
数据量较小(<5000行)且更新频率低(<10次/秒)

示例代码:

// 带颜色标记的插入
void appendColoredData(const QByteArray &data) {
    QString html = QString("<span style='color:red'>%1</span>")
                   .arg(QString::fromLatin1(data));
    ui->textBrowser->insertHtml(html);
    
    // 手动限制行数
    if (ui->textBrowser->document()->blockCount() > 1000) {
        QTextCursor cursor(ui->textBrowser->document()->firstBlock());
        cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 500);
        cursor.removeSelectedText();
    }
}

QPlainTextEdit(推荐大多数场景)
优势

  1. 性能卓越: 专为快速处理纯文本和大文件设计(如日志显示)处理10万行文本时,内存占用比QTextBrowser低30-50%.
    高频插入数据时(如100Hz接收),性能更稳定,不会卡顿。

  2. 行数控制精准:lineCount()返回真实的视觉行数(自动换行也计算在内)。清理旧内容的逻辑更简单(如保留最近500行)

  3. 内存优化:内部使用分块存储,适合长期运行的串口工具。直接支持maximumBlockCount属性限制最大行数

适用场景:
纯文本或十六进制数据显示
高频数据更新(如每10ms接收一次)
需要显示10,000行以上的数据

示例代码:

在这里插入代码片// 初始化设置
ui->plainTextEdit->setMaximumBlockCount(1000); // 自动限制行数
ui->plainTextEdit->setWordWrapMode(QTextOption::NoWrap); // 禁用自动换行

// 插入数据(高性能方式)
void appendData(const QByteArray &data) {
    static QString buffer;
    buffer.append(QString::fromLatin1(data));
    if (buffer.length() > 1000) { // 每1000字符刷新一次
        ui->plainTextEdit->appendPlainText(buffer);
        buffer.clear();
    }
}

结论

首选QPlainTextEdit:在99%的串口助手场景中,它是更优选择

慎用QTextBrowser:仅在需要富文本时使用,且要做好性能优化

混合方案:可同时使用两个控件,用QTextBrowser显示关键信息,QPlainTextEdit显示原始数据流

完整代码

  • mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QString>
#include <QTimer>
#include <QFile>
#include <QElapsedTimer> // 高精度计时模块
#include <QtConcurrent>  //并发编程模块
#include <QMutex> // 互斥锁

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    long receiveNum, sendNum;

private slots:
    void on_Key_open_COM_clicked();
    void on_Key_send_COM_clicked();
    void on_Key_send_COM_clear_clicked();
    void on_Key_receive_COM_clear_clicked();
    void on_Key_save_COM_clicked();
    void on_Key_clearBytes_clicked();
    void on_checkBox_timeSend_clicked();

    void COM_DataReceive();

    void on_Key_capture_COM_clicked();

// 点击下拉框刷新串口
public slots:
    bool eventFilter(QObject*, QEvent*);

private:
    Ui::MainWindow *ui;
    void UiInit();
    void String2Hex(QString str, QByteArray &senddata);
    char ConvertHexChar(char ch);
    QTimer* timeSend;

    const int MAX_DISPLAY_LINES = 100;

    bool isCapturing = false;       // 捕获状态标志
    QFile captureFile;              // 捕获文件对象
    QTextStream fileStream;         // 文件写入流
    QElapsedTimer captureTimer;     // 计时统计
    QString FilePath;               // 捕获数据保存路径
    qint64 totalCaptured = 0;       // 已捕获字节数
    QMutex fileMutex;               // 互斥锁成员变量

    void startCapture(const QString &fileName);
    void stopCapture();
};
#endif // MAINWINDOW_H

  • mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "QSerialPort"      //串口
#include "QSerialPortInfo"  //串口信息

/* 定义串口指针,实例化串口 */
QSerialPort* COM = new QSerialPort();

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    UiInit();

    /* 数据接收信号槽链接 */
    // 谁发出信号,什么信号(成功读取的信号),谁接收信号,执行信号的槽
    connect(COM, SIGNAL(readyRead()), this, SLOT(COM_DataReceive()));
}

MainWindow::~MainWindow()
{
    delete ui;
}


/* 发送数据 */
void MainWindow::on_Key_send_COM_clicked()
{
    QByteArray data_buf;
    QString data_str;

    data_buf = ui->textEdit->toPlainText().toLatin1();

    int count;
    // 发送成功返回发送字节长度,发送失败返回-1
    //count = COM->write(ui->textEdit->toPlainText().toLatin1()); // 串口写数据(发送),获取发送框的数据发送
    // 如果要正确发送中文字符,使用.toLocal8Bit()
    if( !data_buf.isEmpty())
    {
        data_str = QString(data_buf);
        if(ui->checkBox_send_Hex->isChecked())
        {
            QByteArray data_hex;
            String2Hex(data_str, data_hex);
            count = COM->write(data_hex);
        }
        else
        {
            count = COM->write(data_buf);
        }
        sendNum += count;
        ui->sendBytes->setText(QString::number(sendNum));
        ui->sendBytes->setAlignment(Qt::AlignCenter);
    }
}

/* 清空发送 */
void MainWindow::on_Key_send_COM_clear_clicked()
{
    ui->textEdit->clear(); // 清空发送区文本框
}

/* 串口开关 */
void MainWindow::on_Key_open_COM_clicked()
{
//    QSerialPort::BaudRate baudRate; // 波特率
    QSerialPort::DataBits dataBits; // 数据位
    QSerialPort::StopBits stopBits; // 停止位
    QSerialPort::Parity   checkBits;// 校验位

    if(ui->comboBox_dataBits->currentText() == "8")
        dataBits = QSerialPort::Data8;
    else if(ui->comboBox_dataBits->currentText() == "7")
        dataBits = QSerialPort::Data7;
    else if(ui->comboBox_dataBits->currentText() == "6")
        dataBits = QSerialPort::Data6;
    else if(ui->comboBox_dataBits->currentText() == "5")
        dataBits = QSerialPort::Data5;

    if(ui->comboBox_stopBits->currentText() == "1")
        stopBits = QSerialPort::OneStop;
    else if(ui->comboBox_stopBits->currentText() == "1.5")
        stopBits = QSerialPort::OneAndHalfStop;
    else if(ui->comboBox_stopBits->currentText() == "2")
        stopBits = QSerialPort::TwoStop;

    if(ui->comboBox_checkBits->currentText() == "无校验")
        checkBits = QSerialPort::NoParity;
    else if(ui->comboBox_checkBits->currentText() == "奇校验")
        checkBits = QSerialPort::OddParity;
    else if(ui->comboBox_checkBits->currentText() == "偶校验")
        checkBits = QSerialPort::EvenParity;

    // 串口属性初始化
    COM->setPortName(ui->comboBox_COMnumber->currentText());
    COM->setBaudRate(ui->comboBox_baudRate->currentText().toInt());
    //COM->setBaudRate(baudRate);
    COM->setDataBits(dataBits);
    COM->setStopBits(stopBits);
    COM->setParity(checkBits);

    // 串口操作——打开/关闭
    if(ui->Key_open_COM->text() == "打开串口")
    {
        if(COM->open(QIODevice::ReadWrite) == true) // 串口打开成功
        {
            ui->Key_send_COM->setEnabled(true);
            ui->checkBox_timeSend->setEnabled(true);
            ui->lineEdit->setEnabled(true);

            ui->Key_open_COM->setText("关闭串口");
            ui->Key_open_COM->setStyleSheet("background:lightgreen;border-right:2px solid gray;border-bottom:2px solid gray");
            ui->comStatus->setText("打 开");
            ui->comStatus->setAlignment(Qt::AlignCenter);
        }
        else
        {
            QMessageBox::critical(this, "错误提示", "打开串口失败!\n请检查串口连接是否正常");
        }
    }
    else if(ui->Key_open_COM->text() == "关闭串口")
    {
        ui->Key_send_COM->setEnabled(false);
        ui->checkBox_timeSend->setEnabled(false);
        ui->lineEdit->setEnabled(false);
        // 关闭定时发送
        if(ui->checkBox_timeSend->isChecked())
        {
            ui->checkBox_timeSend->setCheckState(Qt::Unchecked);
            timeSend->stop();
        }
        COM->close();
        ui->Key_open_COM->setText("打开串口");
        ui->Key_open_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
        ui->comStatus->setText("关 闭");
        ui->comStatus->setAlignment(Qt::AlignCenter);
    }
}

/* 接收数据 */
void MainWindow::COM_DataReceive()
{
    QByteArray data_buf; // 接收缓冲区
    QString data_str; // 定义一个字符变量

    data_buf = COM->readAll(); //接收数据存储到缓存区

    if( !data_buf.isEmpty() ) // 如果接收到数据
    {
        data_str = tr(data_buf); //将接收数据从字节数据类型转为字符类型,用于显示

        if(ui->checkBox_receive_Hex->isChecked())
        {
            QString data_hex = data_buf.toHex().data();
            data_hex = data_hex.toUpper(); // 转化为大写
            QString data_hex_str;
            // 将data_hex按每两个字符一组进行分割
            for(int i=0; i < data_hex.length(); i+=2)
            {
                QString str_temp = data_hex.mid(i, 2);
                data_hex_str += str_temp;
                data_hex_str += ' ';
            }

            if(isCapturing) // 捕获十六进制数据
            {
                // 异步写入(使用QtConcurrent防止阻塞UI)
                QtConcurrent::run([this,data_buf,data_hex_str](){
                    QMutexLocker locker(&fileMutex);
                    fileStream << data_hex_str;
                    totalCaptured += data_buf.size();
                });
            }
            if(ui->checkBox_display->isChecked()) // 显示数据
            {
                ui->plainTextEdit->insertPlainText(data_hex_str); // 在文本框显示
                ui->plainTextEdit->moveCursor(QTextCursor::End);  // 光标移到最后一行
            }
        }
        else
        {
            if(isCapturing) // 捕获字符数据
            {
                // 异步写入(使用QtConcurrent防止阻塞UI)
                QtConcurrent::run([this,data_buf,data_str](){
                    QMutexLocker locker(&fileMutex);
                    fileStream << data_str;
                    totalCaptured += data_buf.size();
                });
            }
            if(ui->checkBox_display->isChecked()) // 显示数据
            {
            ui->plainTextEdit->insertPlainText(data_str);    // 在文本框显示
            ui->plainTextEdit->moveCursor(QTextCursor::End); // 光标移到最后一行
            }
        }
    }

    // 自动清理旧内容
    int lineCount = ui->plainTextEdit->document()->lineCount();
    if (lineCount > MAX_DISPLAY_LINES) {
        ui->plainTextEdit->clear();
        QTextCursor trimCursor(ui->plainTextEdit->document());
        trimCursor.setPosition(0);
        trimCursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor,
                                lineCount - MAX_DISPLAY_LINES/2);
        trimCursor.removeSelectedText();
    }

    receiveNum += data_buf.size();
    ui->receiveBytes->setText(QString::number(receiveNum));
    ui->receiveBytes->setAlignment(Qt::AlignCenter);

    // 捕获数据时,底部状态栏显示信息
    if(isCapturing)
    {
        QString msg = QString("已捕获 %1 字节").arg(totalCaptured);
        ui->statusbar->showMessage("正在捕获数据到:" + FilePath + "    " + msg);
    }
}

/* 清空接收 */
void MainWindow::on_Key_receive_COM_clear_clicked()
{
    ui->plainTextEdit->clear();
}

/* 保存数据 */
void MainWindow::on_Key_save_COM_clicked()
{
    // 第四个参数不写就表示所有文件
    QString filename = QFileDialog::getSaveFileName(this, "选择保存位置",
                                                    QCoreApplication::applicationFilePath(), ".txt");
    if(filename.isEmpty())
    {
        //QMessageBox::warning(this, "警告", "请选择一个文件夹");
    }
    else
    {
        QFile file(filename);
        file.open(QIODevice::WriteOnly);
        //ui->textEdit->toPlainText();
        QByteArray ba;
        // 把QString接在QByreArray后面,此时ba没东西,相当于将QByteArray转换成了QString
        ba.append(ui->plainTextEdit->toPlainText().toLatin1());
        file.write(ba);
        file.close();
    }
}

/* 给串口号下拉框添加事件过滤器,鼠标点击时刷新串口 */
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
    if(event->type() == QEvent::MouseButtonPress)
    {
        if(object == ui->comboBox_COMnumber)
        {
            QComboBox* combobox = qobject_cast<QComboBox* >(object);
            // combobox->clear();
            // combobox->addItem("list");

            combobox->clear();
            foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
            {
                ui->comboBox_COMnumber->addItem(info.portName());
            }

        }
    }
    return QWidget::eventFilter(object,event);
}

/* 清空计数 */
void MainWindow::on_Key_clearBytes_clicked()
{
    receiveNum = 0;
    sendNum = 0;
    ui->receiveBytes->setText(QString::number(receiveNum));
    ui->receiveBytes->setAlignment(Qt::AlignCenter);
    ui->sendBytes->setText(QString::number(sendNum));
    ui->sendBytes->setAlignment(Qt::AlignCenter);
}

void MainWindow::String2Hex(QString str, QByteArray &senddata)
{
    int hexdata,lowhexdata;
    int hexdatalen = 0;
    int len = str.length();
    senddata.resize(len/2);
    char lstr,hstr;
    for(int i=0; i<len; )
    {
        //char lstr,
        hstr=str[i].toLatin1();
        if(hstr == ' ')
        {
            i++;
            continue;
        }
        i++;
        if(i >= len)
            break;
        lstr = str[i].toLatin1();
        hexdata = ConvertHexChar(hstr);
        lowhexdata = ConvertHexChar(lstr);
        if((hexdata == 16) || (lowhexdata == 16))
            break;
        else
            hexdata = hexdata*16+lowhexdata;
        i++;
        senddata[hexdatalen] = (char)hexdata;
        hexdatalen++;
    }
    senddata.resize(hexdatalen);
}

char MainWindow::ConvertHexChar(char ch)
{
    if((ch >= '0') && (ch <= '9'))
        return ch-0x30;
    else if((ch >= 'A') && (ch <= 'F'))
        return ch-'A'+10;
    else if((ch >= 'a') && (ch <= 'f'))
        return ch-'a'+10;
    else return (-1);
}

/* 定时发送 */
void MainWindow::on_checkBox_timeSend_clicked()
{
    int state = ui->checkBox_timeSend->checkState();
    if(state == Qt::Checked) // 勾选
    {
        if(ui->lineEdit->text()==NULL)
        {
            QMessageBox::about(this, tr("提示"), tr("请设置时间间隔"));
            ui->checkBox_timeSend->setCheckState(Qt::Unchecked);
        }
        else
        {
            // 勾选定时发送时,发送按键无法选中
            ui->Key_send_COM->setEnabled(false);
            ui->lineEdit->setEnabled(false);
            int time=ui->lineEdit->text().toInt();
            timeSend = new QTimer();
            timeSend->setInterval(time);
            connect(timeSend, &QTimer::timeout, this, [=](){on_Key_send_COM_clicked();});
            timeSend->start();
        }
    }
    else // 取消勾选
    {
        timeSend->stop();
        ui->Key_send_COM->setEnabled(true);
        ui->lineEdit->setEnabled(true);
    }
}

/* 捕获数据 */
void MainWindow::on_Key_capture_COM_clicked()
{
    if(!isCapturing)
    {
        // 首次点击:选择文件开始捕获
        QString fileName = QFileDialog::getSaveFileName(this, tr("选择保存文件"), QCoreApplication::applicationFilePath(), "");
        FilePath = fileName;
        if(!fileName.isEmpty())
        {
            startCapture(fileName);
        }
    }
    else
    {
        // 再次点击:停止捕获
        stopCapture();
    }
}

/* 开始捕获 */
void MainWindow::startCapture(const QString &fileName)
{
    captureFile.setFileName(fileName);
    if(!captureFile.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QMessageBox::critical(this, "错误", "无法创建文件!");
        return;
    }

    isCapturing = true;
    fileStream.setDevice(&captureFile);
    captureTimer.start();
    totalCaptured = 0;

    ui->Key_capture_COM->setText("停止捕获");
    ui->Key_capture_COM->setStyleSheet("background:lightcoral");
    // ui->statusbar->showMessage("正在捕获数据到:" + fileName);
}
/* 停止捕获 */
void MainWindow::stopCapture()
{
    if(captureFile.isOpen())
    {
        fileStream.flush();
        captureFile.close();
    }

    isCapturing = false;
    qint64 elapsed = captureTimer.elapsed();

    QString msg = QString("数据捕获完成!共捕获 %1 字节,耗时 %2 秒").arg(totalCaptured).arg(elapsed/1000.0, 0, 'f', 1);

    ui->Key_capture_COM->setText("捕获数据");
    ui->Key_capture_COM->setStyleSheet("");
    ui->statusbar->showMessage(msg);
}

/* Ui界面初始化 */
void MainWindow::UiInit()
{
    this->setWindowTitle("Sport串口调试助手");

    // ui->groupBox->setStyleSheet("border:1px solid gray");
    // ui->groupBox_2->setStyleSheet("border:1px solid gray");
    // ui->groupBox_3->setStyleSheet("border:1px solid gray");
    // ui->groupBox_4->setStyleSheet("border:1px solid gray");

    ui->label->setStyleSheet("border:none");
    ui->label_2->setStyleSheet("border:none");
    ui->label_3->setStyleSheet("border:none");
    ui->label_4->setStyleSheet("border:none");
    ui->label_5->setStyleSheet("border:none");
    ui->label_6->setStyleSheet("border:none");
    ui->label_7->setStyleSheet("border:none");
    ui->label_8->setStyleSheet("border:none");
    ui->label_9->setStyleSheet("border:none");
    ui->checkBox_receive_Hex->setStyleSheet("border:none");
    ui->checkBox_send_Hex->setStyleSheet("border:none");
    ui->checkBox_timeSend->setStyleSheet("border:none");

    ui->Key_open_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
    ui->Key_clearBytes->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
    ui->Key_save_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
    ui->Key_send_COM->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
    ui->Key_send_COM_clear->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");
    ui->Key_receive_COM_clear->setStyleSheet("border-right:2px solid gray;border-bottom:2px solid gray");

    ui->Key_send_COM->setEnabled(false);
    ui->checkBox_timeSend->setEnabled(false);
    ui->lineEdit->setEnabled(false);

    receiveNum = 0;
    sendNum = 0;
    ui->receiveBytes->setText(QString::number(receiveNum));
    ui->receiveBytes->setAlignment(Qt::AlignCenter);
    ui->sendBytes->setText(QString::number(sendNum));
    ui->sendBytes->setAlignment(Qt::AlignCenter);

    ui->comboBox_COMnumber->installEventFilter(this);

    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    { // 添加串口名
        ui->comboBox_COMnumber->addItem(info.portName());
    }
    foreach(qint32 baud, QSerialPortInfo::standardBaudRates())
    { // 添加波特率
        ui->comboBox_baudRate->addItem(QString::number(baud));
    }
    ui->comboBox_baudRate->addItem("230400");
    ui->comboBox_baudRate->addItem("460800");
    ui->comboBox_baudRate->addItem("921600");
    ui->comboBox_baudRate->setCurrentText("460800");
}
  • mainwindow.ui

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值