09-Qt预定义编码文件的读写
7.2.2 Qt预定义编码文件的读写
1.保存为stm文件
先看文件保存功能,因为从文件保存功能的代码可以看出文件内数据的存储顺序。在图7-2的窗口上编辑表格的数据后,单击工具栏上的“保存stm文件”,可以使用Qt预定义编码方式保存文件。此按钮的响应代码如下:
void MainWindow::on_actSave_triggered()
{ //以Qt预定义编码保存文件
QString curPath=QDir::currentPath();
QString aFileName=QFileDialog::getSaveFileName(this,"选择保存文件",
curPath,"Qt预定义编码数据文件(*.stm)");
if (aFileName.isEmpty())
return;
if (saveDataAsStream(aFileName))
QMessageBox::information(this,"提示消息","文件已经成功保存!");
}
bool MainWindow::saveDataAsStream(QString &aFileName)
{//将模型数据保存为Qt预定义编码的数据文件
QFile aFile(aFileName);
if (!(aFile.open(QIODevice::WriteOnly | QIODevice::Truncate)))
return false;
QDataStream aStream(&aFile);
aStream.setVersion(QDataStream::Qt_5_9); //流版本号,写入和读取版本要兼容
qint16 rowCount=theModel->rowCount();
qint16 colCount=theModel->columnCount();
aStream<columnCount();i++)
{
QString str=theModel->horizontalHeaderItem(i)->text();//获取表头文字
aStream<rowCount();i++)
{
QStandardItem* aItem=theModel->item(i,0); //测深
qint16 ceShen=aItem->data(Qt::DisplayRole).toInt();
aStream<item(i,1); //垂深
qreal chuiShen=aItem->data(Qt::DisplayRole).toFloat();
aStream<item(i,2); //方位
qreal fangWei=aItem->data(Qt::DisplayRole).toFloat();
aStream<item(i,3); //位移
qreal weiYi=aItem->data(Qt::DisplayRole).toFloat();
aStream<item(i,4); //固井质量
QString zhiLiang=aItem->data(Qt::DisplayRole).toString();
aStream<item(i,5); //测井取样
bool quYang=(aItem->checkState()==Qt::Checked);
aStream<close();
return true;
}
自定义函数saveDataAsStream()将表格的数据模型theModel的数据保存为一个stm文件。代码首先是创建QFile对象aFile打开文件,然后创建QDataStream对象aStream与QFile对象关联。
在开始写数据流之前,为QDataStream对象aStream设置版本号,即调用setVersion()函数,并传递一个QDataStream:: Version枚举类型的值。
aStream.setVersion(QDataStream::Qt_5_9);
这表示aStream将以QDataStream::Qt_5_9版本的预定义类型写文件流。
注意 以Qt的预定义类型编码保存的文件需要指定流版本号,因为每个版本的Qt对数据类型的编码可能有差别,需要保证写文件和读文件的流版本是兼容的。
接下来,就是按照需要保存数据的顺序写入文件流。例如在文件开始,先写入行数和列数两个qint16的整数。因为行数和列数关系到后面的数据是如何组织的,因此在读取文件数据时,首先读取这两个整数,然后根据数据存储方式的约定,就知道后续数据该如何读取了。向文件写入数据时,直接用流的输入操作,如:
aStream<<rowCount; //写入文件流,行数
aStream<<colCount;//写入文件流,列数
在读取各列的表头字符串之后,将其写入数据流。然后逐行扫描表格的数据模型,将每一行的列数据写入数据流。
数据流写入数据时都使用运算符“<<”,不论写的是qint16、qreal,还是字符串。除了可以写入基本的数据类型外,QDataStream流操作还可以写入很多其他类型的数据,如QBrush、QColor、QImage、QIcon等,这些称为可序列化的数据类型(Serializing Qt Data Types)。
QDataStream以流操作写入这些数据时,我们并不知道文件里每个字节是如何存储的,但是知道数据写入的顺序,以及每次写入数据的类型。在文件数据读出时,只需按照顺序和类型对应读出即可。
2.stm文件格式
根据saveDataAsStream()函数的代码,可知Qt预定义编码保存的stm文件的格式,如表7-1所示。
| 顺序号 | 数据 | 类型 | 备注 | | :----- | :----- | :----- | :----- | :----- | :----- | | 1 | rowCount | qint16 | 行数 | | 2 | colCount | qint16 | 列数 | | 3 | “Depth” | QString | 表头标题1 | | 4 | "Measured Depth" | QString | 表头标题2 | | 5 | "Direction" | QString | 表头标题3 | | 6 | "Offset" | QString | 表头标题4 | | 7 | "Quality" | QString | 表头标题5 | | 8 | "Sampled" | QString | 表头标题6 | | 9 | 第1行各列数据 | qint16 | 测深 | | 10 | | qreal | 垂深 | | 11 | | qreal | 方位 | | 12 | | qreal | 位移 | | 13 | | QString | 固井质量 | | 14 | | bool | 是否测井取样 | | 15 | 第2行各列数据 | | ... |
从表7-1中可以知道stm文件的数据存储顺序和类型,但是并不知道qint16类型的数据存储为几个字节以及QString类型的数据是如何定义长度和字符内容的,其实也不需要知道这些具体的存储方式,在从文件读出时,只需按照表7-1的顺序和类型读出数据即可。
3.读取stm文件
下面是工具栏按钮“打开stm文件”的响应代码及相关函数代码,选择需要打开的stm文件后,主要是调用自定义函数openDataAsStream()将其打开。
void MainWindow::on_actOpen_triggered()
{//打开stm文件
QString curPath=QDir::currentPath();
QString aFileName=QFileDialog::getOpenFileName(this,"打开一个文件",
curPath,"Qt预定义编码数据文件(*.stm)");
if (aFileName.isEmpty())
return;
if (openDataAsStream(aFileName))
QMessageBox::information(this,"提示消息","文件已经打开!");
}
bool MainWindow::openDataAsStream(QString &aFileName)
{ //从stm文件读入数据
QFile aFile(aFileName);
if (!(aFile.open(QIODevice::ReadOnly)))
return false;
QDataStream aStream(&aFile);
aStream.setVersion(QDataStream::Qt_5_9); //设置流文件版本号
qint16 rowCount,colCount;
aStream>>rowCount; //读取行数
aStream>>colCount; //读取列数
this->resetTable(rowCount); //表格复位,设定行数
//获取表头文字,但并不使用
QString str;
for (int i=0;i>str; //读取表头字符串
//读取数据区数据
qint16 ceShen;
qreal chuiShen, fangWei, weiYi;
QString zhiLiang;
bool quYang;
QStandardItem *aItem;
QModelIndex index;
for (int i=0;i>ceShen; //读取测深, qint16
index=theModel->index(i,0);
aItem=theModel->itemFromIndex(index);
aItem->setData(ceShen,Qt::DisplayRole);
aStream>>chuiShen;//垂深,qreal
index=theModel->index(i,1);
aItem=theModel->itemFromIndex(index);
aItem->setData(chuiShen,Qt::DisplayRole);
aStream>>fangWei;//方位,qreal
index=theModel->index(i,2);
aItem=theModel->itemFromIndex(index);
aItem->setData(fangWei,Qt::DisplayRole);
aStream>>weiYi;//位移,qreal
index=theModel->index(i,3);
aItem=theModel->itemFromIndex(index);
aItem->setData(weiYi,Qt::DisplayRole);
aStream>>zhiLiang;//固井质量,QString
index=theModel->index(i,4);
aItem=theModel->itemFromIndex(index);
aItem->setData(zhiLiang,Qt::DisplayRole);
aStream>>quYang;//测井取样
index=theModel->index(i,5);
aItem=theModel->itemFromIndex(index);
if (quYang)
aItem->setCheckState(Qt::Checked);
else
aItem->setCheckState(Qt::Unchecked);
}
aFile.close();
return true;
}
void MainWindow::resetTable(int aRowCount)
{ //表格复位
theModel->removeRows(0,theModel->rowCount());
theModel->setRowCount(aRowCount);
QString str=theModel->headerData(theModel->columnCount()-1,
Qt::Horizontal,Qt::DisplayRole).toString();
for (int i=0;i<theModel->rowCount();i++)
{ //设置最后一列
QModelIndex index=theModel->index(i,FixedColumnCount-1);
QStandardItem* aItem=theModel->itemFromIndex(index);
aItem->setCheckable(true);
aItem->setData(str,Qt::DisplayRole);
aItem->setEditable(false);
}
}
读取stm文件的数据之前也必须设置QDataStream的流版本号,应该等于或高于数据保存时的流版本号。
然后就是按照表7-1所示的写入数据时的顺序和类型,相应地读出每个数据。文件里最早的两个数据是表格的行数和列数,读出这两个数据,就能知道数据的行数和列数,并调用自定义函数resetTable()给数据模型复位,并设置其行数。
然后将保存的每行数据读入到数据模型的每个项中,这样窗口上的QTableView组件就可以显示数据了。
使用QDataStream的流操作方式读写文件的特点如下。
- 读写操作都比较方便,支持读写各种数据类型,包括Qt的一些类,还可以为流数据读写扩展自定义的数据类型。读写某种类型的数据时,只要是流支持即可,而在文件内部是如何存储的,用户无需关心,由Qt预定义。
- 写文件和读文件时必须保证使用的流版本兼容,即流的版本号相同,或读取文件的流版本号高于写文件时的流版本号。这是因为在不同的流版本中,流支持的数据类型的读写方式可能有所改变,必须保证读写版本的兼容。
- 用这种方式保存文件时,写入数据采用Qt预定义的编码,即写入文件的二进制编码是由Qt预定义的,写多少个字节、字节是什么样的顺序,用户是不知道的。如果是由QDataStream读取数据,只需按类型读出即可。但是,如果由这种方法创建的文件是用于交换的,需要用其他的编程语言(如Matlab)来读取文件内容,则存在问题了。因为其他语言并没有与Qt的流写入完全一致的流读出功能,例如,其他语言并不知道Qt保存的QString或QFont的内容是如何组织的。