На этом шаге рассмотрим загрузку и сохранение файла данных для приложения Электронная таблица, используя двоичный пользовательский формат.
Для этого будем использовать объекты 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 данного приложения.