Создание MDI-приложений в Qt


MDI-приложение позволяет пользователю работать с несколькими открытыми документами. По своей сути оно очень напоминает обычный рабочий стол, только в виртуальном исполнении. Пользователь может разложить в его области несколько окон документов, или свернуть их. Окна документов могут перекрываться друг другом, а также могут быть развернуты во всю рабочую область.

Рабочая область, внутри которой размещаются окна документов, реализуется классом QWorkspace. Виджет этого класса производит "закулисное" управление динамически создаваемыми окнами документов. При помощи слотов title() и cascade(), определенных в этом классе, производится упорядочивание окон. Метод QWorkspace::windowList() возвращает список всех содержащихся в нем виджетов.

{рисунок}

Программа, окно которой показано на рисунке, реализует основные функции, присущие MDI-приложению. В качестве класса окна документа применяется класс DocWindow, использованный при реализации SDI-приложения.

{рисунок}


#ifndef _MDIProgram_h_
#define _MDIProgram_h_

#include <QMainWindow>

class QMenu;
class QWorkspace;
class QSignalMapper;
class DocWindow;

class MDIProgram : public QMainWindow {
    Q_OBJECT
private:
    QWorkspace*    m_pws;
    QMenu*         m_pmnuWindows;
    QSignalMapper* m_psigMapper;

    DocWindow* MDIProgram::createNewDoc();

public:
    MDIProgram(QWidget* pwgt = 0);

private slots:
    void slotChangeWindowTitle(const QString&);

private slots:
    void slotNewDoc ();
    void slotLoad   ();
    void slotSave   ();
    void slotSaveAs ();
    void slotAbout  ();
    void slotWindows();
};
#endif //_MDIProgram_h_

Определение класса MDIProgram содержит атрибуты, хранящие указатели на виджет рабочей области (m_pws), на всплывающее меню Windows (Окна) (m_pmnuWindows) и на сопоставителя сигналов (m_psigMapper).

Объекты класса QAction в состоянии высылать сигналы triggered() только с булевыми значениями. Для нас этого недостаточно, так как мы намереваемся высылать указатели виджетов окон редактирования. Поэтому мы и прибегли к использованию класса сопоставления сигналов QSignalMapper.

В классе MDIProgram определен слот, предназначенный для работы с окнами документов slotWindows(), загрузки и сохранения файлов (slotLoad(), slotSave() и slotSaveAs()), создания новых документов (slotNewDoc()), а также предоставления информации о самом приложении.


MDIProgram::MDIProgram(QWidget* pwgt/*=0*/) : QMainWindow(pwgt)
{
    QAction* pactNew = new QAction("New File", 0);
    pactNew->setText("&New");
    pactNew->setShortcut(QKeySequence("CTRL+N"));
    pactNew->setToolTip("New Document");
    pactNew->setStatusTip("Create a new file");
    pactNew->setWhatsThis("Create a new file");
    pactNew->setIcon(QPixmap(filenew));
    connect(pactNew, SIGNAL(triggered()), SLOT(slotNewDoc()));

    QAction* pactOpen = new QAction("Open File", 0);
    pactOpen->setText("&Open...");
    pactOpen->setShortcut(QKeySequence("CTRL+O"));
    pactOpen->setToolTip("Open Document");
    pactOpen->setStatusTip("Open an existing file");
    pactOpen->setWhatsThis("Open an existing file");
    pactOpen->setIcon(QPixmap(fileopen));
    connect(pactOpen, SIGNAL(triggered()), SLOT(slotLoad()));

    QAction* pactSave = new QAction("Save File", 0);
    pactSave->setText("&Save");
    pactSave->setShortcut(QKeySequence("CTRL+S"));
    pactSave->setToolTip("Save Document");
    pactSave->setStatusTip("Save the file to disk");
    pactSave->setWhatsThis("Save the file to disk");
    pactSave->setIcon(QPixmap(filesave));
    connect(pactSave, SIGNAL(triggered()), SLOT(slotSave()));

    QToolBar* ptbFile = new QToolBar("File Operations");
    ptbFile->addAction(pactNew);
    ptbFile->addAction(pactOpen);
    ptbFile->addAction(pactSave);
    addToolBar(Qt::TopToolBarArea, ptbFile);

    QMenu* pmnuFile = new QMenu("&File");
    pmnuFile->addAction(pactNew);
    pmnuFile->addAction(pactOpen);
    pmnuFile->addAction(pactSave);
    pmnuFile->addAction("Save &As...", this, SLOT(slotSaveAs()));
    pmnuFile->addSeparator();
    pmnuFile->addAction("&Quit", 
                        qApp, 
                        SLOT(closeAllWindows()), 
                        QKeySequence("CTRL+Q")
                       );
    menuBar()->addMenu(pmnuFile);

    m_pmnuWindows = new QMenu("&Windows");
    menuBar()->addMenu(m_pmnuWindows);
    connect(m_pmnuWindows, SIGNAL(aboutToShow()), SLOT(slotWindows()));
    menuBar()->addSeparator();

    QMenu* pmnuHelp = new QMenu("&Help");
    pmnuHelp->addAction("&About", this, SLOT(slotAbout()), Qt::Key_F1);
    menuBar()->addMenu(pmnuHelp);

    m_pws = new QWorkspace;
    m_pws->setScrollBarsEnabled(true);
    setCentralWidget(m_pws);

    m_psigMapper = new QSignalMapper(this);
    connect(m_psigMapper, 
            SIGNAL(mapped(QWidget*)), 
            m_pws, 
            SLOT(setActiveWindow(QWidget*))
           );

    statusBar()->showMessage("Ready", 3000);
}

В конструкторе класса MDIProgram создаются три объекта действий для команд создания, открытия и сохранения документов — указатели pactNew, pactOpen и pactSave соответственно. Сигнал объектов triggered() соединяется со слотами класса MDIProgram. Вызовами методов addAction() объекты действий добавляются к панели инструментов и в меню.

Команда меню File | Quit (Файл | Выход) соединяется со слотом объекта приложения closeAllWindows(), который производит закрытие всех окон приложения.

Для создания рабочей области MDI-приложения необходимо создать виджет QWorkspace. Чтобы содержимое рабочей области можно было прокручивать, вызывается метод setScrollBarEnabled(), в который передается true. Установка рабочей области в главном окне виджета производится методом setCentralWidget().

После создания объекта сопоставителя сигналов (указатель m_psigMapper), его сигнал mapped() соединяется со слотом виджета рабочей области setActiveWindow(). Это позволит нам высылать, вместе с сигналами, указатели на виджеты, которые будет обрабатывать слот setActiveWindow().

Метод showMessage(), вызываемый из виджета строки состояния, отображает надпись "Ready" в течение трех секунд.


void MDIProgram::slotNewDoc()
{
    createNewDoc()->show();
}

Слот slotNewDoc()создает новое окно документа и делает его видимым.


DocWindow* MDIProgram::createNewDoc()
{
    DocWindow* pdoc = new DocWindow;
    m_pws->addWindow(pdoc);
    pdoc->setAttribute(Qt::WA_DeleteOnClose);
    pdoc->setWindowTitle("Unnamed Document");
    pdoc->setWindowIcon(QPixmap(filenew));
    connect(pdoc, 
            SIGNAL(changeWindowTitle(const QString&)), 
            SLOT(slotChangeWindowTitle(const QString&))
           );

    return pdoc;
}

В createNewDoc() создается виджет класса DocWindow и добавляется вызовом метода addWindow() в рабочую область приложения. В метод setAttribute() передается значение Qt::WADeleteOnClose, сообщающее виджету о том, что он должен быть уничтожен при закрытии своего окна.

Метод setWindowTitle() устанавливает заголовок окна. Небольшое растровое изображение в области заголовка устанавливается методом setWindowIcon(). Для изменения заголовка окна виджета, а также и окна программы, если виджет развернут, сигнал changeWindowTitie() соединяется со слотом slotChangeWindowTitle().


void MDIProgram::slotChangeWindowTitle(const QString& str)
{
    qobject_cast<DocWindow*>(sender())->setWindowTitle(str);
}

Слот slotChangeWindowTitle() определен как private, и не может быть вызван извне. Поэтому мы проигнорировали проверку успешности приведения к типу DocWindow и сразу вызвали, из виджета окна редактирования, метод setWindowTitle(), установив в нем имя и местонахождение ассоциированного с ним файла.


void MDIProgram::slotLoad()
{
    DocWindow* pdoc = createNewDoc();
    pdoc->slotLoad();
    pdoc->show();
}

Внутри слота slotLoad() вызывается метод createNewDoc(), который создает новый виджет документа и возвращает его указатель, после чего производится делегирование операции считывания далее, к виджету созданного документа.


void MDIProgram::slotSave()
{
    DocWindow* pdoc = qobject_cast<DocWindow*>(m_pws->activeWindow());
    if (pdoc) {
        pdoc->slotSave();
    }
}

Слот slotSave() получает указатель на текущее окно документа при помощи виджета рабочей области и, в случае если приведение к типу DocWindow было успешным, производит делегирование операции сохранения.


void MDIProgram::slotSaveAs()
{
    DocWindow* pdoc = qobject_cast<DocWindow*>(m_pws->activeWindow());
    if (pdoc) {
       pdoc->slotSaveAs();
    }
}

Действия слота slotSaveAs() аналогичны действиям slotSave(), только с делегированием метода slotSaveAs().


void MDIProgram::slotAbout()
{
    QMessageBox::about(this, "Application", "MDI Example");
}

Слот slotAbout() отображает окно сообщения с информацией о приложении.


void MDIProgram::slotWindows()
{
    m_pmnuWindows->clear();

    QAction* pact = 
        m_pmnuWindows->addAction("&Cascade", m_pws, SLOT(cascade()));
    pact->setEnabled(!m_pws->windowList().isEmpty());

    pact = m_pmnuWindows->addAction("&Tile", m_pws, SLOT(tile()));
    pact->setEnabled(!m_pws->windowList().isEmpty());

    m_pmnuWindows->addSeparator();

    QList<QWidget*> listDoc = m_pws->windowList();
    m_pmnuWindows->setEnabled(!listDoc.isEmpty());

    for (int i = 0; i < listDoc.size(); ++i) {
        pact = m_pmnuWindows->addAction(listDoc.at(i)->windowTitle());
        pact->setCheckable(true);
        pact->setChecked(m_pws->activeWindow() == listDoc.at(i));
        connect(pact, SIGNAL(triggered()), m_psigMapper, SLOT(map()));
        m_psigMapper->setMapping(pact, listDoc.at(i));
    }
}

Еще одним отличием MDI- от SDI-приложения является наличие всплывающего меню Windows (Окна), назначение которого — управление окнами документов, находящимися в рабочей области. Для отображения актуальной информации, перед показом, это меню необходимо очистить методом clear(). Первыми в меню Windows (Окна) добавляются команды меню Cascade (Каскад) и Tile (Мозаика). В зависимости от наличия в рабочей области окон (опрашивается методом windowList()), эти две команды при помощи вызова метода setEnabled() становятся доступными или недоступным. В цикле for производится размещение команд с названиями окон документов в меню. Каждая команда меню, вызовом метода setCheckable() со значением true, получает возможность оснащения флажком, который устанавливается только у команды, ассоциирующейся с активным окном. Определение того, является ли окно активным, а также установка статуса активности, достигается сравнением, производимым в методе setChecked().

Далее, сигнал triggered() объекта действия (указатель pact) соединяется со слотом map() сопоставителя сигналов m_psigMapper. Благодаря этому, при активации команды меню будет выслан сигнал mapped() с указателем на виджет окна редактирования, возвращаемый методом at(), который устанавливается методом setMapper().

Читать далее: Работа с файлами, директориями и потоками ввода/вывода в Qt