Загрузка и сохранение
Загрузка и сохранение
Теперь мы реализуем загрузку и сохранение файла данных для приложения Электронная таблица, используя двоичный пользовательский формат. Для этого мы используем объекты QFile и QDataStream, которые совместно обеспечивают независимый от платформы ввод—вывод в двоичном формате.
Мы начнем с записи файла данных Электронная таблица:
01 bool Spreadsheet::writeFile(const QString &fileName)
02 {
03 QFile file(fileName);
04 if (!file.open(QIODevice::WriteOnly)) {
05 QMessageBox::warning(this, tr("Spreadsheet"),
06 tr("Cannot write file %1: %2.")
07 .arg(file.fileName())
08 .arg(file.errorString()));
09 return false;
10 }
11 QDataStream out(&file);
12 out.setVersion(QDataStream::Qt_4_1);
13 out << quint32(MagicNumber);
14 QApplication::setOverrideCursor(Qt::WaitCursor);
15 for (int row = 0; row < RowCount; ++row) {
16 for (int column = 0; column < ColumnCount; ++column) {
17 QString str = formula(row, column);
18 if (!str.isEmpty())
19 out << quint16(row) << quint16(column) << str;
20 }
21 }
22 QApplication::restoreOverrideCursor();
23 return true;
24 }
Функция writeFile() вызывается из MainWindow::saveFile() для записи файла на диск. Она возвращает true при успешном завершении и false при ошибке.
Мы создаем объект QFile, задавая имя файла, и вызываем функцию open() для открытия файла для записи данных. Мы также создаем объект QDataStream, который предназначен для работы с QFile и использует его для записи данных.
Непосредственно перед записью данных мы изменяем курсор приложения на стандартный курсор ожидания (обычно он имеет вид песочных часов) и затем восстанавливаем нормальный курсор после окончания записи данных. В конце функции файл автоматически закрывается деструктором QFile.
QDataStream поддерживает основные типы С++ совместно со многими типами Qt. Их синтаксис напоминает синтаксис классов <iostream> стандартного С++. Например,
out << x << у << z;
выполняет запись в поток значений переменных x, у и z, а
in >> x >> у >> z;
считывает их из потока. Поскольку базовые типы С++, такие как char, short, int, long и long long, на различных платформах могут иметь различный размер, надежнее преобразовать их типы в qint8, quint8, qint16, quint16, qint32, quint32, qint64 и quint64, что гарантирует использование объявленного в них размера (в битах).
Файл данных Электронная таблица имеет очень простой формат. Он начинается с 32-битового числа, идентифицирующего формат файла («волшебное» число MagicNumber определено в spreadsheet.h как 0x7F51C883 — произвольное случайное число). Затем идет последовательность блоков, каждый из которых содержит строку, столбец и формулу одной ячейки. Для экономии места мы не заполняем пустые ячейки.
Рис. 4.3. Формат файла данных для приложения Электронная таблица.
Точное представление типов данных определяется в QDataStream. Например, quint16 представляется двумя байтами со старшим байтом в конце, a QString задается длиной строки, за которой следуют символы в коде Unicode.
Двоичное представление типов в Qt достаточно сильно усовершенствовалось со времени выхода версии Qt 1.0. Такая тенденция, вероятно, сохранится в будущих версиях Qt, чтобы идти вровень с развитием существующих типов и обеспечить новые типы в Qt. По умолчанию класс QDataStream использует самую последнюю версию двоичного формата (версия 7 в Qt 4.1), но он также может быть настроен на чтение прошлых версий. Для того чтобы избежать проблем совместимости при перекомпиляции приложения в будущем, в новой версии Qt мы заставляем QDataStream использовать версию 7 вне зависимости от версии Qt, в которой оно компилируется. (Для удобства используется константа QDataStream::Qt_4_1, равная 7.)
Класс QDataStream достаточно универсален. Он может использоваться для объекта QFile, но также и для QBuffer, QProcess, QTcpSocket или QUdpSocket. Qt также предоставляет класс QTextStream, который может использоваться с QDataStream для чтения и записи текстовых файлов. В главе 10 подробно рассматриваются эти классы и описываются различные методы работы с разными версиями QDataStream.
01 bool Spreadsheet::readFile(const QString &fileName)
02 {
03 QFile file(fileName);
04 if (!file.open(QIODevice::ReadOnly)) {
05 QMessageBox::warning(this, tr("Spreadsheet"),
06 tr("Cannot read file %1: %2.")
07 .arg(file.fileName())
08 .arg(file.errorString()));
09 return false;
10 }
11 QDataStream in(&file);
12 in.setVersion(QDataStream::Qt_4_1);
13 quint32 magic;
14 in >> magic;
15 if (magic != MagicNumber) {
16 QMessageBox::warning(this, tr("Spreadsheet"),
17 tr("The file is not a Spreadsheet file."));
18 return false;
19 }
20 clear();
21 quint16 row;
22 quint16 column;
23 QString str;
24 QApplication::setOverrideCursor(Qt::WaitCursor);
25 while (!in.atEnd()) {
26 in >> row >> column >> str;
27 setFormula(row, column, str);
28 }
29 QApplication::restoreOverrideCursor();
30 return true;
31 }
Функция readFile() очень напоминает writeFile(). Для чтения файла мы пользуемся объектом QFile, но теперь мы используем флажок QIODevice::ReadOnly, а не QIODevice::WriteOnly. Затем мы устанавливаем версию QDataStream на значение 7. Формат чтения всегда должен совпадать с форматом записи.
Если в начале файла содержится правильное «волшебное» число, мы вызываем функцию clear() для очистки в электронной таблице всех ячеек и затем считываем данные ячеек. Поскольку файл содержит только данные для непустых ячеек, маловероятно, что будет заполнена каждая ячейка электронной таблицы, поэтому мы должны очистить все ячейки перед чтением файла.