作者:zzssdd2
E-mail:zzssdd2@foxmail.com
一、前言
開發環境:Qt5.12.10 + MinGW
功能
- 文件的發送
- 數據的保存
知識點
QFile類的使用QTimer類的使用- 文本的轉碼與編碼識別
QPushButton、QProgressBar控件的使用

二、功能實現
本章功能主要包含兩個方面,一是通過串口發送選定的文本文件,二是將接收的數據保存為本地文本文件。最后還有對《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是對於二進制文本的讀取。
