4.3. Загрузка и сохранение.

Теперь перейдем к реализации загрузки и сохранения файлов (в двоичном формате), создаваемых нашей программой. Делать мы это будем с помощью QFile и QDataStream, которые предоставляют платформо-независимый интерфейс для операций ввода/вывода двоичных данных.

Начнем с функции записи файла на диск:

bool Spreadsheet::writeFile(const QString &fileName) { QFile file(fileName); if (!file.open(IO_WriteOnly)) { QMessageBox::warning(this, tr("Spreadsheet"), tr("Cannot write file %1:\n%2.") .arg(file.name()) .arg(file.errorString())); return false; } QDataStream out(&file); out.setVersion(5); out << (Q_UINT32)MagicNumber; QApplication::setOverrideCursor(waitCursor); for (int row = 0; row < NumRows; ++row) { for (int col = 0; col < NumCols; ++col) { QString str = formula(row, col); if (!str.isEmpty()) out << (Q_UINT16)row << (Q_UINT16)col << str; } } QApplication::restoreOverrideCursor(); return true; } Функция writeFile() вызывается из MainWindow::saveFile(), для записи файла на диск. В случае успеха возвращает true, иначе -- false.

Функция начинается с создания экземпляра QFile, с заданным именем файла, после чего файл открывается на запись. Затем создается объект QDataStream, который, используя QFile, записывает данные на диск. Непосредственно перед записью данных, мы меняем внешний вид курсора мыши, показывая занятость приложения. А после записи -- восстанавливаем его. В конце функции файл автоматически закрывается деструктором QFile.

QDataStream поддерживает основные типы языка C++, а так же ряд типов, определяемых библиотекой Qt. Синтаксис соответствует стандарту классов <iostream>. Например,

out << x << y << z; записывает переменные x, y и z в поток, а in >> x >> y >> z; читает их из потока.

Поскольку базовые типы языка C++ char, short, int, long и long long могут иметь различный размер на разных платформах, в целях безопасности их следует приводить к одному из следующих: Q_INT8, Q_UINT8, Q_INT16, Q_UINT16, Q_INT32, Q_UINT32, Q_INT64, Q_UINT64, которые гарантированно имеют декларируемый, в битах, размер.

QDataStream -- довольно универсальный класс, он может совместно работать не только с QFile, но так же и с QBuffer, и с QSocket, и с QSocketDevice. Точно так же QFile может использоваться совместно с QTextStream и как самостоятельное средство работы с файлами. В Главе 10 мы глубже рассмотрим эти классы.

Формат файлов приложения Spreadsheet очень прост. Начинается файл с 32-х битного числа, идентифицирующего формат (MagicNumber определена как 0x7F51C882 в spreadsheet.h). Далее следует серия блоков, каждый из которых соответствует одной ячейке (номера строки и колонки, и формула). Для экономии мы не записываем в файл пустые ячейки.

Рисунок 4.5. Формат файла Spreadsheet.


Двоичное представление типов данных определяется классом QDataStream. Например, тип Q_UINT16 представлен двумя байтами, следующими в порядке big-endian (т.е. первым идет старший байт, потом -- младший). Тип QString записывается как последовательность символов в кодировке Unicode.

Двоичное представление типов, определяемых библиотекой Qt, стало осуществляться еще в Qt 1.0 и, вероятно, будет развиваться и дальше, чтобы в процессе развития библиотеки имелась возможность представлять в двоичном виде вновь появляющиеся типы. По-умолчанию, QDataStream использует самую современную версию двоичного формата (версия 5 в Qt 3.2), но способен работать и с более ранними версиями. Воизбежание проблем с совместимостью, на тот случай, если наша программа будет скомпилирована с более свежим выпуском Qt, мы укажем QDataStream на то, что необходимо использовать 5-ю версию, независимо от того, с какой версией Qt была скомпилирована программа.

bool Spreadsheet::readFile(const QString &fileName) { QFile file(fileName); if (!file.open(IO_ReadOnly)) { QMessageBox::warning(this, tr("Spreadsheet"), tr("Cannot read file %1:\n%2.") .arg(file.name()) .arg(file.errorString())); return false; } QDataStream in(&file); in.setVersion(5); Q_UINT32 magic; in >> magic; if (magic != MagicNumber) { QMessageBox::warning(this, tr("Spreadsheet"), tr("The file is not a " "Spreadsheet file.")); return false; } clear(); Q_UINT16 row; Q_UINT16 col; QString str; QApplication::setOverrideCursor(waitCursor); while (!in.atEnd()) { in >> row >> col >> str; setFormula(row, col, str); } QApplication::restoreOverrideCursor(); return true; } Функция readFile() очень похожа на writeFile(). Для работы с файлом опять используется QFile, только на этот раз при открытии файла устанавливается флаг режима доступа IO_WriteOnly. Далее идет установка версии формата. При чтении данных в двоичном представлении всегда должна указываться та же версия, которая использовалась при записи.

Если сигнатура файла (magic number) представлена корректным значением, то вызывается clear(), для очистки таблицы, поскольку в файле могут быть представлены не все ячейки.