При перекладі інтерфейсу програми 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.
Спасибо)