Evgenii Legotckoi
1 ноября 2015 г. 23:39

QML - Урок 008. Работа с System Tray в Qml Qt приложении

При переводе интерфейса приложения c Qt/C++ на Qt/QML мои руки добрались и до иконки приложения в системном трее. Задача заключалась в том, чтобы перевести иконку в System Tray с C++ на Qml, частично или полностью. Первый вариант, который я реализовал, являлся обёрткой вокруг QSystemTrayIcon с QMenu с использованием системы сигналов и слотов . Решение вполне логичное, учитывая, что в QML нет готового объекта, наподобие MenuBar для System Tray .   Поэтому делаем обёртку, с которой можно взаимодействовать из QML слоя.

После того, как обёртка была реализована, мне довелось посоветоваться с программистом из Wargamming Константином Ляшкевичем , который порекомендовал мне также обратить внимание на то, что QML может иметь доступ не только к сигналам и слотам, но и к параметрам Q_PROPERTY, которые также имелись в классе QSystemTrayIcon, то есть фактически можно было только зарегистрировать данный класс, как тип в QML слое и попытаться написать практически весь код на QML. Я проверил данный совет и рассказал о результате Константину. В итоге он сам заинтересовался данной задачей и Мы потратили вечерний час на занимательное костылирование и совместными усилиями запихали QSystemTrayIcon максимально в QML.

Таким образом в данной статье Вы увидите две реализации для работы с иконкой в системном трее.

Полученное приложение будет сворачиваться в System Tray по клику по иконке в трее, а также по нажатию кнопки закрытия окна. Но только в том случае, если будет активен специальный чекбокс, для контроля процесса сворачивания окна приложения в трей, если же чекбокс не активен, то приложение будет закрываться. Также приложение можно будет закрыть при активном чекбоксе через пункт меню в иконке системного трея.


Первый вариант

Вариант для работы с системным треем через класс обёртку.

Структура проекта для работы с System Tray

В проект входят следующие файлы:

  • QmlSystemTray.pro - профайл проекта;
  • main.cpp - основной файл исходных кодов для запуска приложения;
  • systemtray.h - заголовочный файл класса для работы с системным треем;
  • systemtray.cpp - файл исходных кодов класса для работы с системным треем;
  • main.qml - файл с главным окном приложения;
  • logo-min.png - любая иконка, которая будет помещена в системный трей.

QmlSystemTray.pro

  1. TEMPLATE = app
  2.  
  3. QT += qml quick widgets
  4.  
  5. SOURCES += main.cpp \
  6. systemtray.cpp
  7.  
  8. RESOURCES += qml.qrc
  9.  
  10. # Additional import path used to resolve QML modules in Qt Creator's code model
  11. QML_IMPORT_PATH =
  12.  
  13. # Default rules for deployment.
  14. include(deployment.pri)
  15.  
  16. HEADERS += \
  17. systemtray.h

main.cpp

Как и в уроке по сигналам и слотам, производим объявление и инициализацию объекта отдельного Qt/C++ класса и устанавливаем доступ к нему из слоя Qml.

  1. #include <QApplication>
  2. #include <QQmlApplicationEngine>
  3. #include <QQmlContext>
  4. #include <QSystemTrayIcon>
  5.  
  6. #include <systemtray.h>
  7.  
  8. int main(int argc, char *argv[])
  9. {
  10. QApplication app(argc, argv);
  11.  
  12. QQmlApplicationEngine engine;
  13.  
  14. // Объявляем и инициализируем объекта класса для работы с системным треем
  15. SystemTray * systemTray = new SystemTray();
  16. QQmlContext * context = engine.rootContext();
  17. // Устанавливаем доступ к свойствам объекта класса в контексте QML
  18. context->setContextProperty("systemTray", systemTray);
  19.  
  20. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  21.  
  22. return app.exec();
  23. }

systemtray.h

В заголовочном файле класса SystemTray объявляем сигналы, которые будут передавать информацию в QML, а также объект QSystemTrayIcon, с которым будет осуществляться взаимодействие. Помимо этого объявляем обработчик взаимодействия с данной иконкой.

  1. #ifndef SYSTEMTRAY_H
  2. #define SYSTEMTRAY_H
  3.  
  4. #include <QObject>
  5. #include <QAction>
  6. #include <QSystemTrayIcon>
  7.  
  8. class SystemTray : public QObject
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit SystemTray(QObject *parent = 0);
  13.  
  14. // Сигналы от системного трея
  15. signals:
  16. void signalIconActivated();
  17. void signalShow();
  18. void signalQuit();
  19.  
  20. private slots:
  21. /* Слот, который будет принимать сигнал от события
  22. * нажатия на иконку приложения в трее
  23. */
  24. void iconActivated(QSystemTrayIcon::ActivationReason reason);
  25.  
  26. public slots:
  27. void hideIconTray();
  28.  
  29. private:
  30. /* Объявляем объект будущей иконки приложения для трея */
  31. QSystemTrayIcon * trayIcon;
  32. };
  33.  
  34. #endif // SYSTEMTRAY_H

systemtray.cpp

Далее прописываем исходный код класса для работы с System Tray , но реализуем лишь подачу сигналов при взаимодействии с пунктами меню и иконкой системного трея. Логика обработки сигналов будет реализована в QML.

  1. #include "systemtray.h"
  2. #include <QMenu>
  3. #include <QSystemTrayIcon>
  4.  
  5. SystemTray::SystemTray(QObject *parent) : QObject(parent)
  6. {
  7.  
  8. // Создаём контекстное меню с двумя пунктами
  9. QMenu *trayIconMenu = new QMenu();
  10.  
  11. QAction * viewWindow = new QAction(trUtf8("Развернуть окно"), this);
  12. QAction * quitAction = new QAction(trUtf8("Выход"), this);
  13.  
  14. /* подключаем сигналы нажатий на пункты меню к соответсвующим сигналам для QML.
  15. * */
  16. connect(viewWindow, &QAction::triggered, this, &SystemTray::signalShow);
  17. connect(quitAction, &QAction::triggered, this, &SystemTray::signalQuit);
  18.  
  19. trayIconMenu->addAction(viewWindow);
  20. trayIconMenu->addAction(quitAction);
  21.  
  22. /* Инициализируем иконку трея, устанавливаем иконку,
  23. * а также задаем всплывающую подсказку
  24. * */
  25. trayIcon = new QSystemTrayIcon();
  26. trayIcon->setContextMenu(trayIconMenu);
  27. trayIcon->setIcon(QIcon(":/logo-min.png"));
  28. trayIcon->show();
  29. trayIcon->setToolTip("Tray Program" "\n"
  30. "Работа со сворачиванием программы трей");
  31.  
  32. /* Также подключаем сигнал нажатия на иконку к обработчику
  33. * данного нажатия
  34. * */
  35. connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
  36. this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
  37. }
  38.  
  39. /* Метод, который обрабатывает нажатие на иконку приложения в трее
  40. * */
  41. void SystemTray::iconActivated(QSystemTrayIcon::ActivationReason reason)
  42. {
  43. switch (reason){
  44. case QSystemTrayIcon::Trigger:
  45. // В случае сигнала нажатия на иконку трея вызываем сигнал в QML слой
  46. emit signalIconActivated();
  47. break;
  48. default:
  49. break;
  50. }
  51. }
  52.  
  53. void SystemTray::hideIconTray()
  54. {
  55. trayIcon->hide();
  56. }

main.qml

Для получения доступа к свойствам объекта класса SystemTray в Qml слое прописываем объект Connections, через которое осуществляется подключение к объекту SystemTray. в свойстве target прописываем имя, которое было объявлено в файле main.cpp, когда в движок Qml устанавливался доступ к объекту системного трея через метод setContextProperty().

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3. import QtQuick.Window 2.0
  4.  
  5. ApplicationWindow {
  6. id: application
  7. visible: true
  8. width: 640
  9. height: 480
  10. title: qsTr("Hello World")
  11.  
  12. // Переменная для игнорирования чекбокса
  13. property bool ignoreCheck: false
  14.  
  15. /* С помощью объекта Connections
  16. * Устанавливаем соединение с классом системного трея
  17. * */
  18. Connections {
  19. target: systemTray
  20. // Сигнал - показать окно
  21. onSignalShow: {
  22. application.show();
  23. }
  24.  
  25. // Сигнал - закрыть приложения игнорируя чек-бокс
  26. onSignalQuit: {
  27. ignoreCheck = true
  28. close();
  29. }
  30.  
  31. // Свернуть/развернуть окно через клик по системному трею
  32. onSignalIconActivated: {
  33. if(application.visibility === Window.Hidden) {
  34. application.show()
  35. } else {
  36. application.hide()
  37. }
  38. }
  39. }
  40.  
  41. // Тестовый чекбокс для управления закрытием окна
  42. CheckBox {
  43. id: checkTray
  44. anchors.centerIn: parent
  45. text: qsTr("Включить сворачивание в системный трей при нажатии кнопки закрытия окна")
  46. }
  47.  
  48. // Обработчик события закрытия окна
  49. onClosing: {
  50. /* Если чекбокс не должен игнорироваться и он активен,
  51. * то скрываем приложение.
  52. * В противном случае закрываем приложение
  53. * */
  54. if(checkTray.checked === true && ignoreCheck === false){
  55. close.accepted = false
  56. application.hide()
  57. } else {
  58. // Завершаем приложение
  59. Qt.quit()
  60. }
  61. }
  62. }

Второй вариант

Ну а теперь приступим ко второму варианту реализации, который написан в соавторстве с Константином Ляшкевичем.

Структура проекта

В данном случае структура проекта будет состоять только из:

    • QmlSystemTray.pro - профайл проекта;
  • main.cpp - основной файл исходных кодов для запуска приложения;
  • main.qml - файл с главным окном приложения;
  • logo-min.png - любая иконка, которая будет помещена в системный трей.

QmlSystemTray_2.pro

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

  1. TEMPLATE = app
  2.  
  3. QT += qml quick widgets quickwidgets
  4.  
  5. SOURCES += main.cpp
  6.  
  7. RESOURCES += qml.qrc
  8.  
  9. # Additional import path used to resolve QML modules in Qt Creator's code model
  10. QML_IMPORT_PATH =
  11.  
  12. # Default rules for deployment.
  13. include(deployment.pri)

main.cpp

Также необходимо подключить библиотеку QQuickWidget в исходник файла main.cpp. Это необходимо для использования функции qmlRegisterType.

  1. #include <QApplication>
  2. #include <QQmlApplicationEngine>
  3. #include <QIcon>
  4. #include <QQuickWidget>
  5. #include <QSystemTrayIcon>
  6. #include <QQmlContext>
  7.  
  8. // Объявляем пользовательский тип данных для работы с иконкой в QML
  9. Q_DECLARE_METATYPE(QSystemTrayIcon::ActivationReason)
  10.  
  11. int main(int argc, char *argv[])
  12. {
  13. QApplication app(argc, argv);
  14. QQmlApplicationEngine engine; // Инициализируем Qml движок
  15.  
  16. // Регистрируем QSystemTrayIcon в качестве типа объекта в Qml
  17. qmlRegisterType<QSystemTrayIcon>("QSystemTrayIcon", 1, 0, "QSystemTrayIcon");
  18. // Регистрируем в QML тип данных для работы с получаемыми данными при клике по иконке
  19. qRegisterMetaType<QSystemTrayIcon::ActivationReason>("ActivationReason");
  20. // Устанавливаем Иконку в контекст движка
  21. engine.rootContext()->setContextProperty("iconTray", QIcon(":/logo-min.png"));
  22. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  23.  
  24. return app.exec();
  25. }

main.qml

Далее производим объявление объекта QSystemTrayIcon и настраиваем его в методе onCompleted. Благодаря тому, что мы зарегистрировали тип QSystemTrayIcon::ActivationReason, то в методе onActivated мы получаем возможность в зависимости от типа передаваемого значения reason определять реакцию на клики мыши по иконке приложения в системном трее. Когда мы делаем клик правой кнопкой мыши по иконке приложения в системном трее, то возникает Menu. Меню вызывается функцией popup(). Особенность функции в том, что она вызывает меню в том месте, где находится курсор мыши, поэтому меню возникает в месте нахождения иконки системного трея.

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3. import QtQuick.Window 2.0
  4. import QSystemTrayIcon 1.0
  5.  
  6. ApplicationWindow {
  7. id: application
  8. visible: true
  9. width: 640
  10. height: 480
  11. title: qsTr("Hello World")
  12.  
  13. // Зарегистрированный системный трей в QML слое
  14. QSystemTrayIcon {
  15. id: systemTray
  16.  
  17. // Первоначальная инициализация системного трея
  18. Component.onCompleted: {
  19. icon = iconTray // Устанавливаем иконку
  20. // Задаём подсказку для иконки трея
  21. toolTip = "Tray Program
  22. Работа со сворачиванием программы трей"
  23. show();
  24. }
  25.  
  26. /* По клику на иконку трея определяем,
  27. * левой или правой кнопкой мыши был клик.
  28. * Если левой, то скрываем или открываем окно приложения.
  29. * Если правой, то открываем меню системного трея
  30. * */
  31. onActivated: {
  32. if(reason === 1){
  33. trayMenu.popup()
  34. } else {
  35. if(application.visibility === Window.Hidden) {
  36. application.show()
  37. } else {
  38. application.hide()
  39. }
  40. }
  41. }
  42. }
  43.  
  44. // Меню системного трея
  45. Menu {
  46. id: trayMenu
  47.  
  48. MenuItem {
  49. text: qsTr("Развернуть окно")
  50. onTriggered: application.show()
  51. }
  52.  
  53. MenuItem {
  54. text: qsTr("Выход")
  55. onTriggered: {
  56. systemTray.hide()
  57. Qt.quit()
  58.  
  59. }
  60. }
  61. }
  62.  
  63. // Тестовый чекбокс для управления закрытием окна
  64. CheckBox {
  65. id: checkTray
  66. anchors.centerIn: parent
  67. text: qsTr("Включить сворачивание в системный трей при нажатии кнопки закрытия окна")
  68. }
  69.  
  70. // Обработчик события закрытия окна
  71. onClosing: {
  72. /* Если чекбокс не должен игнорироваться и он активен,
  73. * то скрываем приложение.
  74. * В противном случае закрываем приложение
  75. * */
  76. if(checkTray.checked === true){
  77. close.accepted = false
  78. application.hide()
  79. } else {
  80. // Завершаем приложение
  81. Qt.quit()
  82. }
  83. }
  84. }

Итог

В результате проделанной работы, Вы получите приложение, которое будет сворачиваться в трей как по кликам по иконке в системной трее, так и по нажатию кнопки закрытия окна приложения. Выглядеть оно будет, как на нижеследующем рисунке. Ну а демонстрацию работы приложения Вы можете увидеть в видеоуроке, где объясняется оба варианта программного кода

Соавтор статьи: Константин Ляшкевич

Видеоурок

Вам это нравится? Поделитесь в социальных сетях!

o
  • 4 апреля 2017 г. 3:13

Здравствуйте. В первом варианте не выложен файл QmlSystemTray.pro без него как-то не полно.

Evgenii Legotckoi
  • 4 апреля 2017 г. 12:33

Добрый день. Добавил QmlSystemTray.pro.

o
  • 4 апреля 2017 г. 12:34

Спасибо)

Комментарии

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