一、前言

开发环境:Qt5.12.10 + MinGW

  • 文件的发送
  • 数据的保存
  • QFile类的使用
  • QTimer类的使用
  • 文本的转码与编码识别
  • QPushButtonQProgressBar控件的使用

QT串口助手(五):文件操作-LMLPHP

二、功能实现

本章功能主要包含两个方面,一是通过串口发送选定的文本文件,二是将接收的数据保存为本地文本文件。最后还有对《QT串口助手(三):数据接收》章节内容进行一个补充扩展。

2.1、文件打开

选择文件按钮点击后,触发点击信号对应的槽函数,在槽函数中进行文件的打开与读取:

/*选择并打开文件*/
QString curPath = QDir::currentPath();  //系统当前目录
QString dlgTitle = "打开文件";  //对话框标题
QString filter = "文本文件(*.txt);;二进制文件(*.bin *.dat);;所有文件(*.*)"; //文件过滤器
QString filepath = QFileDialog::getOpenFileName(this,dlgTitle,curPath,filter);
QFileInfo fileinfo(filepath);

if (filepath.isEmpty())
{
    QMessageBox::warning(this,"警告","文件为空");
    return;
}
//文件路径显示到发送框
ui->Send_TextEdit->clear();
ui->Send_TextEdit->appendPlainText(filepath);

QFile file(filepath);
if (!file.exists())
{
    QMessageBox::warning(this,"警告","文件不存在");
    return;
}
if (!file.open(QIODevice::ReadOnly))
{
    QMessageBox::warning(this,"警告","文件打开失败");
    return;
}

2.2、编码判断

因为应用程序默认使用的编码为UTF-8,如果打开GBK格式编码的文件就会乱码,所以需要判断文件的编码,如果不是UTF-8则需要对文件进行编码转换。

/* 设置应用程序的编码解码器 */
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
QTextCodec::setCodecForLocale(codec);
/* 判断编码 */
QTextCodec::ConverterState state;
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
FileText = codec->toUnicode(data.constData(),data.size(),&state);
//若有无效字符则是GBK编码
if (state.invalidChars > 0)
{
    //转码后返回
    FileText = QTextCodec::codecForName("GBK")->toUnicode(data);
}
else
{
    FileText =  data;
}

对文件进行上述的处理后,如果是GBK编码则也能够正确的读取了。

QT串口助手(五):文件操作-LMLPHP

2.3、文件读取

文件打开后,需要对文件类型进行判断,然后进行文件数据的读取:

/*判断文件类型*/
int type = 0;
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(filepath);
if (mime.name().startsWith("text/"))
{
    type = 1;	//文本文件
}
else if (mime.name().startsWith("application/"))
{
    type = 2;	//二进制文件
}
/*读取文件*/
switch(type)
{
    case 1:
    {
        //QIODevice读取普通文本
        QByteArray data = file.readAll();
        file.close();
        if (data.isEmpty())
        {
            QMessageBox::information(this, "提示", "文件内容空");
            return;
        }
        /* 判断编码 */
    }
    break;
    case 2:
    {
        int filelen = fileinfo.size();
        QVector<char> cBuf(filelen);//储存读出数据
        //使用QDataStream读取二进制文件
        QDataStream datain(&file);
        datain.readRawData(&cBuf[0],filelen);
        file.close();
        //char数组转QString
        FileText = QString::fromLocal8Bit(&cBuf[0],filelen);
    }
    break;
}

最后将读取到的文件大小信息和内容显示到接收框并标记有待发送文件:

//显示文件大小信息
QString info = QString("%1%2").arg("文件大小为:").arg(FileText.length());
ui->Receive_TextEdit->clear();
ui->Receive_TextEdit->append(info);
//显示文件内容
if (ui->HexDisp_checkBox->isChecked())
{
    ui->Receive_TextEdit->setPlainText(FileText.toUtf8().toHex(' ').toUpper());
}
else
{
    ui->Receive_TextEdit->setPlainText(FileText);
}
//设置显示焦点在最顶部
ui->Receive_TextEdit->moveCursor(QTextCursor::Start,QTextCursor::MoveAnchor);

/*标记有文件发送*/
isSendFile = true;
FrameCount = 0;
ProgressBarValue = 0;

2.4、文件发送

此时在标记了有文件发送的情况下,点击发送按钮则是发送文件:

/*
    函   数:on_Send_Bt_clicked
    描   述:发送按键点击信号槽
    输   入:无
    输   出:无
*/
void Widget::on_Send_Bt_clicked()
{
    if (isSerialOpen != false)
    {
        if (isSendFile)	//发送文件数据
        {
            if (ui->Send_Bt->text() == "发送")
            {
                ui->Send_Bt->setText("停止");
                SendFile();
            }
            else
            {
                ui->Send_Bt->setText("发送");
                FileSendTimer->stop();
            }
        }
        else	//发送发送框数据
        {
            SerialSendData(SendTextEditBa);
        }
    }
    else
    {
        QMessageBox::information(this, "提示", "串口未打开");
    }
}

/*
    函   数:SendData
    描   述:定时器发送文件
    输   入:无
    输   出:无
*/
void Widget::SendFile(void)
{
    /*按设置参数发送*/
    FrameLen = ui->PackLen_lineEdit->text().toInt(); // 帧大小
    FrameGap = ui->GapTim_lineEdit->text().toInt();  // 帧间隔
    int textlen = Widget::FileText.size();           // 文件大小
    if (FrameGap <= 0 || textlen <= FrameLen)
    {
        //时间间隔为0 或 帧大小≥文件大小 则直接一次发送
        serial->write(FileText.toUtf8());
        ui->Send_Bt->setText("发送");
    }
    else
    {
        //按设定时间和长度发送
        FrameNumber = textlen / FrameLen; // 包数量
        LastFrameLen = textlen % FrameLen; // 最后一包数据长度
        //进度条步进值
        if (FrameNumber >= 100)
        {
            ProgressBarStep = FrameNumber / 100;
        }
        else
        {
            ProgressBarStep = 100 / FrameNumber;
        }
        //设置定时器
        FileSendTimer->start(FrameGap);
    }
}

设置一个定时器,将文件按照设定的帧大小帧间隔来发送:

/*文件帧发送定时器信号槽*/
FileSendTimer = new QTimer(this);
connect(FileSendTimer,SIGNAL(timeout()),this,SLOT(File_TimerSend()));

/*
    函   数:File_TimerSend
    描   述:发送文件定时器槽函数
    输   入:无
    输   出:无
*/
void Widget::File_TimerSend(void)
{
    if (FrameCount < FrameNumber)
    {
        serial->write(FileText.mid(FrameCount * FrameLen, FrameLen).toUtf8());
        FrameCount++;
        //更新进度条
        ui->progressBar->setValue(ProgressBarValue += ProgressBarStep);
    }
    else
    {
        if (LastFrameLen > 0)
        {
            serial->write(FileText.mid(FrameCount * FrameLen, LastFrameLen).toUtf8());
            ui->progressBar->setValue(100);
        }
        /*发送完毕*/
        FileSendTimer->stop();
        FrameCount = 0;
        ProgressBarValue = 0;
        FrameNumber = 0;
        LastFrameLen = 0;
        QMessageBox::information(this, "提示", "发送完成");
        ui->progressBar->setValue(0);
        ui->Send_Bt->setText("发送");
    }
}

2.5、数据保存

保存数据按钮按下时,将接收框数据按文本方式进行保存:

/*
    函   数:on_SaveData_Button_clicked
    描   述:保存数据按钮点击槽函数
    输   入:无
    输   出:无
*/
void Widget::on_SaveData_Button_clicked()
{
    QString data = ui->Receive_TextEdit->toPlainText();

    if (data.isEmpty())
    {
        QMessageBox::information(this, "提示", "数据内容空");
        return;
    }

    QString curPath = QDir::currentPath();            //获取系统当前目录
    QString dlgTitle = "保存文件";                     //对话框标题
    QString filter = "文本文件(*.txt);;所有文件(*.*)";  //文件过滤器
    QString filename = QFileDialog::getSaveFileName(this,dlgTitle,curPath,filter);
    if (filename.isEmpty())
    {
        return;
    }
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly))
    {
        return;
    }

    /*保存文件*/
    QTextStream stream(&file);
    stream << data;
    file.close();
}

三、扩展

这里对接收模块的功能进行一个补充。上面说了应用程序默认编码是UTF-8,那么如果在ascii显示模式下收到的数据是GBK格式的编码就会造成显示乱码,所以需要对接收数据进行编码判断,如果是GBK编码则进行转换显示。

/*
    函   数:SerialPortReadyRead_slot
    描   述:readyRead()信号对应的数据接收槽函数
    输   入:无
    输   出:无
*/
void Widget::SerialPortReadyRead_slot()
{
    /*读取串口收到的数据*/
    QByteArray bytedata = serial->readAll();

    //......省略

    if(ui->HexDisp_checkBox->isChecked() == false)  //ascii显示
    {
        /* 判断编码 */
        QTextCodec::ConverterState state;
        //调用utf8转unicode的方式转码,获取转换结果
        QString asciidata = QTextCodec::codecForName("UTF-8")->toUnicode(bytedata.constData(),bytedata.size(),&state);
        //存在无效字符则是GBK,转码后返回
        if (state.invalidChars > 0)
        {
            asciidata = QTextCodec::codecForName("GBK")->toUnicode(bytedata);
        }
        else
        {
            asciidata = QString(bytedata);
        }

        //......省略
    }

    //......省略
}

QT串口助手(五):文件操作-LMLPHP

四、总结

本章主要讲解对文件的操作。除了需要了解在QT中对文件类的常用操作之外,个人认为还有比较重要的两个知识点:1是文本编码的判断和转码处理,2是对于二进制文本的读取。

02-08 14:32