На этом шаге рассмотрим загрузку и сохранение файла данных для приложения Электронная таблица, используя двоичный пользовательский формат.
Для этого будем использовать объекты QFile и QDataStream, которые совместно обеспечивают независимый от платформы ввод-вывод в двоичном формате.
Начнем с записи файла данных электноррой таблицы. Функция writeFile() вызывается из MainWindow::saveFile() для записи файла на диск. Она возвращает true при успешном завершении и false при ошибке.
bool Spreadsheet::writeFile(const QString &fileName)
{
//создаем объект QFile, задавая имя файла
QFile file(fileName);
//вызываем функцию ореn() для открытия файла для записи данных
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, tr("Электронная таблица"),
tr("Не удается записать файл %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
/*создаем объект QDataStream, который предназначен для работы
с QFile и использует его для записи данных*/
QDataStream out(&file);
out.setVersion(QDataStream::Qt_5_2);
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())
/*Поскольку элементарные целочисленные типы C++ на различных
платформах могут иметь различный размер, надежнее преобразовать их типы в qint8,
quint8, qint16, quint16, qint32, quint32, qint64 и quint64, что гарантирует
использование объявленного в них размера (в битах)*/
out << quint16(row) << quint16(column) << str;
}
}
/*восстанавливаем нормальный курсор после окончания записи данных.
В конце функции файл автоматически закрывается деструктором QFile*/
QApplication::restoreOverrideCursor();
return true;
}QDataStream поддерживает основные типы C++ совместно со многими типами Qt. Их синтаксис напоминает синтаксис классов <iostream> стандартного C++. Например, out << х << у << z; выполняет запись в поток значений переменных х, у и z, a in >> х >> у >> z; считывает их из потока.
Файл данных приложения Электронная таблица имеет очень следующий формат. Он начинается с 32-битового числа, идентифицирующего формат файла ("волшебное" число MagicNumber определено в spreadsheet.h как 0x7F51C883 - произвольное случайное число). Затем идет последовательность блоков, содержащих строку, столбец и формулу одной ячейки. Для экономии места мы не записываем пустые ячейки. Формат показан на рис. 1.

Рис.1. Формат файла данных для приложения Электронная таблица
Точно представление типов данных определяется в QDataStream. Например, quint16 представляется двумя байтами со старшим байтом в конце, a QString задается длиной строки, за которой следуют символы в коде Unicode.
Двоичное представление типов в Qt достаточно сильно усовершенствовалось со времени выхода версии Qt 1.0. Такая тенденция, вероятно, сохранится в будущих версиях Qt, чтобы идти вровень с развитием существующих типов и обеспечить новые типы в Qt.
По умолчанию класс QDataStream использует 15 версию двоичного формата в Qt 5.2, но он также может быть настроен на чтение прошлых версий. Для того чтобы избежать проблем совместимости при перекомпиляции приложения в будущем, в новой версии Qt мы заставляем QDataStream использовать версию 15 вне зависимости от версии Qt, в которой оно компилируется. (Для удобства используется константа QDataStream::Qt_5_2, равная 15).
Класс QDataStream достаточно универсален. Он может использоваться для объекта QFile, но также и для QBuffer, QProcess, QTcpSocket, QUdpSocket или QSslSocket.
Qt также предоставляет класс QTextStream, который может использоваться с QDataStream для чтения и записи текстовых файлов.
Рассмотрим чтение данных из файла приложения Электронная таблица:
bool Spreadsheet::readFile(const QString &fileName)
{
//для чтения файла мы пользуемся объектом QFile
QFile file(fileName);
//используем флажок QIODevice::Readonly
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Электронная таблица"),
tr("Не удается прочитать файл %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream in(&file);
/*устанавливаем версию QDataStream на значение 15.
Формат чтения всегда должен совпадать с форматом записи*/
in.setVersion(QDataStream::Qt_5_2);
quint32 magic;
in >> magic;
if (magic != MagicNumber) {
QMessageBox::warning(this, tr("Электронная таблица"),
tr("Данный файл не является файлом электронной таблицы."));
return false;
}
/*если в начале файла содержится правильное "волшебное"
число, мы вызываем функцию сlear() для очистки в электронной таблице всех
ячеек и затем считываем данные ячеек. Поскольку файл содержит только данные
для непустых ячеек, маловероятно, что будет заполнена каждая ячейка электронной
таблицы, поэтому мы должны очистить все ячейки перед чтением файла*/
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;
}На следующем шаге рассмотрим реализацию слотов, относящихся к меню Edit данного приложения.