При переводе интерфейса приложения 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
- TEMPLATE = app
- QT += qml quick widgets
- SOURCES += main.cpp \
- systemtray.cpp
- RESOURCES += qml.qrc
- # Additional import path used to resolve QML modules in Qt Creator's code model
- QML_IMPORT_PATH =
- # Default rules for deployment.
- include(deployment.pri)
- HEADERS += \
- systemtray.h
main.cpp
Как и в уроке по сигналам и слотам, производим объявление и инициализацию объекта отдельного Qt/C++ класса и устанавливаем доступ к нему из слоя Qml.
- #include <QApplication>
- #include <QQmlApplicationEngine>
- #include <QQmlContext>
- #include <QSystemTrayIcon>
- #include <systemtray.h>
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QQmlApplicationEngine engine;
- // Объявляем и инициализируем объекта класса для работы с системным треем
- SystemTray * systemTray = new SystemTray();
- QQmlContext * context = engine.rootContext();
- // Устанавливаем доступ к свойствам объекта класса в контексте QML
- context->setContextProperty("systemTray", systemTray);
- engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
- return app.exec();
- }
systemtray.h
В заголовочном файле класса SystemTray объявляем сигналы, которые будут передавать информацию в QML, а также объект QSystemTrayIcon, с которым будет осуществляться взаимодействие. Помимо этого объявляем обработчик взаимодействия с данной иконкой.
- #ifndef SYSTEMTRAY_H
- #define SYSTEMTRAY_H
- #include <QObject>
- #include <QAction>
- #include <QSystemTrayIcon>
- class SystemTray : public QObject
- {
- Q_OBJECT
- public:
- explicit SystemTray(QObject *parent = 0);
- // Сигналы от системного трея
- signals:
- void signalIconActivated();
- void signalShow();
- void signalQuit();
- private slots:
- /* Слот, который будет принимать сигнал от события
- * нажатия на иконку приложения в трее
- */
- void iconActivated(QSystemTrayIcon::ActivationReason reason);
- public slots:
- void hideIconTray();
- private:
- /* Объявляем объект будущей иконки приложения для трея */
- QSystemTrayIcon * trayIcon;
- };
- #endif // SYSTEMTRAY_H
systemtray.cpp
Далее прописываем исходный код класса для работы с System Tray , но реализуем лишь подачу сигналов при взаимодействии с пунктами меню и иконкой системного трея. Логика обработки сигналов будет реализована в QML.
- #include "systemtray.h"
- #include <QMenu>
- #include <QSystemTrayIcon>
- SystemTray::SystemTray(QObject *parent) : QObject(parent)
- {
- // Создаём контекстное меню с двумя пунктами
- QMenu *trayIconMenu = new QMenu();
- QAction * viewWindow = new QAction(trUtf8("Развернуть окно"), this);
- QAction * quitAction = new QAction(trUtf8("Выход"), this);
- /* подключаем сигналы нажатий на пункты меню к соответсвующим сигналам для QML.
- * */
- connect(viewWindow, &QAction::triggered, this, &SystemTray::signalShow);
- connect(quitAction, &QAction::triggered, this, &SystemTray::signalQuit);
- trayIconMenu->addAction(viewWindow);
- trayIconMenu->addAction(quitAction);
- /* Инициализируем иконку трея, устанавливаем иконку,
- * а также задаем всплывающую подсказку
- * */
- trayIcon = new QSystemTrayIcon();
- trayIcon->setContextMenu(trayIconMenu);
- trayIcon->setIcon(QIcon(":/logo-min.png"));
- trayIcon->show();
- trayIcon->setToolTip("Tray Program" "\n"
- "Работа со сворачиванием программы трей");
- /* Также подключаем сигнал нажатия на иконку к обработчику
- * данного нажатия
- * */
- connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
- this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
- }
- /* Метод, который обрабатывает нажатие на иконку приложения в трее
- * */
- void SystemTray::iconActivated(QSystemTrayIcon::ActivationReason reason)
- {
- switch (reason){
- case QSystemTrayIcon::Trigger:
- // В случае сигнала нажатия на иконку трея вызываем сигнал в QML слой
- emit signalIconActivated();
- break;
- default:
- break;
- }
- }
- void SystemTray::hideIconTray()
- {
- trayIcon->hide();
- }
main.qml
Для получения доступа к свойствам объекта класса SystemTray в Qml слое прописываем объект Connections, через которое осуществляется подключение к объекту SystemTray. в свойстве target прописываем имя, которое было объявлено в файле main.cpp, когда в движок Qml устанавливался доступ к объекту системного трея через метод setContextProperty().
- import QtQuick 2.5
- import QtQuick.Controls 1.4
- import QtQuick.Window 2.0
- ApplicationWindow {
- id: application
- visible: true
- width: 640
- height: 480
- title: qsTr("Hello World")
- // Переменная для игнорирования чекбокса
- property bool ignoreCheck: false
- /* С помощью объекта Connections
- * Устанавливаем соединение с классом системного трея
- * */
- Connections {
- target: systemTray
- // Сигнал - показать окно
- onSignalShow: {
- application.show();
- }
- // Сигнал - закрыть приложения игнорируя чек-бокс
- onSignalQuit: {
- ignoreCheck = true
- close();
- }
- // Свернуть/развернуть окно через клик по системному трею
- onSignalIconActivated: {
- if(application.visibility === Window.Hidden) {
- application.show()
- } else {
- application.hide()
- }
- }
- }
- // Тестовый чекбокс для управления закрытием окна
- CheckBox {
- id: checkTray
- anchors.centerIn: parent
- text: qsTr("Включить сворачивание в системный трей при нажатии кнопки закрытия окна")
- }
- // Обработчик события закрытия окна
- onClosing: {
- /* Если чекбокс не должен игнорироваться и он активен,
- * то скрываем приложение.
- * В противном случае закрываем приложение
- * */
- if(checkTray.checked === true && ignoreCheck === false){
- close.accepted = false
- application.hide()
- } else {
- // Завершаем приложение
- Qt.quit()
- }
- }
- }
Второй вариант
Ну а теперь приступим ко второму варианту реализации, который написан в соавторстве с Константином Ляшкевичем.
Структура проекта
В данном случае структура проекта будет состоять только из:
- QmlSystemTray.pro - профайл проекта;
- main.cpp - основной файл исходных кодов для запуска приложения;
- main.qml - файл с главным окном приложения;
- logo-min.png - любая иконка, которая будет помещена в системный трей.
QmlSystemTray_2.pro
В данном случае рекомендую обратить внимание на модули, которые подключаются в проекте. Поскольку без модуля quickwidgets обойтись не получится.
- TEMPLATE = app
- QT += qml quick widgets quickwidgets
- SOURCES += main.cpp
- RESOURCES += qml.qrc
- # Additional import path used to resolve QML modules in Qt Creator's code model
- QML_IMPORT_PATH =
- # Default rules for deployment.
- include(deployment.pri)
main.cpp
Также необходимо подключить библиотеку QQuickWidget в исходник файла main.cpp. Это необходимо для использования функции qmlRegisterType.
- #include <QApplication>
- #include <QQmlApplicationEngine>
- #include <QIcon>
- #include <QQuickWidget>
- #include <QSystemTrayIcon>
- #include <QQmlContext>
- // Объявляем пользовательский тип данных для работы с иконкой в QML
- Q_DECLARE_METATYPE(QSystemTrayIcon::ActivationReason)
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QQmlApplicationEngine engine; // Инициализируем Qml движок
- // Регистрируем QSystemTrayIcon в качестве типа объекта в Qml
- qmlRegisterType<QSystemTrayIcon>("QSystemTrayIcon", 1, 0, "QSystemTrayIcon");
- // Регистрируем в QML тип данных для работы с получаемыми данными при клике по иконке
- qRegisterMetaType<QSystemTrayIcon::ActivationReason>("ActivationReason");
- // Устанавливаем Иконку в контекст движка
- engine.rootContext()->setContextProperty("iconTray", QIcon(":/logo-min.png"));
- engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
- return app.exec();
- }
main.qml
Далее производим объявление объекта QSystemTrayIcon и настраиваем его в методе onCompleted. Благодаря тому, что мы зарегистрировали тип QSystemTrayIcon::ActivationReason, то в методе onActivated мы получаем возможность в зависимости от типа передаваемого значения reason определять реакцию на клики мыши по иконке приложения в системном трее. Когда мы делаем клик правой кнопкой мыши по иконке приложения в системном трее, то возникает Menu. Меню вызывается функцией popup(). Особенность функции в том, что она вызывает меню в том месте, где находится курсор мыши, поэтому меню возникает в месте нахождения иконки системного трея.
- import QtQuick 2.5
- import QtQuick.Controls 1.4
- import QtQuick.Window 2.0
- import QSystemTrayIcon 1.0
- ApplicationWindow {
- id: application
- visible: true
- width: 640
- height: 480
- title: qsTr("Hello World")
- // Зарегистрированный системный трей в QML слое
- QSystemTrayIcon {
- id: systemTray
- // Первоначальная инициализация системного трея
- Component.onCompleted: {
- icon = iconTray // Устанавливаем иконку
- // Задаём подсказку для иконки трея
- toolTip = "Tray Program
- Работа со сворачиванием программы трей"
- show();
- }
- /* По клику на иконку трея определяем,
- * левой или правой кнопкой мыши был клик.
- * Если левой, то скрываем или открываем окно приложения.
- * Если правой, то открываем меню системного трея
- * */
- onActivated: {
- if(reason === 1){
- trayMenu.popup()
- } else {
- if(application.visibility === Window.Hidden) {
- application.show()
- } else {
- application.hide()
- }
- }
- }
- }
- // Меню системного трея
- Menu {
- id: trayMenu
- MenuItem {
- text: qsTr("Развернуть окно")
- onTriggered: application.show()
- }
- MenuItem {
- text: qsTr("Выход")
- onTriggered: {
- systemTray.hide()
- Qt.quit()
- }
- }
- }
- // Тестовый чекбокс для управления закрытием окна
- CheckBox {
- id: checkTray
- anchors.centerIn: parent
- text: qsTr("Включить сворачивание в системный трей при нажатии кнопки закрытия окна")
- }
- // Обработчик события закрытия окна
- onClosing: {
- /* Если чекбокс не должен игнорироваться и он активен,
- * то скрываем приложение.
- * В противном случае закрываем приложение
- * */
- if(checkTray.checked === true){
- close.accepted = false
- application.hide()
- } else {
- // Завершаем приложение
- Qt.quit()
- }
- }
- }
Итог
В результате проделанной работы, Вы получите приложение, которое будет сворачиваться в трей как по кликам по иконке в системной трее, так и по нажатию кнопки закрытия окна приложения. Выглядеть оно будет, как на нижеследующем рисунке. Ну а демонстрацию работы приложения Вы можете увидеть в видеоуроке, где объясняется оба варианта программного кода
Соавтор статьи: Константин Ляшкевич
Здравствуйте. В первом варианте не выложен файл QmlSystemTray.pro без него как-то не полно.
Добрый день. Добавил QmlSystemTray.pro.
Спасибо)