Аналогичная статья на PyQt5/Python
Сегодня обсудим то, каким образом сворачивать приложение, написанное на фреймворке Qt, в трей операционной системы с помощью класса QSystemTrayIcon. Данная функция является весьма полезной для приложений, которые должны выполняться в фоновом режиме длительное время. Например, приложение видеозаписи или аудио воспроизведения.
Поэтому займёмся следующими вопросами:
- Как научить Вашу программу сворачиваться в трей;
- Как сделать контектсное меню для иконки трея Вашего приложения;
- Как отключать данную функцию, если в ней нет необходимости.
Программный код был написан в QtCreator 3.3.1 на основе Qt 5.4.1.
Структура проекта для QSystemTrayIcon
Проект создается как Приложение Qt Widgets, в котором по умолчанию создаются файлы:
- Tray.pro - профайл;
- mainwindow.h - заголовочный файл основного окна приложения;
- mainwindow.cpp - исходный код окна;
- main.cpp - основной исходный файл, с которого стартует приложение;
- mainwindow.ui - формочка основного окна приложения.
Примечание. Большую часть интерфейса создаю в дизайнере, чтобы не загромождать логику основного кода лишней информацией. По сути это лишь дело вкуса и привычки.
mainwindow.ui
Формочка окна для проверки трея Для теста создадим простую и ничем не примечательную формочку с чек-боксом.
Название объекта QCheckBox следующее - trayCheckBox
Tray.pro
Данный файл оставляем с настройками по умолчанию.
#------------------------------------------------- # # Project created by QtCreator 2015-08-10T17:18:30 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = Tray TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp HEADERS += mainwindow.h FORMS += mainwindow.ui
main.cpp
Данный файл также не подвергается изменениям
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
В заголовочном файле уже вносятся изменения, поскольку нам необходимо отслеживать событие закрытия окна, а также необходимо создать обработчик нажатий на иконку приложения в трее. Также в данном файле не забываем подключить все необходимые библиотеки. Иначе проект не скомпилируется.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QCloseEvent> #include <QSystemTrayIcon> #include <QAction> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); protected: /* Виртуальная функция родительского класса в нашем классе * переопределяется для изменения поведения приложения, * чтобы оно сворачивалось в трей, когда мы этого хотим */ void closeEvent(QCloseEvent * event); private slots: /* Слот, который будет принимать сигнал от события * нажатия на иконку приложения в трее */ void iconActivated(QSystemTrayIcon::ActivationReason reason); private: Ui::MainWindow * ui; /* Объявляем объект будущей иконки приложения для трея */ QSystemTrayIcon * trayIcon; }; #endif // MAINWINDOW_H
mainwindow.cpp
Вся логика работы тестируемого приложения заложена в данном классе. Приложение будет сворачиваться в трей только в том случае, если галочкой отмечен чекбокс. Вместо чекбокса может выступать переменная или любой другой способ хранения информации о необходимости работы данной функции.
иконка приложения в трее с контестным меню При этом, чтобы выйти из приложения при отмеченном чекбоксе, используется соответствующий пункт в контекстном меню.
Также, когда приложение сворачивает в трей, то всплывает соответствующее сообщение, которое информирует пользователя о данном событии.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->setWindowTitle("Tray Program"); /* Инициализируем иконку трея, устанавливаем иконку из набора системных иконок, * а также задаем всплывающую подсказку * */ trayIcon = new QSystemTrayIcon(this); trayIcon->setIcon(this->style()->standardIcon(QStyle::SP_ComputerIcon)); trayIcon->setToolTip("Tray Program" "\n" "Работа со сворачиванием программы трей"); /* После чего создаем контекстное меню из двух пунктов*/ QMenu * menu = new QMenu(this); QAction * viewWindow = new QAction(trUtf8("Развернуть окно"), this); QAction * quitAction = new QAction(trUtf8("Выход"), this); /* подключаем сигналы нажатий на пункты меню к соответсвующим слотам. * Первый пункт меню разворачивает приложение из трея, * а второй пункт меню завершает приложение * */ connect(viewWindow, SIGNAL(triggered()), this, SLOT(show())); connect(quitAction, SIGNAL(triggered()), this, SLOT(close())); menu->addAction(viewWindow); menu->addAction(quitAction); /* Устанавливаем контекстное меню на иконку * и показываем иконку приложения в трее * */ trayIcon->setContextMenu(menu); trayIcon->show(); /* Также подключаем сигнал нажатия на иконку к обработчику * данного нажатия * */ connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); } MainWindow::~MainWindow() { delete ui; } /* Метод, который обрабатывает событие закрытия окна приложения * */ void MainWindow::closeEvent(QCloseEvent * event) { /* Если окно видимо и чекбокс отмечен, то завершение приложения * игнорируется, а окно просто скрывается, что сопровождается * соответствующим всплывающим сообщением */ if(this->isVisible() && ui->trayCheckBox->isChecked()){ event->ignore(); this->hide(); QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(QSystemTrayIcon::Information); trayIcon->showMessage("Tray Program", trUtf8("Приложение свернуто в трей. Для того чтобы, " "развернуть окно приложения, щелкните по иконке приложения в трее"), icon, 2000); } } /* Метод, который обрабатывает нажатие на иконку приложения в трее * */ void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason){ case QSystemTrayIcon::Trigger: /* Событие игнорируется в том случае, если чекбокс не отмечен * */ if(ui->trayCheckBox->isChecked()){ /* иначе, если окно видимо, то оно скрывается, * и наоборот, если скрыто, то разворачивается на экран * */ if(!this->isVisible()){ this->show(); } else { this->hide(); } } break; default: break; } }
Итог
В случае успешной сборки проекта, Ваше приложение с легкостью свернётся в трей колачиком. Пример работы приложения с QSystemTrayIcon показан на следующем видео:
И то и другое можно применить для десктопного приложения, и как лучше строить выбор?
Два примера реализации системного трея с использованием QML есть вот в этом уроке: http://www.evileg.ru/baza-znanij/qt-qml-android/rabota-s-system-tray-v-qml-qt-prilozhenii.html
Добрый день.
При попытке запуска приложения в Qt Creator 4.2.0 на Qt 5.7.1 выдаёт следующие ошибки (как при ручном вводе кода, так и при копировании с сайта):
1. В файле mainwindow.cpp :
Помогло добавление директивы
Спасибо за урок!
Видимо, когда я работал с этим кодом, QMenu подключался в заголовочнике QSystemTrayIcon , поэтому и сработало нормально. В любом случае ваше решение верное. Внесу поправку, что если у Вас нет объявлений QMenu в заголовочнике, то лучше перенеси include в cpp файл.
Спасибо! А чем ваш вариант лучше? Скажу сразу, что не совсем хорошо разбираюсь в C++ (школьная программа была пройдена, - в институте немного "подзабил")), но вроде при инклюде заголовочного файла с объявленным QMenu, например в другой .cpp, в нём тоже всё будет работать, иначе для каждого cpp-файла QMenu будет подключаться отдельно - выходит, это более оптимально?
Это к вопросу о скорости компиляции проекта. В маленьких проектах это не очень заметно, а в крупных становится очевидным.
Если всё подключать в заголовочниках, то много времени тратится на проверку того, был ли уже тот или иной заголовочник подключён в в конкретном заголовочном файле. А если стараться подключать заголовочные файлы в файле исходных кодов, то количество таких проверок снижается, соответственно уменьшается время на сборку проекта.
Также есть ещё один хороший способ увеличить скорость компиляции, если объект какого-либо класса, например того же самого QMenu объявляется как указатель в заголовочном файле, то можно объявить класс QMenu, а заголовочный файл подключить уже в файле исходных кодов.
Это будет выглядеть следующим образом:
widget.h widget.cppУ кого при компиляции ворненги - отредактируйте код таким образом, чтобы trUtf8() не было, а было просто tr()
Здравствуйте, извиняюсь за вопрос немного не по теме урока, а скорее по общему синтаксису Qt, связанному с активным использованием указателей.
В частности в вашем примере, разве new QMenu, QAction и QSystemTrayIconnew без их последующего delete не должно приводить к утечке памяти?
Добрый день!