На этом уроке мы поговорим о создании таких графических элементов приложения, как меню и панель инструментов. Большинство современных приложений имеют как меню, так и панель инструментов, которые содержат более-менее идентичный набор команд.
Меню является стандартной частью приложения с графическим интерфейсом и предоставляет пользователю возможность заняться «исследованием» приложения (изучать доступные команды), в то время как панели инструментов обеспечивают быстрый доступ к наиболее часто используемым командам.
Создание простого меню
Сейчас мы попробуем создать простое меню.
Заголовочный файл — simplemenu.h:
1 2 3 4 5 6 7 8 9 10 |
#pragma once #include <QMainWindow> #include <QApplication> class SimpleMenu : public QMainWindow { public: SimpleMenu(QWidget *parent = 0); }; |
У нас есть строка меню с элементами и действиями, которые активируются по нажатию кнопки мыши (на соответствующий пункт). Чтобы работать с меню, нам сначала нужно выполнить наследование виджета QMainWindow
.
Файл реализации — simplemenu.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include "simplemenu.h" #include <QMenu> #include <QMenuBar> SimpleMenu::SimpleMenu(QWidget *parent) : QMainWindow(parent) { // Создаем объект класса QAction (действие) с названием пункта меню "Quit" QAction *quit = new QAction("&Quit", this); // Создаем объект класса QMenu (меню) QMenu *file; file = menuBar()->addMenu("&File"); // Помещаем действие "Quit" (Выход) в меню с помощью метода addAction() file->addAction(quit); // Когда мы выбираем в меню опцию "Quit", то приложение сразу же завершает свое выполнение connect(quit, &QAction::triggered, qApp, QApplication::quit); } |
Класс QAction представляет собой абстрактное действие пользовательского интерфейса, которое может быть вставлено в виджеты. В приложениях множество общих команд может быть вызвано через меню, панель инструментов и сочетания горячих клавиш. Поскольку пользователь ожидает, что каждая команда будет выполняться одним и тем же способом, независимо от используемого пользовательского интерфейса, то полезно представить каждую команду как действие. При этом каждый объект класса QMenu может иметь один или несколько объектов действия.
Основной файл программы — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "simplemenu.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); SimpleMenu window; window.resize(250, 150); window.setWindowTitle("Simple menu"); window.show(); return app.exec(); } |
Результат выполнения программы:
Иконки, горячие клавиши и разделители
Теперь давайте немного улучшим наше предыдущее приложение. Добавим в него иконки к пунктам меню, зададим сочетания горячих клавиш и установим разделитель.
Заголовочный файл — anothermenu.h:
1 2 3 4 5 6 7 8 9 10 |
#pragma once #include <QMainWindow> #include <QApplication> class AnotherMenu : public QMainWindow { public: AnotherMenu(QWidget *parent = 0); }; |
В данном примере у нас есть одно меню с тремя действиями. При этом только действие Quit
является рабочим. Мы также установим разделитель — горизонтальную линию, которая позволит нам группировать действия меню в некоторые логические группы, и сочетание клавиш CTRL+Q
, которое будет завершать работу нашего приложения.
Файл реализации — anothermenu.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include "anothermenu.h" #include <QMenu> #include <QMenuBar> AnotherMenu::AnotherMenu(QWidget *parent) : QMainWindow(parent) { // Данные изображения мы будем использовать в качестве иконок в нашем меню QPixmap newpix("new.png"); QPixmap openpix("open.png"); QPixmap quitpix("quit.png"); // Здесь в качестве первых аргументов мы используем конструкторы класса QAction QAction *newa = new QAction(newpix, "&New", this); QAction *open = new QAction(openpix, "&Open", this); QAction *quit = new QAction(quitpix, "&Quit", this); // А здесь мы задаем сочетание горячих клавиш CTRL+Q, которое будет выполнять действие Quit (Выход) quit->setShortcut(tr("CTRL+Q")); QMenu *file; file = menuBar()->addMenu("&File"); file->addAction(newa); // добавляем действие "New" file->addAction(open); // добавляем действие "Open" file->addSeparator(); // устанавливаем разделитель file->addAction(quit); // добавляем действие "Quit" // В некоторых средах значки меню по умолчанию не отображаются, поэтому мы можем попробовать отключить атрибут Qt::AA_DontShowIconsInMenus qApp->setAttribute(Qt::AA_DontShowIconsInMenus, false); connect(quit, &QAction::triggered, qApp, &QApplication::quit); } |
Основной файл программы — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "anothermenu.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); AnotherMenu window; window.resize(350, 200); window.setWindowTitle("Another menu"); window.show(); return app.exec(); } |
Результат выполнения программы:
Меню с чекбоксом
Теперь попробуем создать пункт меню, который можно будет отметить галочкой.
Заголовочный файл — checkable.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#pragma once #include <QMainWindow> #include <QApplication> class Checkable : public QMainWindow { Q_OBJECT public: Checkable(QWidget *parent = 0); private slots: void toggleStatusbar(); private: QAction *viewst; }; |
Файл реализации — checkable.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include "checkable.h" #include <QMenu> #include <QMenuBar> #include <QStatusBar> Checkable::Checkable(QWidget *parent) : QMainWindow(parent) { // Создаем действие (пункт) "View statusbar" в меню viewst = new QAction("&View statusbar", this); // Делаем так, чтобы этот пункт меню можно было отметить галочкой viewst->setCheckable(true); // Делаем этот пункт меню отмеченным галочкой по умолчанию viewst->setChecked(true); QMenu *file; file = menuBar()->addMenu("&File"); file->addAction(viewst); statusBar(); connect(viewst, &QAction::triggered, this, &Checkable::toggleStatusbar); } void Checkable::toggleStatusbar() { // Определяем, установлен ли флажок для элемента меню, и, исходя из этого, скрываем или показываем строку состояния if (viewst->isChecked()) { statusBar()->show(); } else { statusBar()->hide(); } } |
Основной файл программы — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "checkable.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Checkable window; window.resize(250, 150); window.setWindowTitle("Checkable menu"); window.show(); return app.exec(); } |
Результат выполнения программы:
Добавление панели инструментов
Класс QToolBar предоставляет программисту возможность использовать подвижную панель, содержащую набор элементов управления, которые обеспечивают быстрый доступ к действиям приложения.
Заголовочный файл — toolbar.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#pragma once #include <QMainWindow> #include <QApplication> class Toolbar : public QMainWindow { Q_OBJECT public: Toolbar(QWidget *parent = 0); }; |
Чтобы создать панель инструментов, нужно выполнить наследование виджета QMainWindow
.
Файл реализации — toolbar.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "toolbar.h" #include <QToolBar> #include <QIcon> #include <QAction> Toolbar::Toolbar(QWidget *parent) : QMainWindow(parent) { QPixmap newpix("new.png"); QPixmap openpix("open.png"); QPixmap quitpix("quit.png"); // Устанавливаем указатель на созданную панель инструментов QToolBar *toolbar = addToolBar("main toolbar"); toolbar->addAction(QIcon(newpix), "New File"); // добавляем действие "New File" на панель инструментов toolbar->addAction(QIcon(openpix), "Open File"); // добавляем действие "Open File" на панель инструментов toolbar->addSeparator(); // добавляем разделитель на панель инструментов QAction *quit = toolbar->addAction(QIcon(quitpix), "Quit Application"); // добавляем действие "Quit" на панель инструментов connect(quit, &QAction::triggered, qApp, &QApplication::quit); } |
Основной файл программы — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "toolbar.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Toolbar window; window.resize(300, 200); window.setWindowTitle("QToolBar"); window.show(); return app.exec(); } |
Результат выполнения программы:
Каркас приложения
Сейчас мы попробуем создать заготовку, которая может стать основой для нашего будущего (уже более сложного) приложения. Пример основан на возможностях виджета QMainWindow
.
Заголовочный файл — skeleton.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#pragma once #include <QMainWindow> #include <QApplication> class Skeleton : public QMainWindow { Q_OBJECT public: Skeleton(QWidget *parent = 0); }; |
Создаем элементы меню, панель инструментов и панель состояния.
Файл реализации — skeleton.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include "skeleton.h" #include <QToolBar> #include <QIcon> #include <QAction> #include <QMenu> #include <QMenuBar> #include <QStatusBar> #include <QTextEdit> Skeleton::Skeleton(QWidget *parent) : QMainWindow(parent) { QPixmap newpix("new.png"); QPixmap openpix("open.png"); QPixmap quitpix("quit.png"); QAction *quit = new QAction("&Quit", this); QMenu *file; file = menuBar()->addMenu("&File"); file->addAction(quit); connect(quit, &QAction::triggered, qApp, &QApplication::quit); QToolBar *toolbar = addToolBar("main toolbar"); toolbar->addAction(QIcon(newpix), "New File"); toolbar->addAction(QIcon(openpix), "Open File"); toolbar->addSeparator(); QAction *quit2 = toolbar->addAction(QIcon(quitpix), "Quit Application"); connect(quit2, &QAction::triggered, qApp, &QApplication::quit); QTextEdit *edit = new QTextEdit(this); // создаем виджет QTextEdit setCentralWidget(edit); // помещаем созданный виджет в центр виджета QMainWindow statusBar()->showMessage("Ready"); // показываем в нижней панели приложения сообщение "Ready" } |
Основной файл приложения — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "skeleton.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Skeleton window; window.resize(350, 250); window.setWindowTitle("Application skeleton"); window.show(); return app.exec(); } |
Результат выполнения программы:
Заключение
На следующем уроке мы рассмотрим набор классов для управления макетом нашего приложения.
Поясните за последний пример, вроде TextEdit ничем не инициализируется но на скрине написано «Quit Application»
можно рассказать о двух строчках из чек бокса
зачем в коде statusBar(); по документации он возвращает пустую строку состояния главного окна но как она помогает в текущем коде не ясно
и в конекте всегда писали 3 аргументом qApp а тут this с чем связанно то что в данном примере мы передаем QMainWindow
заранее спасибо
По первому вопросу:
Попробуйте закомментировать в конструкторе вызов statusBar(). Вы увидите, что теперь на старте у приложения не будет строки состояния.
(Для наглядности вместо statusBar() можете использовать statusBar()->showMessage("Status Bar"); )
По второму вопросу:
Если по простому, то соединение сигнала со слотом происходит следующим образом: connect(отправитель, сигнал_отправителя, получатель, слот_получателя). Все четыре параметра представлены в виде адресов на соответствующие объекты.
Далее, мы, находясь внутри объекта-получателя класса Checkable, пытаемся связать его слот-метод Checkable::toggleStatusbar() с внешним сигналом. А как сослаться на объект, находясь внутри него? Правильно, через указатель this.
Почему в других примерах вместо this использовали qApp? Потому что в этом случае получателем сигнала (приводящего к закрытию окна) было само приложение (объект класса QApplication), а не наш объект класса Checkable . И мы должны каким-то образом передать указатель на приложение. Для этого в Qt есть встроенная переменная под названием… qApp. Как-то так 🙂
Обратил внимание что в "плюс минус" приложении из прошлого поста тоже указатель this в коннекте и там тоже собственный слот сигнал.
Резюмируя — если слот самописный то указатель this и адрес метода слота внутри нашего класса четвертым параметром, а если слот уже есть в программе то обращаться нужно к qApp? Если все верно то спасибо разобрался.
Единственное что смущает это макрос slots после спецификатора,
он функциональный или лишь для удобства чтения? Удаление его (в моем случае) не повредило сигналам и слотам.
>>Резюмируя — если слот самописный то указатель this и адрес метода слота внутри нашего класса четвертым параметром, а если слот уже есть в программе то обращаться нужно к qApp? Если все верно…
Нет.
В качестве примера, посмотрите на объявление класса Checkable:
Как нетрудно заметить, происходит открытое наследование от класса QMainWindow. А в данном классе есть уже готовые слоты, которые перейдут и в наш класс Checkable, а именно:
Public Slots
https://doc.qt.io/qt-5/qmainwindow.html#public-slots
Мы не будем сейчас разбирать, для чего нужны вышеприведенные слоты. Важен сам факт того, что они есть. Допустим, мы захотели связать сигнал не с самописным слотом Checkable::toggleStatusbar(), а с унаследованным от QMainWindow слотом setAnimated(). Для этого напишем:
Обратите внимание, что слот setAnimated() — не самописный, но в то же время мы используем указатель this. Подумайте — почему так?
А чтобы легче думалось, советую перечитать моё предыдущее сообщение, а также этот урок:
Урок №121. Скрытый указатель *this
https://ravesli.com/urok-121-skrytyj-ukazatel-this/
Вот смотрю на примеры, в частности на последний, и у меня возникает недоумение…
Вот создаются в конструкторе окна объекты QToolbar и QMenu(точнее указатели на них), но при создании, им не передается это окно как владелец/родитель(this)(явной привязки этих объектов к главному окну нет), им вообще никакой владелец не передается при создании. Как после сборки, они оказываются частью главного окна?
>>Вот создаются в конструкторе окна объекты QToolbar и QMenu […]. Как после сборки, они оказываются частью главного окна?
Насчет QMenu офф. документация по Qt говорит следующее:
"QMenuBar *QMainWindow::menuBar() const
If you want all windows in a Mac application to share one menu bar, don't use this function to create it, because (—->)the menu bar created here will have this QMainWindow as its parent(<—-). Instead, you must create a menu bar that does not have a parent, which you can then share among all the Mac windows. Create a parent-less menu bar this way:
QMenuBar *menuBar = new QMenuBar(nullptr);"
Т.е., для меню, созданного через вызов функции menuBar(), родитель назначается автоматически.
Для понимания, при создании QAction амперсанд "&" указывает на букву для быстрого вызова этого пункта меню. Т.е. если мы сделает вот так QAction("F&ile") — тогда быстрая буква будет на "i", если вот так QAction("&File") — тогда быстрая буква будет на "f". Может быть я невнимательно читал и не нашел тут упоминания об этом, и полей в нете искать причину этого амперсанда)
Извините, когда пробовал у себя anothermenu картинки около пунктов меню не появлялись, хотя код точная — копия вашего, версия — qt 5.13.0, помогите, пожалуйста
У меня было так же, я просто указал прямой путь к другим иконкам, подумал может он не видит эти иконки. Как только указал всё заработало, вот пример:
Как уже верно заметили выше. попробуйте прописать прямой путь к каждой иконке 🙂