3.7. Работа с несколькими документами одновременно.

Мы готовы приступить к созданию функции main():

#include <qapplication.h> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mainWin; app.setMainWidget(&mainWin); mainWin.show(); return app.exec(); } Эта функция немного отличается от того, что мы видели до сих пор: экземпляр MainWindow был создан на стеке, без использования оператора new. Благодаря этому, объект класса MainWindow будет уничтожен автоматически, по завершении работы функции.

В данном случае, программа Spreadsheet представляет из себя единственное главное окно, и может работать только с одним документом в каждый конкретный момент времени. Если нам потребуется работать с несколькими документами одновременно, мы должны будем запустить несколько экземпляров программы.

Но для пользователя было бы гораздо удобнее, если бы программа могла запускать несколько главных окон с различными документами, как, например, это делают некоторые web-браузеры.

Попробуем внести дополнительные изменения в нашу программу, чтобы она могла одновременно работать с несколькими документами. Для этого, прежде всего, необходимо немного изменить меню File:

  • Пункт File|New создает новое главное окно с пустым документом, вместо того, чтобы создавать новый документ в этом же окне.

  • Пункт File|Close закрывает текущее главное окно.

  • Пункт File|Exit закрывает все окна приложения.

Рисунок 3.16. Новое меню File.




В своем первоначальном варианте, меню File не имело пункта Close, поскольку смысл операции закрытия окна был равносилен завершению приложения (пункт Exit).

Так выглядит новый вариант функции main():

#include <qapplication.h> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow *mainWin = new MainWindow; mainWin->show(); QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit())); return app.exec(); } Здесь мы связали сигнал lastWindowClosed() со слотом quit(), который завершает приложение.

В данном варианте, теперь имеет смысл создавать экземпляр MainWindow оператором new, поскольку затем, при закрытии окна, он будет удаляться оператором delete. Эта необходимость не возникает в случае приложения, которое работает с единственным документом.

Ниже приводится измененный вариант слота MainWindow::newFile():

void MainWindow::newFile() { MainWindow *mainWin = new MainWindow; mainWin->show(); } Здесь просто создается новый экземпляр MainWindow. Может показаться странным, что мы нигде не запоминаем указатель на вновь созданный объект, но здесь нет никакой ошибки -- Qt хранит указатели на все окна сама, без нашего участия.

Ниже приводится код, создающий "действия" (actions) Close и Exit:

closeAct = new QAction(tr("&Close"), tr("Ctrl+W"), this); connect(closeAct, SIGNAL(activated()), this, SLOT(close())); exitAct = new QAction(tr("E&xit"), tr("Ctrl+Q"), this); connect(exitAct, SIGNAL(activated()), qApp, SLOT(closeAllWindows())); Слот closeAllWindows() закрывает все окна приложения, кроме тех, которые отвергнут событие close. Это в точности соответствует нашим требованиям. Нам нет нужды беспокоиться о несохраненных изменениях, поскольку сохранение выполняется в обработчике MainWindow::closeEvent(), при закрытии окна.

Теперь наше приложение в состоянии работать с несколькими окнами. К сожалению, на данный момент у нас в программе кроется трудноуловимая ошибка. Если пользователь будет создавать и закрывать окна приложения, то может наступить момент, когда вся доступная память в машине будет исчерпана! Это происходит потому, что мы создаем новые окна, выбирая пункт меню File|New, но нигде не удаляем их из памяти. Когда пользователь закрывает очередное окно, то объект класса MainWindow не удаляется из памяти, а просто делается невидимым.

Решение этой проблемы заключается в добавлении флага WDestructiveClose в конструктор:

MainWindow::MainWindow(QWidget *parent, const char *name) : QMainWindow(parent, name, WDestructiveClose) { ... } Он вынуждает Qt удалять объект окна при его закрытии. Этот флаг один из множества, которые могут быть переданы в конструктор наследника от QWidget, но другие флаги используются довольно редко.

Однако, утечка памяти -- не единственная проблема, с которой мы можем столкнуться. Весь наш первоначальный дизайн предполагал работу с единственным главным окном. Теперь, каждое из окон приложения может иметь свой список недавно использовавшихся файлов и свои дополнительные настройки. Совершенно очевидно, что список недавно использовавшихся файлов должен быть глобальным для всего приложения. Сделать это можно довольно легко, достаточно просто объявить переменную recentFiles статической. Но, теперь везде, где необходимо вызвать updateRecentFileItems() для обновления меню File, мы должны вызвать эту функцию для всех главных окон. Ниже приводится код, который делает это:

QWidgetList *list = QApplication::topLevelWidgets(); QWidgetListIt it(*list); QWidget *widget; while ((widget = it.current())) { if (widget->inherits("MainWindow")) ((MainWindow *)widget)->updateRecentFileItems(); ++it; } delete list; Здесь выполняется перебор всех виджетов верхнего уровня и вызывается функция updateRecentFileItems() во всех экземплярах MainWindow. Аналогичный подход может быть использован для синхронизации флагов Show Grid и Auto-recalculate, а так же для предотвращения загрузки одного и того же документа дважды. Тип QWidgetList определен как QPtrList<QWidget>, который будет обсуждаться в Главе 11 (Классы-контейнеры).

Рисунок 3.17. SDI и MDI.

Когда приложение открывает каждый следующий документ в новом окне, то говорят, что приложение относится к классу SDI-приложений (от англ. single document interface -- однодокументный интерфейс). Популярная альтернатива SDI -- MDI (от англ. multiple document interface -- многодокументный интерфейс), в этом случае приложение имеет одно главное окно, которое может включать в себя несколько дочерних окон с открытыми документами и разделяющими между собой пространство главного окна. С помощью Qt можно создавать как SDI, так и MDI приложения. На рисунке 3.17 показаны оба варианта оформления приложения Spreadsheet. Более подробно MDI будет описан в Главе 6.