Сталкивались ли вы с ощущением, что «сделать главное окно» в Qt — это быстро… пока не начинается реальная жизнь? Меню живёт отдельно, кнопки тулбара — отдельно, статус-бар молчит, а отключить одну команду приходится в двух местах — и всё это легко рассинхронизируется.
Эта глава раскроет, как профессиональные разработчики собирают «скелет» приложения так, чтобы интерфейс не разваливался при первом же расширении. Здесь обнаружите неочевидный способ централизовать команды, узнаете секрет аккуратной синхронизации меню/тулбаров и получите выигрыш в скорости разработки без хаоса в коде.
Будут разобраны 3 ключевых узла QMainWindow (рабочая область, панели, статус-бар), единый источник правды через QAction, док-панели (QDockWidget) и «профессиональная» заставка (QSplashScreen), которая маскирует долгую инициализацию. Плюс — практическое сравнение SDI vs MDI и как открыть несколько документов, не превращая проект в комок сигналов.
Если нужно, чтобы ваше приложение выглядело как “настоящее” — это тот самый переходный шаг, который лучше не пропускать.
В этой главе вы найдёте готовые к использованию примеры кода.
Самопроверка по главе
Почему использование QAction значительно эффективнее, чем создание отдельных команд меню и кнопок панели инструментов?Ответ
Правильный ответ: QAction централизует все элементы интерфейса (текст меню, иконку, горячие клавиши, подсказки) в одном объекте, устраняя дублирование кода и автоматически синхронизируя состояние элементов — например, отключение действия сразу отключает и команду меню, и кнопку на панели.
В чем принципиальная разница между виджетами QToolBar и QDockWidget с точки зрения их назначения?Ответ
Правильный ответ: QToolBar предназначен для размещения простых кнопок быстрого доступа к командам, тогда как QDockWidget позволяет размещать сложные составные виджеты, которые пользователь может перемещать между сторонами окна и даже отделять в самостоятельные окна.
Зачем в длительных операциях инициализации необходимо вызывать QApplication::processEvents()?Ответ
Правильный ответ: Без обработки накопившихся событий интерфейс «зависнет», что проявляется в виде «не отвечающего» приложения или крутящихся индикаторов ожидания. Вызов processEvents() позволяет обработать события GUI и сохранить отзывчивость интерфейса.
Почему в SDI-приложении метод closeEvent() должен проверять статус document()->isModified() перед закрытием?Ответ
Правильный ответ: Это предотвращает потерю несохраненных данных — если документ изменен, пользователю предлагается сохранить изменения. Вызов pe->ignore() позволяет отменить закрытие окна, если пользователь передумал.
Какая проблема возникнет, если в MDI-приложении вызвать m_pma->activeSubWindow()->widget() без предварительной проверки?Ответ
Правильный ответ: Если в рабочей области нет активного окна, activeSubWindow() вернет nullptr, и попытка вызова widget() приведет к аварийному завершению программы. Необходимо сначала проверить результат на nullptr.
В чем преимущество использования setAttribute(Qt::WA_DeleteOnClose) для окон документов в MDI-приложении?Ответ
Правильный ответ: Атрибут гарантирует автоматическое удаление виджета из памяти при закрытии окна, предотвращая утечки памяти при работе с множеством документов без необходимости ручного управления временем жизни объектов.
Почему меню Windows в MDI-приложении необходимо очищать и заново заполнять перед каждым показом?Ответ
Правильный ответ: Состав открытых документов динамически меняется — окна создаются и закрываются. Пересоздание меню перед показом гарантирует актуальность списка документов и правильную установку флажка у активного окна.
Для чего используется объект QSignalMapper в контексте меню Windows MDI-приложения?Ответ
Правильный ответ: QSignalMapper позволяет отправлять вместе с сигналом указатель на конкретное окно документа, так как стандартный сигнал triggered() передает только булево значение. Это необходимо для активации выбранного окна.
Как метод setAllowedAreas() в QDockWidget влияет на пользовательский опыт работы с приложением?Ответ
Правильный ответ: Метод ограничивает возможные места размещения дока, что позволяет разработчику контролировать компоновку интерфейса — например, запретить размещение панели по бокам, если она слишком широкая для вертикального расположения.
В чем разница между промежуточными и постоянными сообщениями строки состояния с точки зрения архитектуры приложения?Ответ
Правильный ответ: Промежуточные сообщения (showMessage) используют общую текстовую область и автоматически очищаются, а постоянные (addPermanentWidget) требуют отдельных виджетов и остаются видимыми постоянно — это разделяет временные уведомления и стабильную информацию о состоянии.
Что произойдет, если в MDI-приложении не соединить сигнал changeWindowTitle() документа со слотом главного окна?Ответ
Правильный ответ: Заголовок окна документа не будет обновляться при сохранении файла под новым именем или загрузке файла — пользователь будет видеть устаревшую информацию, что снижает юзабилити приложения.
Почему в примере с окном заставки объект QSplashScreen создается после QApplication, но до вызова exec()?Ответ
Правильный ответ: QApplication должен существовать для работы с GUI, а окно заставки должно отображаться до запуска цикла обработки событий exec() — чтобы быть видимым во время инициализации приложения и скрыть время загрузки.
Какую архитектурную проблему решает делегирование операций загрузки и сохранения от MDIProgram к DocWindow?Ответ
Правильный ответ: Разделение ответственности — MDIProgram управляет окнами и меню, а DocWindow инкапсулирует логику работы с файлами. Это соответствует принципу единственной ответственности и упрощает повторное использование DocWindow в других приложениях.
Практические задания
Простой уровень
Текстовый редактор с панелью инструментов
Создайте SDI-приложение с текстовым редактором (QTextEdit), которое имеет панель инструментов с тремя кнопками: Bold (полужирный), Italic (курсив) и Underline (подчеркивание). Используйте QAction для каждой кнопки и добавьте соответствующие иконки. Кнопки должны изменять форматирование выделенного текста.
Подсказки: Унаследуйте класс от QMainWindow. Используйте QTextEdit::setFontWeight(), setFontItalic() и setFontUnderline() для изменения форматирования. Создайте QAction объекты с методами setText(), setIcon() и setToolTip(). Соедините сигнал triggered() каждого действия со слотами, изменяющими форматирование. Добавьте действия на панель инструментов методом addAction().
Средний уровень
Приложение с настраиваемыми доками
Разработайте приложение с центральным виджетом (QTextEdit) и двумя док-виджетами: один для отображения списка открытых файлов (QListWidget), второй для свойств документа (количество символов, строк, слов в QLabel). Доки должны размещаться слева и справа, но пользователь может их перемещать. При вводе текста информация в правом доке должна обновляться в реальном времени.
Подсказки: Создайте два QDockWidget и установите в них соответствующие виджеты методом setWidget(). Используйте addDockWidget() с параметрами Qt::LeftDockWidgetArea и Qt::RightDockWidgetArea. Соедините сигнал textChanged() текстового редактора со слотом, который пересчитывает статистику. Используйте QString::length(), split(‘\n’).size() и split(QRegularExpression(“\\s+”)).size() для подсчета символов, строк и слов.
Сложный уровень
Полнофункциональное MDI-приложение с обработкой несохраненных изменений
Реализуйте MDI-приложение для работы с текстовыми документами, которое поддерживает создание, открытие и сохранение файлов. Приложение должно: отслеживать несохраненные изменения в каждом документе (отображать звездочку в заголовке), предупреждать при закрытии окна с несохраненными данными, корректно обрабатывать закрытие всего приложения с проверкой всех открытых документов, реализовать меню Windows с командами Cascade и Tile, а также списком всех открытых документов с возможностью переключения между ними.
Подсказки: Унаследуйте класс документа от QTextEdit и добавьте отслеживание изменений через document()->setModified(). Переопределите closeEvent() как в окне документа, так и в главном окне. В closeEvent() главного окна используйте цикл по m_pma->subWindowList() для проверки isModified() всех документов. Используйте QMessageBox::question() с кнопками Save/Discard/Cancel. Обновляйте заголовок окна добавлением “[*]” и вызовом setWindowModified(true). В меню Windows используйте цикл для создания действий с именами документов и setCheckable(true) для активного окна.
💬 Присоединяйтесь к обсуждению!
Разобрались с архитектурой MDI-приложений? Есть вопросы о том, когда использовать QDockWidget вместо панели инструментов?
Возникли сложности с обработкой несохраненных изменений или синхронизацией интерфейса через QAction?
Поделитесь своим опытом реализации главного окна приложения, обсудите лучшие практики работы с документами или задайте вопросы сообществу!