3.2. Создание меню и панелей инструментов.

Большинство современных приложений имеют как меню, так и панели инструментов, которые содержат более или менее идентичный набор команд. Меню дают пользователю возможность заняться "исследованием" приложения, изучать доступные команды, а панели инструментов служат для быстрого доступа к командам, используемым наиболее часто.

Qt значительно упрощает разработку меню и панелей инструментов за счет использования концепции "действия" (action). Action -- это элемент, который может быть добавлен в меню и/или на панель инструментов. Для создания меню и панели инструментов необходимо выполнить следующие шаги:

В нашем приложении все необходимое выполняет функция createActions(): void MainWindow::createActions() { newAct = new QAction(tr("&New"), tr("Ctrl+N"), this); newAct->setIconSet(QPixmap::fromMimeSource("new.png")); newAct->setStatusTip(tr("Create a new spreadsheet file")); connect(newAct, SIGNAL(activated()), this, SLOT(newFile())); В данном случае создается новое "действие" с названием "New", горячей комбинацией клавиш Ctrl+N и с владельцем -- главным окном приложения. Затем к "действию" прицепляются иконка (new.png) и текст подсказки, который будет выводиться в строке состояния. В заключение -- сигнал activated() подключается к слоту главного окна newFile(), который будет описан в следующем разделе. Без этого соединения, при выборе пункта меню "File|New" или при нажатии на кнопку "New" (в панели инструментов), ничего происходить не будет.

Аналогичным образом создаются все остальные "действия" (action).

Рисунок 3.3. Меню приложения Spreadsheet.


Команда (action) "Show Grid", в меню "Options" реализуется несколько иначе:

showGridAct = new QAction(tr("&Show Grid"), 0, this); showGridAct->setToggleAction(true); showGridAct->setOn(spreadsheet->showGrid()); showGridAct->setStatusTip(tr("Show or hide the spreadsheet s " "grid")); connect(showGridAct, SIGNAL(toggled(bool)), spreadsheet, SLOT(setShowGrid(bool))); Эта команда имеет два фиксированных состояния -- включено-выключено. В меню рядом с ней отображается галочка (когда включено), а на панели инструментов она выглядит как кнопка с фиксацией. Когда "действие" включено, то компонент Spreadsheet отображается в окне приложения. Мы инициализируем "действие" значением по-умолчанию -- включено. Таким образом выполняется начальная синхронизация "действия" с фактическим режимом отображения компонента Spreadsheet. Затем мы подключаем сигнал toggled(bool) к слоту setShowGrid(bool), компонента Spreadsheet. После этого "действие" (action) добавляется в меню или на панель инструментов. Теперь пользователь может "включать" и "выключать" таблицу.

Команды "Show Grid" и "Auto-recalculate" -- являются "действиями" с независимой фиксацией. Однако, QAction имеет наследника -- QActionGroup, с помощью которого можно создавать группы "действий" с зависимой фиксацией.

Рисунок 3.4. About Qt.


aboutQtAct = new QAction(tr("About &Qt"), 0, this); aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); connect(aboutQtAct, SIGNAL(activated()), qApp, SLOT(aboutQt())); }

Для вызова диалога "AboutQt" используется слот aboutQt() глобальной переменной qApp -- экземпляр класса QApplication.

После создания всех "действий" (action) мы можем разместить их в меню:

void MainWindow::createMenus() { fileMenu = new QPopupMenu(this); newAct->addTo(fileMenu); openAct->addTo(fileMenu); saveAct->addTo(fileMenu); saveAsAct->addTo(fileMenu); fileMenu->insertSeparator(); exitAct->addTo(fileMenu); for (int i = 0; i < MaxRecentFiles; ++i) recentFileIds[i] = -1; В Qt все меню являются экземплярами QPopupMenu. Мы создали меню "File" и затем добавили в него пункты "New", "Open", "Save", "Save As" и "Exit". Перед пунктом "Exit" добавлен разделитель, чтобы визуально отделить его от остальных. Цикл for инициализирует recentFileIds -- массив файлов, открывавшихся недавно. Мы еще вернемся к этому массиву, когда приступим к рассмотрению реализации слотов меню "File" в следующем разделе. editMenu = new QPopupMenu(this); cutAct->addTo(editMenu); copyAct->addTo(editMenu); pasteAct->addTo(editMenu); deleteAct->addTo(editMenu); selectSubMenu = new QPopupMenu(this); selectRowAct->addTo(selectSubMenu); selectColumnAct->addTo(selectSubMenu); selectAllAct->addTo(selectSubMenu); editMenu->insertItem(tr("&Select"), selectSubMenu); editMenu->insertSeparator(); findAct->addTo(editMenu); goToCellAct->addTo(editMenu); Меню "Edit" включает в себя подменю, которое так же является экземпляром класса QPopupMenu. Мы просто создаем подменю и вставляем его в то место меню "Edit", где оно должно находиться. toolsMenu = new QPopupMenu(this); recalculateAct->addTo(toolsMenu); sortAct->addTo(toolsMenu); optionsMenu = new QPopupMenu(this); showGridAct->addTo(optionsMenu); autoRecalcAct->addTo(optionsMenu); helpMenu = new QPopupMenu(this); aboutAct->addTo(helpMenu); aboutQtAct->addTo(helpMenu); menuBar()->insertItem(tr("&File"), fileMenu); menuBar()->insertItem(tr("&Edit"), editMenu); menuBar()->insertItem(tr("&Tools"), toolsMenu); menuBar()->insertItem(tr("&Options"), optionsMenu); menuBar()->insertSeparator(); menuBar()->insertItem(tr("&Help"), helpMenu); } Меню "Tools", "Options" и "Help" создаются аналогичным образом и в конце, все созданные меню вставляются в полосу меню, в верхней части главного окна приложения. Функция QMainWindow::menuBar() возвращает указатель на экземпляр класса QMenuBar, который создается автоматически, при первом вызове menuBar(). Мы добавили разделитель между меню "Options" и "Help". В случае отображения в стиле Motif и ему подобных, меню "Help" смещается в крайнее правое положение, в других стилях отображения разделитель игнорируется.

Рисунок 3.5. Полоса меню в стиле Motif и Windows.


Создание панелей инструментов происходит очень похожим образом:

void MainWindow::createToolBars() { fileToolBar = new QToolBar(tr("File"), this); newAct->addTo(fileToolBar); openAct->addTo(fileToolBar); saveAct->addTo(fileToolBar); editToolBar = new QToolBar(tr("Edit"), this); cutAct->addTo(editToolBar); copyAct->addTo(editToolBar); pasteAct->addTo(editToolBar); editToolBar->addSeparator(); findAct->addTo(editToolBar); goToCellAct->addTo(editToolBar); } Мы создали две панели инструментов -- "File" и "Edit". Как и меню, панели инструментов могут включать в себя разделители.

Рисунок 3.6. Панели инструментов приложения Spreadsheet.

Теперь, когда мы закончили создание главного меню и панелей инструментов, добавим контекстное меню.

void MainWindow::contextMenuEvent(QContextMenuEvent *event) { QPopupMenu contextMenu(this); cutAct->addTo(&contextMenu); copyAct->addTo(&contextMenu); pasteAct->addTo(&contextMenu); contextMenu.exec(event->globalPos()); } Когда пользователь щелкает правой кнопкой мыши, то виджету посылается событие (event) "контекстное меню". Перекрывая метод QWidget::contextMenuEvent(), мы можем перехватить это событие и показать контекстное меню в позиции курсора мыши.

Рисунок 3.5. Контекстное меню приложения Spreadsheet.


События, точно так же как сигналы и слоты, являются одним из фундаментальных аспектов программирования в Qt. События рождаются в недрах библиотеки, чтобы сообщить о щелчках мышью, о нажатиях клавиш, о попытках изменения размеров и т.п.. Они могут быть перехвачены и обработаны за счет перекрытия виртуальных функций, как это делается в нашем случае.

Отлавливать событие мы будем в MainWindow потому, что здесь реализуются все наши "действия" (actions). Однако, это событие можно поймать и в виджете Spreadsheet. Когда пользователь щелкнет правой кнопкой мыши по виджету, то этот виджет первым получит событие. Если виджет перекроет реализацию функции contextMenuEvent() и обработает событие, то дальше передаваться оно не будет. В противном случае оно будет передано дальше -- владельцу виджета (MainWindow). Более подробно события будут рассматриваться в Главе 7.

Функция-обработчик события вызова контекстного меню отличается от всего, что мы до сих пор видели, поскольку она создает экземпляр QPopupMenu, размещая его на стеке. Хотя, в принципе, можно было бы создать/удалить этот виджет и с помощью операторов new/delete:

QPopupMenu *contextMenu = new QPopupMenu(this); cutAct->addTo(contextMenu); copyAct->addTo(contextMenu); pasteAct->addTo(contextMenu); contextMenu->exec(event->globalPos()); delete contextMenu; Еще один примечательный аспект -- это функция exec(). Она выводит меню на экран, в заданную позицию, и ожидает, пока пользователь не сделает свой выбор, после чего управление возвращается в точку вызова. С этого момента экземпляр QPopupMenu нам больше не нужен, поэтому он удаляется. В случае размещения на стеке -- он будет уничтожен автоматически, по завершении работы функции.

Часть интерфейса, касающуюся меню и панелей инструментов, можно считать завершенной. В следующем разделе мы рассмотрим реализацию слотов меню "File".