载入和保存
现在,我们将使用一种自定义的二进制数格式来实现Spreadsheet文件的载人和保存。将使用QFile和QDataSrean来完成这一工作,由它们共同提供与平台无关的二进制数输入/输出接口。
首先从一个Spreadsheet文件的输出开始:
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("Cannot write file %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_3); //Qt_4_3 = 9
out << quint32(MagicNumber);
QApplication::setOverrideCursor(Qt::WaitCursor);
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
}
QApplication::restoreOverrideCursor();
return true;
}
从MainWindow::saveFile()中调用的writeFile()函数把文件输出到磁盘中。如果输出成功,它会返回true;如果出现错误,则返回false。
我们使用给定的文件名创建一个QFile对象,并且调用open()打开这个用于输出的文件。我们也会创建一个QDataStream对象,由它操作这个QFile对象并且使用该对象输出数据。
在输出数据之前,我们把这个应用程序的光标修改为标准的等待光标(通常是一个沙漏),并且一旦所有的数据输出完毕,就需要把这个应用程序的光标重新恢复为普通光标。在函数的最后,文件会由QFile对象的析构函数自动关闭。
QDataStream 支持基本的 C++类型,也支持多种 Qt 类型。语法和标准 C++<iostream>
类是一样的。例如:
out << x << y << z;
把变量 x,y,z 写入数据流。
in >> x >> y >> z;
会从流中读出它们。
在不同的平台上,基本的 C++类型如short,char,int,long,long long 会有不同的字长,所以把它们强制转换为 qint8,quint8,qint16,quint16,qint32,quint32,qint64,quint64,这些类型能确保字长是不随平台改变的。
Spreadsheet应用程序的文件格式是相当简单的。一个Spreadsheet文件以一个32位数字作为文件的开始,由它确定文件的格式(MagicNumber,在sreadsheet.h中定义为0x7F51C883,它是一个任意的随机数)。然后是连续的数据块,每一数据块都包含了用于一个单元格中的行、列和公式。为了节省空间,我们没有输出空白单元格。
关于这些数据类型的二进制数确切表示方法则是由QDataStream决定的。例如,一个quint16按照高字节在后的顺序存储为两个字节,而一个QString则被存储为字符串的长度后跟Unicode字符的形式。
关于Qt数据类型的二进制数确切表示方法,自Qt 1.0以来已经发生了许多变化。而且在未来的Qt发行版中,为了能够与现存的数据类型和将来允许出现的新的Qt类型保持一致,这样的表示方法还可能会继续变化下去。默认情况下,QDataSrean会使用最近版本的二进制数格式(在Qt 4.3中的版本是第9版) ,但是可以设置它,使它可以读取那些旧的数据版本。如果以后有可能使用新的Qt发行版来重新编译这个应用程序,那么为了避免出现任何可能的兼容性问题,需要明确告诉QDataSream应该使用的是第9版,从而无需再考虑要使用的Qt版本。(QDataStream::Qt_4_3是一
个方便的常量,它就等于9。)
QDataStream 可以支持多种类型。如 QFile,QBuffer,QProcess,QTcpSocket 或者QUdpSocket。Qt 还提供了类 QTextStream 能够读写文本文件。
bool Spreadsheet::readFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("Cannot read file %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_3);
quint32 magic; //用于匹配文件格式
in >> magic;
if (magic != MagicNumber) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("The file is not a Spreadsheet file."));
return false;
}
clear();
quint16 row;
quint16 column;
QString str;
QApplication::setOverrideCursor(Qt::WaitCursor);
while (!in.atEnd()) {
in >> row >> column >> str;
setFormula(row, column, str);
}
QApplication::restoreOverrideCursor();
return true;
}
readFile()函数与writeFile() 函数非常相似。我们使用QFile读取一个文件,但这一次使用的是QIODevice::ReadOnly标记,而不是QIODevice::WriteOnly标记。然后,把QDataStream的版本设置为9。用于读取文件的格式必须总是与输出文件的格式相同。
如果该文件在开始处具有正确的幻数(magic number),那么可以调用clear()来清空电子制表软件中的所有单元格,并且读入单元格中的数据。由于该文件中只包含那些非空单元格的数据,并且也不大可能重置电子制表软件中的每个单元格,所以必须确保在读入数据之前已经清空了所有的单元格。