Evgenii Legotckoi
Evgenii LegotckoiҚар. 1, 2015, 12:39 Т.Қ.

QML - Сабақ 008. 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

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()
        }
    }
}

Итог

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

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

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

o
  • Сәуір 3, 2017, 5:13 Т.Қ.

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

Evgenii Legotckoi
  • Сәуір 4, 2017, 2:33 Т.Ж.

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

o
  • Сәуір 4, 2017, 2:34 Т.Ж.

Спасибо)

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

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

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

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

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз