© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

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

QSystemTrayIcon, Qt, QML

При переводе интерфейса приложения 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()
        }
    }
}

Итог

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

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

Видеоурок

Комментарии

3 апреля 2017 г. 21:13

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

4 апреля 2017 г. 6:33

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

Для Django рекомендую VDS-хостинг TIMEWEB

4 апреля 2017 г. 6:34

Спасибо)

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
15 октября 2018 г. 21:36
Allyans .

C++ - Тест 001. Первая программа и типы данных

  • Результат 60баллов,
  • Очки рейтинга-1
15 октября 2018 г. 11:25
Екатерина Самойлова

C++ - Тест 002. Константы

  • Результат 33баллов,
  • Очки рейтинга-10
15 октября 2018 г. 11:17
Екатерина Самойлова

C++ - Тест 006. Перечисления

  • Результат 80баллов,
  • Очки рейтинга4
Последние комментарии
16 октября 2018 г. 16:14
pasagir

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

В Qt 5.11. при попытке вставить в БД запись выдает ошибку QSqlQuery::prepare: database not openQSqlDatabasePrivate::database: requested database does not belong to the calling thread. ...
10 октября 2018 г. 9:50
Евгений Легоцкой

Qt/C++ - Урок 083. Создание динамической библиотеки и подключение её в другой проект

Если и начинать писать о плагинах, то нужно тогда с Qt Creator начинать, там наверняка будет одинаковый принцип, но по Qt Creator хотя бы информация есть.
10 октября 2018 г. 9:48
ost.vld

Qt/C++ - Урок 083. Создание динамической библиотеки и подключение её в другой проект

О плагинах к QtCreator в целом, тоже интересно.
10 октября 2018 г. 9:46
ost.vld

Qt/C++ - Урок 083. Создание динамической библиотеки и подключение её в другой проект

ну типа того, создание программы, функционал которой можно расширять плагинами, и, в перспективе, создание API.
Сейчас обсуждают на форуме
15 октября 2018 г. 12:45
Allyans .

QGraphicsItem change color

Хорошо)
11 октября 2018 г. 10:13
Arrow

Работа с WebView в QML

Нашел в чем проблема. Пишу на случай если кому-то попадется такое же счастье с WebView как и мне. Проблема как оказалась с Debug версией, так как в Release и Profile все работает (...
10 октября 2018 г. 12:49
Виталий Антипов

Кто что делает на Qt?

Работаем по локальной сети. Файл базы, схемы и фото лежат на сервере. Чтобы не было проблем при одновременной работе с одним файлом, все запросы обернул в транзакции, как указано в документаци...
10 октября 2018 г. 11:21
Arrow

Редактирование записи на форуме

Добрый день! К сожалению встретил небольшой баг на форуме. При создании записи на форуме и вставке кода, через соответствующую кнопку номера строк проставляются верно, но каждый...
10 октября 2018 г. 9:46
Arrow

Настройка Qt Creator для Android

Я Genymotion  ставил с VBox в комплекте для личного использования, после добавил из списка телефон с нужным API. Запустил его и при компиляции Qt Creator сам нашел его и все запустилось...
Присоединяйтесь к нам в социальных сетях