【C++ QT项目2】——高仿安信可串口调试助手

news2024/11/22 9:58:55

【C++ QT项目2】——高仿安信可串口调试助手

  • 1. 项目概述
  • 2. 项目UI设计
  • 3. 串口通信核心代码开发
    • 3.1 QSerialPort介绍及示例
    • 3.2 扫描系统串口
    • 3.3 数据的收发
    • 3.4 定时发送(QT定时器)
    • 3.5 HEX显示与发送
  • 4. 串口调试助手功能的优化
    • 4.1 串口的实时扫描
    • 4.2 获取系统当前时间
    • 4.3 按钮数组的遍历与绑定槽函数
    • 4.4 循环发送实现(定时器与Qt多线程并发)
    • 4.5 重置保存与载入

1. 项目概述

  串口调试助手是一种串口通讯测试工具,它可以用于打开、关闭、配置串口,读写串口数据等常见的串口通信操作。 在嵌入式系统调试、模块测试、通讯协议分析等领域都具有广泛的应用。
  串口助手通常提供GUI界面,让用户可以更加方便、直观地进行串口通讯测试和调试。用户可以通过界面上的下拉框和按钮来配置串口参数,打开/关闭串口以及发送和接收串口数据。它还支持16进制显示和发送,方便用户进行二进制数据的调试和测试。
  本次设计高仿安信可串口调试助手进行设计与实现相应的功能

2. 项目UI设计

实际运行效果UI界面

  UI界面整体布局为垂直布局,其中上面为上方为三个groupBox的水平布局接收区、历史记录区以及多文本区。中间部分分为左右两则,左侧为串口参数设置,右边又分为上下两个groupBox的垂直布局。最下方是一栏状态栏标签数据的显示,布局完全模仿安信可串口调试助手设置。

注意: 在布局时候,对于控件的命名尤为重要,方便后期程序的开发与提高辨别度

以下提供种本博文中主要用到的变量命名
在这里插入图片描述
在这里插入图片描述

3. 串口通信核心代码开发

3.1 QSerialPort介绍及示例

  QSerialPort类是Qt框架中用于串口通信的类,它允许与串口设备进行通信,如传感器、微控制器、GPS接收器等,可以使用这个类来发送和接收数据,配置传偶参数满足实际通信需求。

以下是一些常见的QSerialPort类的用法和方法:
1. 打开和关闭串口

  • bool QSerialPort::open(QIODevice::OpenMode mode); 打开串口
  • void QSerialPort::close();              关闭串口

2. 配置串口参数

  • setPortName(const QString &name);       设置端口号
  • setBaudRate(qint32 baudRate);         设置波特率
  • setDataBits(QSerialPort::DataBits dataBits);   设置数据位
  • setStopBits(QSerialPort::StopBits stopBits);   设置停止位
  • setParity(QSerialPort::Parity parity);       设置校验位
  • setFlowControl(QSerialPort::FlowControl flowControl);  设置流控

3. 读取和写入数据

  • qint64 write(const char *data);  向串口写入数据
  • readAll();           读取所有可用的串口数据
  • read(qint64 maxSize);      读取指定大小数据
  • waitForReadyRead(int msecs); 等待串口准备好读取数据

4. 信号与槽
  QSerialPort类提供了一些信号,如readyRead,用于在接收到新数据时发出信号,可以连接这些信号到槽函数以处理数据。
5. 错误处理
  可以使用error()方法和errorString() 方法来检测和获取串口通信过程中可能发生的错误。
以下是一个简单的实例代码,演示如何使用QSerialPort类来打开串口,发送和接收数据

#include <QApplication>
#include <QDebug>
#include <QSerialPort>
#include <QSerialPortInfo>
int main(int argc, char *argv[])
{
 	QApplication app(argc, argv);
 	
	//创建一个QSerialPort对象
	QSerialPort serialPort;

    //配置串口参数
    serialPort.setPortName("COM1");
    serialPort.setBaudRate(QSerialPort::Baud9600);
    serialPort.setDataBits(QSerialPort::Data8);
    serialPort.setParity(QSerialPort::NoParity);
    serialPort.setStopBits(QSerialPort::OneStop);
    //serialPort.setFlowControl(QSerialPort::NoFlowControl);

    //尝试打开串口
    if(serialPort.open(QIODevice::ReadWrite))
    {
        qDebug() << "串口打开成功";

        QByteArray data = "hello world!";

        //向串口写数据
        serialPort.write(data);


        /***********这里添加其他代码和事件处理逻辑***********/

        //连接readyRead信号与槽函数,接收到数据时候执行槽函数
        QObject::connect(&serialPort, &QSerialPort::readyRead, [&](){
            //读取所有可用的串口数据
            QByteArray receiveData = serialPort.readAll();
            qDebug() << "Received data: " << receiveData;
        });

        /***********这里添加其他代码和事件处理逻辑***********/

        serialPort.close();
    }
    else
    {
        qDebug() << "串口打开失败";
    }

    return app.exce();
}

  在这个示例中,首先配置了串口参数,然后打开串口,发送数据,并设置了一个槽函数用来处理接收到的数据,最后,在应用程序的事件循环中运行。注意,需要根据实际串口配置和需求来调整代码

3.2 扫描系统串口

  在Qt中使用QSerialPort类来扫描系统中的串口设备非常简单,可以使用QSerialPortInfo类来获取有关系统中可用串口设备的信息,以下是一个实例代码,演示如何扫描系统中的串口。

#include <QSerialPortInfo>
#include <QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
 	QApplication app(argc, argv);
	//提取系统中所有可用的串口信息
	QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
    if(serialList.isEmpty())
    {
        qDebug() << "no serial ports found.";
    }
    else
    {
        qDebug() << "available ports found.";
        //遍历打印每个串口的信息
        for(QSerialPortInfo serialInfo: serialList)
        {
            qDebug() << "port: " << serialInfo.portName();  //端口名
            qDebug() << "Description: " << serialInfo.description();
            qDebug() << "Manufacturer: " << serialInfo.manufacturer();
            qDebug() << "System Location: " << serialInfo.systemLocation();
            qDebug() << "Vendor ID: " << serialInfo.vendorIdentifier();
            qDebug() << "Product ID: " << serialInfo.productIdentifier();
            qDebug() << "-------------------------" << endl;
        }
    }
	 return app.exce();
}

  上述代码会打印出系统中所有可用串口的信息,包括串口名字、描述、制造商、位置等,可以根据这些信息来选择要与之通信的串口,其中主要用于配置的是串口名字

将扫描到的可用串口添加至comboBox选择框:

 for(QSerialPortInfo serialInfo: serialList){
  	ui->comboBox_sreialNum->addItem(serialInfo.portName());
 }

扫描可用串口的核心程序如下所示:

//检测系统可用串口,并添加至comboBox控件
QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
if(serialList.isEmpty())
{
    qDebug() << "no serial ports found.";
}
else
{
    //qDebug() << "available ports found.";
    //遍历打印每个串口的信息
    for(QSerialPortInfo serialInfo: serialList)
    {
        //qDebug() << "port: " << serialInfo.portName();  //端口名
        ui->comboBox_sreialNum->addItem(serialInfo.portName());
    }
}

3.3 数据的收发

数据的发送: 通过 write() 方法实现数据的发送,其中返回值为成功发送的字节数

//发送按钮槽函数
void Widget::on_btnSend_clicked()
{
	//从行编辑器中获取待发送的数据,注意类型的转化
    const char* sendData =  ui->lineEditSendContext->text().toStdString().c_str();
    //字符串长最好用以下转化方式
    //const char* sendData =  ui->lineEditSendContext->text().toLocal8Bit().constData();
    int nwrite = serialPort->write(sendData);	//获取成功发送字节数
    if(nwrite == -1){
        ui->labelSendStatus->setText("Send error!");
    }
    else	//成功发送数据
    {
        qDebug() << "send ok: " << sendData;
		/***********这里添加其他代码和事件处理逻辑***********/
    }
}

数据的接收: 在接收到新数据时发出信号 readyRead ,通过绑定信号与槽实现数据的接收

构造函数内绑定信号与槽:

//绑定发送与接收信号与槽
connect(serialPort, &QSerialPort::readyRead, this, &Widget::on_serialData_readyToRead);

.cpp文件内实现槽函数:

//接收数据槽函数
void Widget::on_serialData_readyToRead()
{
	//定义存放数据的变量
    QString revMessage = serialPort->readAll();
    if(revMessage != NULL )
    {
    	//将数据添加至文本框内
        ui->textEditRev->append(revMessage);
        qDebug() << "rev: " << revMessage;
    }
}

3.4 定时发送(QT定时器)

参考博文:Qt扫盲-QTimer定时器理论总结
  QTimer 其实就是一个定时器工具类,定时器就是间隔一定时间后,去执行某一个任务,这个在很多场景都会使用的,我经常使用的即是弹窗自动关闭,就是消息框自动关闭之类的功能。
  Qt封装的定时器是使用很简单的,QTimer 类为定时器提供了高级编程接口。要使用它,需要创建一个QTimer,将其timeout()信号连接到适当的槽位,然后调用start()。从那时起,它将以固定的时间间隔发出timeout()信号

通过CheckBox控件来完成定时器的启动与关闭示例:

timer = new QTimer(this);		//实例化定时器

void Widget::on_timeSend_clicked(bool checked)
{
	//选中
    if(checked)
    {
         //qDebug() << "on_timeSend_clicked true";
         //启动定时器,并设定定时时间为500ms,定时器每500ms将发出timeout信号
         timer->start(1000);	
    }
    else	//取消选中
    {
        //qDebug() << "on_timeSend_clicked false";
        timer->stop();		/停止定时
    }
}

绑定timeout信号与槽函数,即每500ms运行一次槽函数(从单片机角度理解为定时器中断服务函数)

//绑定定时器定时槽函数
connect(timer, &QTimer::timeout, [=](){
	//直接调用发送按钮的槽函数,每500ms定时发送数据
    on_btnSend_clicked();		
});

3.5 HEX显示与发送

HEX显示

  • 读取文本框数据 QString 类型

  • 将 QString 类型的数据转化成 HEX 格式

    • 需要将QString类型转为QByteArray 类型,利用 toUtf8()方法
    • QByteArray 类型转成 HEX 格式,利用 toHex() 方法
  • QByteArray 类型的 HEX 格式数据再转为 QString 类型用于在文本编辑器上显示

    //HEX显示槽函数
    void Widget::on_checkBoxHexShow_clicked(bool checked)
    {
        //HEX显示
        if(checked)
        {
            //1. 读取文本框数据
            QString tempStr = ui->textEditRev->toPlainText();
            
            //2. 转化成hex
            QByteArray tempHex= tempStr.toUtf8();
            tempHex = tempHex.toHex();
    
            //3. 显示
            QString lastShow;
            tempStr = QString::fromUtf8(tempHex);   //12345678
            for(int i = 0; i< tempStr.size(); i += 2)
            {
                //空格间隔 12 34 56 78
                lastShow += tempStr.mid(i,2) + " ";
            }
    
            ui->textEditRev->setText(lastShow.toUpper());
        }
        else	//取消HEX显示
        {
            //1. 读取文本框数据 QString 类型
            QString tempStr = ui->textEditRev->toPlainText();
            //2. 转化成hex格式
            //QString 转成 QByteArray 类型
            QByteArray tempUtf8 = tempStr.toUtf8();
            // QByteArray类型的HEX格式数据转为字符格式 
            QByteArray lastShow= QByteArray::fromHex(tempUtf8);
            //3. 显示
            ui->textEditRev->setText(QString::fromUtf8(lastShow));
        }
    }
    

    注意: 因为 QString 类型无法直接转为 HEX 格式,因此需要先将数据转为 QByteArray 类型

HEX发送

  • 读取文本框数据 QString 类型,并将其转化为 QByteArray 类型
  • 判断是否为偶数位、是否符合16进制的表达
  • 转化成16进制发送,避免成为字符发送
    if(ui->checkBoxHexSend->isChecked())    //HEX发送
    {
    	// 读取发送文本框数据
    	QString temp = ui->lineEditSendContext->text();
    	//数据转化为QByteArray类型,用toUtf8方法也可以
    	QByteArray tempArray = temp.toLocal8Bit();
    	
    	//判断是否为偶数位
    	if(tempArray.size() % 2 != 0)
    	{
    	    ui->labelSendStatus->setText("error input");
    	    return;
    	}
    	
    	//判断每一位是否符合十六进制
    	for(char c : tempArray)
    	{
    	    if(!std::isxdigit(c))
    	    {
    	        ui->labelSendStatus->setText("error input");
    	        return;
    	    }
    	}
    	
    	//勾选发送新行选项
    	if(ui->checkBoxSendNewLine->isChecked())
    	    tempArray.append("\r\n");
    	
    	//转化成16进制发送
    	QByteArray arraySend = QByteArray::fromHex(tempArray);
    	nwrite = serialPort->write(arraySend);
    }
    

4. 串口调试助手功能的优化

4.1 串口的实时扫描

  在实际应用中,每次插入串口后,在点击串口选择串口号时应检测当下的可用串口,并将其可用串口添加至选择框中。因此在实现的过程中,需要将选项框提升为自定义控件MycomboBox,检测鼠标左键按下事件,在对应的槽函数中重新检测当前可用的串口号,并将添加至选项框中

自定义控件在记事本项目中有涉及,具体可参考博文:C++ QT入门2——记事本功能实现与优化 中的事件自定义按钮

自定义控件MycomboBox
类的声明.h文件:

//自定义控件继承于QComboBox
class MycomboBox : public QComboBox
{
    Q_OBJECT

public:
    MycomboBox(QWidget *parent);
    
//重写鼠标按下事件
protected:
    void mousePressEvent(QMouseEvent *e) override;
//自定义鼠标按下事件发送的刷新信号
signals:
    void refresh();
};

.cpp文件重写自定义控件:当鼠标左键按下时,发出refresh信号

//构造函数
MycomboBox::MycomboBox(QWidget *parent):QComboBox(parent)
{
}
//重写鼠标移动事件函数
void MycomboBox::mousePressEvent(QMouseEvent *e)
{
    //如果检测到左键按下,发出一个refresh信号
    if(e->button() == Qt::LeftButton)
        emit refresh();
    //将事件e传回原来的按下的事件操作,否则将无法选择端口,被自定义控件占用
    QComboBox::mousePressEvent(e);
}

注意: 记得需要在UI文件中将控件提升为自定义控件

主界面检测refresh信号,绑定槽函数,当检测到该信号时,重新检测当前可用串口号并添加在选项框中

//绑定信号与槽函数,按下comboBox就检测系统可用串口
connect(ui->comboBox_sreialNum, &MycomboBox::refresh, this, &Widget::refreshSerialName);
//槽函数实现刷新当前可用串口号并添加至控件
void Widget::refreshSerialName()
{
    ui->comboBox_sreialNum->clear();	//清空原有串口号
    //检测系统可用串口,并添加至comboBox控件
    QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
    if(serialList.isEmpty())
    {
        qDebug() << "no serial ports found.";
    }
    else
    {
        //qDebug() << "available ports found.";
        //遍历打印每个串口的信息
        for(QSerialPortInfo serialInfo: serialList)
        {
            //qDebug() << "port: " << serialInfo.portName();  //端口名
            //串口名添加至comboBox控件
            ui->comboBox_sreialNum->addItem(serialInfo.portName());
        }
    }
    ui->labelSendStatus->setText("COM refreshed");
}

4.2 获取系统当前时间

参考博文:qt – 获取当前时间 QDateTime、QTime、QDate

时间日期类型:
QDateTime:日期时间数据类型,表示日期和时间,如:2024-01-01 05:20:21
QDate:  日期数据类型,表示日期,如:2024-01-01
QTime:  时间数据类型,表示时间,如:05:20:21

获取日期时间数据类型:

QDateTime dateTime = QDateTime::currentDateTime();			//获取系统当前时间

QString str = dateTime .toString("yyyy-MM-dd hh:mm:ss");	//格式化时间

日期时间数据类 QDateTime 存放方法 date 获取日期类(QDate)对象,存放 time 方法获取时间类(QTime)对象

QDate date = dateTime.date();	//通过date方法获取日期类对象
QTime time = dateTime.time();	//通过time方法获取时间类对象

再通过日期对象的方法 yearmonthday 来获取系统的年月日数据,通过时间对象的方法 hourminutesecond 获取系统时间的时、分、秒数据。

QDateTime dateTime = QDateTime::currentDateTime();			//获取系统当前时间

QDate date = dateTime.date();	//通过date方法获取日期类对象
int year  = date.year();
int month = date.month();
int day   = date.day();

QTime time = dateTime.time();	//通过time方法获取时间类对象
int hour = time.hour();
int min  = time.minute();
int sec  = time.second();

最后可以将各个数据封装成一个QString类型的字符串,并格式化位宽、进制以及结束字符

realTime = QString("%1-%2-%3 %4:%5:%6")
           .arg(year,  2, 10, QChar('0'))
           .arg(month, 2, 10, QChar('0'))
           .arg(day,   2, 10, QChar('0'))
           .arg(hour,  2, 10, QChar('0'))
           .arg(min,   2, 10, QChar('0'))
           .arg(sec,   2, 10, QChar('0'));
              //数据  位宽 进制   结束字符

4.3 按钮数组的遍历与绑定槽函数

  在右侧多按钮组的发送中,存在10颗按键,对每个按钮分别定义一个槽函数,用于发送对于行编辑器的内容,在实现功能的基础上程序显得十分冗余,因此解决方案是分别获取10个按钮的名称对象将其存入QPushButton数组中,并通过遍历数组来获取每个按键对象。
  每个按钮点击时均发出clicked()信号,因此按钮数组通过绑定同一个槽函数,用于检测clicked()信号,并下标索引来确定具体的按钮及其对应的行编辑器里面的内容

定义私有的成员变量:按钮数组

QList<QPushButton *> btns;  //按钮数组

在构造函数内

  • 循环获取每个按钮对象,通过字符串拼接获取每个控件的名称
  • findChild<>() 函数可以通过控件的名称来获取对应的控件对象
  • 将按钮控件存入数据,并对每个按钮设置ID属性及其绑定同一个槽函数
    //遍历右侧按钮名存入数据
    for(int  i= 1; i <= 10; i++)
    {
        //格式化按钮名称 获取每个按键名称
        QString btnName = QString("pushButton_%1").arg(i);
        //通过控件名称找子控件
        QPushButton *btn = findChild<QPushButton *>(btnName);
        if(btn)
        {
            btns.append(btn);               //将按钮添加至按钮数组
            btn->setProperty("btnID",i);    //为每个按钮设置btnID属性
            //为当前按钮绑定槽函数
            connect(btn, SIGNAL(clicked()), this, SLOT(on_command_btns_clicked()));
        }
    }
    

在槽函数中

  • 首先需要确定按钮的发送者:qobject_cast,并获取按钮ID属性:property
  • 字符串格式化拼接生成对应的行编辑器和选项框的名称
  • 通过 findChild<>() 根据控件名称获取对应的控件对象
  • 获取对应行编辑器内容并将其发送
    void Widget::on_command_btns_clicked()
    {
        //查找触发槽函数的信号发送者
        QPushButton *btn = qobject_cast<QPushButton *>(sender());
    
        if(btn)
        {
            //获取ID 通过ID 关联按钮和文本框等控件
            int num = btn->property("btnID").toInt();
    
            //获取ID对应的控件名称
            QString lineEditName = QString("lineEdit_%1").arg(num);
            QString checkBoxName = QString("checkBox_%1").arg(num);
            //根据控件名称锁定实际的控件对象 通过findChild泛型
            QLineEdit *lineEdit = findChild<QLineEdit *>(lineEditName);
            QCheckBox *checkBox = findChild<QCheckBox *>(checkBoxName);
    
            if(lineEdit)
            {
                //如果有数据才发送
                if(lineEdit->text().size() <= 0)
                    return;
    
                ui->lineEditSendContext->setText(lineEdit->text());
            }
    
            if(checkBox)
                ui->checkBoxHexSend->setChecked(checkBox->isChecked());
    
            //发送槽函数 发送数据
            on_btnSend_clicked();
        }
    }
    

4.4 循环发送实现(定时器与Qt多线程并发)

定时器实现

  循环发送的实现可以采用定时器实现,每定时一定的时间,在定时器槽函数遍历行编辑器的内容。行编辑器的控件对象通过编辑器名称获得,而编辑器名称的ID号通过按钮组的下标获得

创建定时器并绑定槽函数:

 //实例化循环发送定时器
buttonConTimer = new QTimer(this);    
     
//绑定循环发送定时器溢出与槽函数
connect(buttonConTimer, &QTimer::timeout,this, &Widget::btnCon_Handler);

定时器槽函数: 相当于定时轮流按 按钮组

//循环发送定时器中断函数
void Widget::btnCon_Handler()
{
    if(buttonIndex < btns.size())
    {
    	//遍历按钮 发出clicked()信号,相当于定时轮流按按钮组
        QPushButton *btn = btns[buttonIndex];	
        emit btn->clicked();
        //成员变量,用于控制按钮组下标
        buttonIndex++;	
    }
    else	//数组遍历溢出 重置下标
        buttonIndex = 0;
}

循环发送选项框槽函数,主要用于控制定时器的开关及定时时间

//循环发送按钮槽函数
void Widget::on_checkBoxSend_clicked(bool checked)
{
    if(checked)
    {
        ui->spinBox->setEnabled(false);
        //定时时间从spinBox内获取
        buttonConTimer->start(ui->spinBox->text().toInt());
    }
    else
    {
        buttonConTimer->stop();
        ui->spinBox->setEnabled(true);
    }
}

多线程并发实现

  若在循环发送的选项框槽函数内使用UI线程的延时函数,会导致UI线程卡死,并且无法实时发送数据,因此需要创建一个单独的定时发送数据的线程,使得UI线程能正常显示。实际应用中,响应事件若与UI相关则可用定时器解决,若需完全其他任务需有耗时操作,需要使用多线程,在其他线程中尽量不操作UI界面
  本次设计也是通过线程来模拟定时器,每隔1定的时间线程发送一个自定义信号,主函数接收到信号后启动定时器,否则终止线程,类似于实现一个软定时的效果

线程类的定义

class CustomThread : public QThread
{
    Q_OBJECT
protected:
    void run() override;
public:
    CustomThread(QWidget * parent);
signals:
    void threadTimeOut();
};

重写 run() 函数

void CustomThread::run()
{
	//每隔1秒发送自定义信号
    while(true)
    {
        msleep(1000);
        emit threadTimeOut();
    }
}

实例化线程并绑定槽函数
注意: 线程时间溢出槽函数与定时器时间溢出槽函数一样,为同一个函数

myThread = new CustomThread(this);          //实例化循环发送线程对象

 //循环发送线程 信号与槽
connect(myThread, &CustomThread::threadTimeOut, this, &Widget::btnCon_Handler);

当检测到循环发送按钮按下后,只需将原本的启动定时器改为启动线程即可

//循环发送按钮槽函数
void Widget::on_checkBoxSend_clicked(bool checked)
{
    if(checked)
    {
        ui->spinBox->setEnabled(false);
        //buttonConTimer->start(ui->spinBox->text().toInt());
        myThread->start();		//启动线程
    }
    else
    {
        //buttonConTimer->stop();
        myThread->terminate();	//终止线程
        //循环发送
        ui->spinBox->setEnabled(true);
    }
}

4.5 重置保存与载入

1. 重置功能的实现

重置功能即清空右侧文本框和选择框的数据

//重置按钮槽函数
void Widget::on_btnInit_clicked()
{
	//消息提示框提示
    QMessageBox msgBox;
    msgBox.setWindowTitle("提示");
    msgBox.setText("重置列表不可逆,确认是否重置?");
    msgBox.setIcon(QMessageBox::Question);
    msgBox.addButton("是", QMessageBox::YesRole); 
    msgBox.addButton("否", QMessageBox::NoRole);
    int ret = msgBox.exec();
    //qDebug() << "ret = " << ret;
    
    switch (ret)
    {
        case 0:
            //qDebug() << "yes button";
            //遍历QLineEdit QPushButton QCheckBox 并清空数据
            for(int i = 0; i < lineEdits.size(); i++)
                lineEdits[i]->clear();
            for(int i = 0; i < checkBoxs.size(); i++)
                checkBoxs[i]->setChecked(false);
            break;
        case 1:
            //qDebug() << "no button";
            break;
        default:
            qDebug() << "ret default";
            break;
    }
}

2. 保存功能的实现

保存功能即将文本框和选择框的数据保存至txt文件中,方便实现下次读取后直接载入即可

//保存按钮槽函数
void Widget::on_btnSave_clicked()
{
    //创建文本对话框
    QString fileName = QFileDialog::getSaveFileName(this, tr("保存文件"),
                                                    "E:/qtProject/00_QT_CLC/03_USARTSerial/SendList.txt",
                                                    tr("文本类型 files (*.txt)"));
    //打开文件
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;

    //将文本框内数据保存至文件
    QTextStream out(&file);
    for(int i = 0; i< checkBoxs.size(); i++)
    {
        out << checkBoxs[i]->isChecked() << ",";
        out << lineEdits[i]->text() << endl;
    }

    file.close();
}

3. 载入功能的实现

读取txt文本数据,根据数据直接设置选择框的状态和行编辑器内容

//输入按钮槽函数
void Widget::on_btnLoad_clicked()
{
    int i = 0;
    //创建文本对话框
    QString fileName = QFileDialog::getOpenFileName(this, tr("打开文件"),
                                                    "E:/qtProject/00_QT_CLC/03_USARTSerial/SendList.txt",
                                                    tr("文本类型 files (*.txt)"));
    if(fileName != NULL)
    {
    	//以只读方式打开文件
        QFile file(fileName);
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            return;

        //读取数据
        QTextStream in(&file);
        if(in.atEnd() && i <= 9)
        {
        	//读取每一行的数
            QString line = in.readLine();
            //以 , 分割字符串存入parts列表  形如 0,AT+RST
            QStringList parts = line.split(",");
            if(parts.count() == 2)
            {
                checkBoxs[i]->setChecked(parts[0].toInt());
                lineEdits[i]->setText(parts[1]);
            }
            i++;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1452390.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

skimage库简介

scikit-image 是专注于图像处理的Python包&#xff0c;全称是scikit-image SciKit。该包由python语言编写&#xff0c;由scipy 社区开发和维护&#xff0c;使用原生的Numpy数组作为图像对象。 一、skimage简介 skimage&#xff08;scikit-Image&#xff09;是基于python开发的…

petalinux安装

跟着正点原子文档安装的&#xff0c;记录一下 1. 安装包下载 xilinx官网&#xff08;没有注册需要注册&#xff0c;注册比较慢&#xff0c;嫌弃耽搁时间直接用正点原子网盘里下好的&#xff09; https://china.xilinx.com/support/download/index.html/content/xilinx/zh/dow…

人工智能学习与实训笔记(十四):Langchain Agent

0、概要 Agent是干什么的&#xff1f; Agent的核心思想是使用语言模型&#xff08;LLM&#xff09;作为推理的大脑&#xff0c;以制定解决问题的计划、借助工具实施动作。在agents中几个关键组件如下&#xff1a; Agent&#xff1a;制定计划和思考下一步需要采取的行动。Tools…

拿捏单链表

目录 引言 一&#xff1a;链表的定义 二&#xff1a;单链表的定义 三&#xff1a;单链表的增删查改 1.单链表增删查改及遍历的声明 注&#xff1a;在测试中创建指向头结点的指针plist 2.二级指针应用的说明 3.单链表的遍历 4.创建节点 5.单链表的插入 (1)头插 …

【深度学习:DICOM 注释工具】在 DICOM 注释工具中寻找的 7 个功能

【深度学习&#xff1a;DICOM 注释工具】在 DICOM 注释工具中寻找的 7 个功能 原生 DICOM 支持原生 3D 注释易于使用的界面DICOM 图像的自动注释质量控制功能审计跟踪SOC2 和 HIPAA 合规性 如果您尝试为医疗 AI 模型创建训练数据&#xff0c;您可能已经使用了免费的开源工具&am…

html从零开始9:javaScript简介,语句、标识符,变量,JavaScript引入到文件【搬代码】

javaScript简介 javaScript语句、标识符 变量 var num 10; var就是固定声明,num就是变量名&#xff0c;10就是变量&#xff1b;<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Comp…

【pyopenGL编程手册- 01/20】pyopenGL安装和简要说明

目录 一、说明二、测试系统安装的健康性三、安装64位的openGL四、写给程序员的4. 1 函数库介绍4.2 库内函数的命名 五、常见库的函数介绍5.1 OpenGL 核心库 GL5.2 OpenGL 实用库 GLU5.3 OpenGL 工具库 GLUT5.4 Windows 专用库 WGL 六、错误引发点和异常追踪6.1 错误检查开关6.…

人工智能学习与实训笔记(五):神经网络之推荐系统处理

目录 ​​​​​​​七、智能推荐系统处理 7.1 常用的推荐系统算法 7.2 如何实现推荐​​​​​​​ 7.3 基于飞桨实现的电影推荐模型 7.3.1 电影数据类型 7.3.2 数据处理 7.3.4 数据读取器 7.3.4 网络构建 7.3.4.1用户特征提取 7.3.4.2 电影特征提取 7.3.4.3 相似度…

TenorFlow多层感知机识别手写体

文章目录 数据准备建立模型建立输入层 x建立隐藏层h1建立隐藏层h2建立输出层 定义训练方式建立训练数据label真实值 placeholder定义loss function选择optimizer 定义评估模型的准确率计算每一项数据是否正确预测将计算预测正确结果&#xff0c;加总平均 开始训练画出误差执行结…

C++初阶(十一) list

一、list的介绍及使用 1.1 list的介绍 list的文档介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点…

爱上JVM——常见问题(一):JVM组成

1 JVM组成 1.1 JVM由那些部分组成&#xff0c;运行流程是什么&#xff1f; 难易程度&#xff1a;☆☆☆ 出现频率&#xff1a;☆☆☆☆ JVM是什么 Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 好处&#xff1a; 一次编写&…

巨抽象的前端vue3

根据实践证明&#xff0c;越是简单的问题&#xff0c;越容易造成大bug 一个自定义组件的路径就废了我老半天了 各种查询&#xff0c;各种百度&#xff0c;各种问&#xff0c;结果规规矩矩去导入组件路径&#xff0c;成了&#xff01; 错误代码&#xff1a; <script setu…

canal监听binlog记录业务数据的变更;canalAdmin对instance做web配置

概述 平时在开发中会通过logback打印一些开发日志&#xff0c;有时也会需要记录一些业务日志&#xff0c;简单的就直接用log记录一下&#xff0c;但是系统中需要记录日志的地方越来越多时&#xff0c;不能每个地方都写一套log记录&#xff1b; 由于平常用的大多都是mysql&…

c语言遍历文件夹中的文件

文件目录如下&#xff0c;文件夹里还有一些txt文件未展示出来。 使用递归实现&#xff0c;深度优先遍历文件夹中的文件。 代码如下&#xff0c;用了一点C的语法。 #include <io.h> #include <iostream> using namespace std;#define MAX_PATH_LENGTH 100int Tr…

创新技巧|迁移到 Google Analytics 4 时如何保存历史 Universal Analytics 数据

Google Universal Analytics 从 2023 年 7 月起停止收集数据&#xff08;除了付费 GA360 之外&#xff09;。它被Google Analytics 4取代。为此&#xff0c;不少用户疑惑&#xff1a;是否可以将累积&#xff08;历史&#xff09;数据从 Google Analytics Universal 传输到 Goog…

@ControllerAdvice 的介绍及三种用法

ControllerAdvice 的介绍及三种用法 浅析ControllerAdvice 首先&#xff0c;ControllerAdvice本质上是一个Component&#xff0c;因此也会被当成组建扫描&#xff0c;一视同仁&#xff0c;扫扫扫。 然后&#xff0c;我们来看一下此类的注释&#xff1a; 这个类是为那些声明了&…

智胜未来,新时代IT技术人风口攻略-第四版(弃稿)

文章目录 前言鸿蒙生态科普调研人员画像高校助力鸿蒙高校鸿蒙课程开设占比教研力量并非唯一原因 企业布局规划全盘接纳仍需一段时间企业对鸿蒙的一些诉求 机构入场红利机构鸿蒙课程开设占比机构对鸿蒙的一些诉求 鸿蒙实际体验高校用户群体高度认同与影响体验企业用户群体未来可…

【数据分享】2001~2020年青藏高原植被净初级生产力数据集

各位同学们好&#xff0c;今天和大伙儿分享的是2001~2020年青藏高原植被净初级生产力数据集。如果大家有下载处理数据等方面的问题&#xff0c;您可以私信或评论。 朱军涛. (2022). 青藏高原植被净初级生产力数据集&#xff08;2001-2020&#xff09;. 国家青藏高原数据中心. …

基于STM32的老人心率监测系统

1. 系统设计 本次课题为基于STM32的老人心率监测系统&#xff0c;在此设计了如图2.1所示的系统结构框图&#xff0c;整个系统包括了MAX30102心率血氧检测模块&#xff0c;SIM800短信模块&#xff0c;液晶显示模块&#xff0c;按键&#xff0c;ESP8266无线通信模块以及主控制器s…

WIN11 WSL2 Ubuntu系统删除Docker镜像后磁盘空间未减少问题解决办法

因为 windows 中的 docker 使用虚拟磁盘&#xff08;VHDX&#xff09;来存储文件系统。 windows中&#xff0c;docker需在WSL2配置下才可使用。 &#xff08;WSL是windows推出的可让开发人员不需要安装虚拟机(vmware,virtbox)或者设置双系统启动就可以原生支持运行GNU/Linux的…