QT串列埠助手(五):檔案操作

zzssdd2發表於2021-02-07

作者:zzssdd2

E-mail:zzssdd2@foxmail.com

一、前言

開發環境:Qt5.12.10 + MinGW

功能

  • 檔案的傳送
  • 資料的儲存

知識點

  • QFile類的使用
  • QTimer類的使用
  • 文字的轉碼與編碼識別
  • QPushButtonQProgressBar控制元件的使用

二、功能實現

本章功能主要包含兩個方面,一是通過串列埠傳送選定的文字檔案,二是將接收的資料儲存為本地文字檔案。最後還有對《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);
  • [static] QTextCodec *QTextCodec::codecForName(const char *name)

    Searches all installed QTextCodec objects and returns the one which best matches name; the match is case-insensitive. Returns 0 if no codec matching the name name could be found.

  • [static] void QTextCodec::setCodecForLocale(QTextCodec **c*)

    Set the codec to c; this will be returned by codecForLocale(). If c is a null pointer, the codec is reset to the default.

    This might be needed for some applications that want to use their own mechanism for setting the locale.

    Warning: This function is not reentrant.

/* 判斷編碼 */
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編碼則也能夠正確的讀取了。

  • QString QTextCodec::toUnicode(const char *input, int size, QTextCodec::ConverterState *state = nullptr) const

    Converts the first size characters from the input from the encoding of this codec to Unicode, and returns the result in a QString.

    The state of the convertor used is updated.

  • QString QTextCodec::toUnicode(const QByteArray &a) const

    Converts a from the encoding of this codec to Unicode, and returns the result in a QString.

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;	//二進位制檔案
}

QMimeType QMimeDatabase::mimeTypeForFile(const QString&fileName, QMimeDatabase::MatchMode mode = MatchDefault) const

Returns a MIME type for the file named fileName using mode.

This is an overloaded function.

QMimeType 類描述檔案或資料的型別,由 MIME 型別字串表示,獲取到檔案型別後接下來就知道應該使用什麼方法讀取檔案內容了。常見檔案型別如下:

描述(startsWith) 型別 示例
text 普通文字 text/plain, text/html, text/css, text/javascript
image 影像檔案(包含動態gif) image/gif, image/png, image/jpeg, image/bmp, image/webp
audio 音訊檔案 audio/wav, audio/mpeg, audio/midi, audio/webm, audio/ogg
video 視訊檔案 video/mp4, video/x-flv, video/webm, video/ogg
application 二進位制資料 application/xml, application/pdf
/*讀取檔案*/
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;
}
  • QByteArray QIODevice::readAll()

    Reads all remaining data from the device, and returns it as a byte array.

    This function has no way of reporting errors; returning an empty QByteArray can mean either that no data was currently available for reading, or that an error occurred.

  • int QDataStream::readRawData(char **s*, int len)

    Reads at most len bytes from the stream into s and returns the number of bytes read. If an error occurs, this function returns -1.

    The buffer s must be preallocated. The data is not decoded.

  • 關於QVector:QVector類是一個提供動態陣列的模板類。QVector是Qt的通用容器類之一,它將其項儲存在相鄰的記憶體位置並提供基於索引的快速訪問。例如上面程式碼定義了一個大小為filelen的char型別的陣列用來儲存讀取的二進位制資料(相對與普通陣列而言,QVector陣列項可以動態增加,能夠避免訪問越界等問題)。

  • QT中使用QTextStream或QIODevice類讀寫普通文字檔案,使用QDataStream類讀寫二進位制文字檔案

最後將讀取到的檔案大小資訊和內容顯示到接收框並標記有待傳送檔案:

//顯示檔案大小資訊
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("傳送");
    }
}

QString QString::mid(int position, int n = -1) const

Returns a string that contains n characters of this string, starting at the specified position index.

Returns a null string if the position index exceeds the length of the string. If there are less than n characters available in the string starting at the given position, or if n is -1 (default), the function returns all characters that are available from the specified position.

Example:

 QString x = "Nine pineapples";
 QString y = x.mid(5, 4);            // y == "pine"
 QString z = x.mid(5);               // z == "pineapples"

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();
}

QTextStream::QTextStream(FILE **fileHandle, QIODevice::OpenModeopenMode* = QIODevice::ReadWrite)

Constructs a QTextStream that operates on fileHandle, using openMode to define the open mode. Internally, a QFile is created to handle the FILE pointer.

This constructor is useful for working directly with the common FILE based input and output streams: stdin, stdout and stderr. Example:

 QString str;
 QTextStream in(stdin);
 in >> str;

三、擴充套件

這裡對接收模組的功能進行一個補充。上面說了應用程式預設編碼是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中對檔案類的常用操作之外,個人認為還有比較重要的兩個知識點:1是文字編碼的判斷和轉碼處理,2是對於二進位制文字的讀取。

相關文章