Qt/C++ - Урок 002. QSystemTrayIcon или Как свернуть приложение в трей?

QSystemTrayIcon, Tray, qt, трей

Аналогичная статья на PyQt5/Python

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

Поэтому займёмся следующими вопросами:

  • Как научить Вашу программу сворачиваться в трей;
  • Как сделать контектсное меню для иконки трея Вашего приложения;
  • Как отключать данную функцию, если в ней нет необходимости.

Программный код был написан в QtCreator 3.3.1 на основе Qt 5.4.1.

Структура проекта для QSystemTrayIcon

Проект создается как Приложение Qt Widgets, в котором по умолчанию создаются файлы:

  • Tray.pro - профайл;
  • mainwindow.h - заголовочный файл основного окна приложения;
  • mainwindow.cpp - исходный код окна;
  • main.cpp - основной исходный файл, с которого стартует приложение;
  • mainwindow.ui - формочка основного окна приложения.

Примечание. Большую часть интерфейса создаю в дизайнере, чтобы не загромождать логику основного кода лишней информацией. По сути это лишь дело вкуса и привычки.

mainwindow.ui

Формочка окна для проверки трея

Для теста создадим простую и ничем не примечательную формочку с чек-боксом.

Название объекта QCheckBox следующее - trayCheckBox

Tray.pro

Данный файл оставляем с настройками по умолчанию.

#-------------------------------------------------
#
# Project created by QtCreator 2015-08-10T17:18:30
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = Tray
TEMPLATE = app


SOURCES += main.cpp\
        mainwindow.cpp

HEADERS  += mainwindow.h

FORMS    += mainwindow.ui

main.cpp

Данный файл также не подвергается изменениям

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

mainwindow.h

В заголовочном файле уже вносятся изменения, поскольку нам необходимо отслеживать событие закрытия окна, а также необходимо создать обработчик нажатий на иконку приложения в трее. Также в данном файле не забываем подключить все необходимые библиотеки. Иначе проект не скомпилируется.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QCloseEvent>
#include <QSystemTrayIcon>
#include <QAction>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected:
    /* Виртуальная функция родительского класса в нашем классе
     * переопределяется для изменения поведения приложения,
     *  чтобы оно сворачивалось в трей, когда мы этого хотим
     */
    void closeEvent(QCloseEvent * event);

private slots:
    /* Слот, который будет принимать сигнал от события
     * нажатия на иконку приложения в трее
     */
    void iconActivated(QSystemTrayIcon::ActivationReason reason);

private:
    Ui::MainWindow          * ui;
    /* Объявляем объект будущей иконки приложения для трея */
    QSystemTrayIcon         * trayIcon;
};

#endif // MAINWINDOW_H

mainwindow.cpp

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

иконка приложения в трее с контестным меню

При этом, чтобы выйти из приложения при отмеченном чекбоксе, используется соответствующий пункт в контекстном меню.

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

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle("Tray Program");

    /* Инициализируем иконку трея, устанавливаем иконку из набора системных иконок,
     * а также задаем всплывающую подсказку
     * */
    trayIcon = new QSystemTrayIcon(this);
    trayIcon->setIcon(this->style()->standardIcon(QStyle::SP_ComputerIcon));
    trayIcon->setToolTip("Tray Program" "\n"
                         "Работа со сворачиванием программы трей");
    /* После чего создаем контекстное меню из двух пунктов*/
    QMenu * menu = new QMenu(this);
    QAction * viewWindow = new QAction(trUtf8("Развернуть окно"), this);
    QAction * quitAction = new QAction(trUtf8("Выход"), this);

    /* подключаем сигналы нажатий на пункты меню к соответсвующим слотам.
     * Первый пункт меню разворачивает приложение из трея,
     * а второй пункт меню завершает приложение
     * */
    connect(viewWindow, SIGNAL(triggered()), this, SLOT(show()));
    connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));

    menu->addAction(viewWindow);
    menu->addAction(quitAction);

    /* Устанавливаем контекстное меню на иконку
     * и показываем иконку приложения в трее
     * */
    trayIcon->setContextMenu(menu);
    trayIcon->show();

    /* Также подключаем сигнал нажатия на иконку к обработчику
     * данного нажатия
     * */
    connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

/* Метод, который обрабатывает событие закрытия окна приложения
 * */
void MainWindow::closeEvent(QCloseEvent * event)
{
    /* Если окно видимо и чекбокс отмечен, то завершение приложения
     * игнорируется, а окно просто скрывается, что сопровождается
     * соответствующим всплывающим сообщением
     */
    if(this->isVisible() && ui->trayCheckBox->isChecked()){
        event->ignore();
        this->hide();
        QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(QSystemTrayIcon::Information);

        trayIcon->showMessage("Tray Program",
                              trUtf8("Приложение свернуто в трей. Для того чтобы, "
                                     "развернуть окно приложения, щелкните по иконке приложения в трее"),
                              icon,
                              2000);
    }
}

/* Метод, который обрабатывает нажатие на иконку приложения в трее
 * */
void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason){
    case QSystemTrayIcon::Trigger:
        /* Событие игнорируется в том случае, если чекбокс не отмечен
         * */
        if(ui->trayCheckBox->isChecked()){
            /* иначе, если окно видимо, то оно скрывается,
             * и наоборот, если скрыто, то разворачивается на экран
             * */
            if(!this->isVisible()){
                this->show();
            } else {
                this->hide();
            }
        }
        break;
    default:
        break;
    }
}

Итог

В случае успешной сборки проекта, Ваше приложение с легкостью свернётся в трей колачиком. Пример работы приложения с QSystemTrayIcon показан на следующем видео:

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Поддержать автора Donate
  • #
  • 16 апреля 2016 г. 16:14
Вопрос такой, на сколько большая разница в реализации трея на Qt или на QML как у вас есть еще такое видео?
И то и другое можно применить для десктопного приложения, и как лучше строить выбор?
В QML нет собственного типа для системного трея. Поэтому нужно регистрировать через qmlRegisterType сам QSystemTrayIcon, либо класс, в который обёрнут QSystemTrayIcon.
Два примера реализации системного трея с использованием QML есть вот в этом уроке: http://www.evileg.ru/baza-znanij/qt-qml-android/rabota-s-system-tray-v-qml-qt-prilozhenii.html
СС

Добрый день.
При попытке запуска приложения в Qt Creator 4.2.0 на Qt 5.7.1 выдаёт следующие ошибки (как при ручном вводе кода, так и при копировании с сайта):



1. В файле mainwindow.cpp :

E:\Proj\Qt\Tray\mainwindow.cpp:21: ошибка: invalid use of incomplete type 'class QMenu'
QMenu *menu = new QMenu(this);
^


Помогло добавление директивы в заголовок данного файла, либо (что как мне кажется более грамотно) в mainwindow.h :

#include <QMenu>


Спасибо за урок!

Видимо, когда я работал с этим кодом, QMenu подключался в заголовочнике QSystemTrayIcon , поэтому и сработало нормально. В любом случае ваше решение верное. Внесу поправку, что если у Вас нет объявлений QMenu в заголовочнике, то лучше перенеси include в cpp файл.

СС

Спасибо! А чем ваш вариант лучше? Скажу сразу, что не совсем хорошо разбираюсь в C++ (школьная программа была пройдена, - в институте немного "подзабил")), но вроде при инклюде заголовочного файла с объявленным QMenu, например в другой .cpp, в нём тоже всё будет работать, иначе для каждого cpp-файла QMenu будет подключаться отдельно - выходит, это более оптимально?

Это к вопросу о скорости компиляции проекта. В маленьких проектах это не очень заметно, а в крупных становится очевидным.

Если всё подключать в заголовочниках, то много времени тратится на проверку того, был ли уже тот или иной заголовочник подключён в в конкретном заголовочном файле. А если стараться подключать заголовочные файлы в файле исходных кодов, то количество таких проверок снижается, соответственно уменьшается время на сборку проекта.

Также есть ещё один хороший способ увеличить скорость компиляции, если объект какого-либо класса, например того же самого QMenu объявляется как указатель в заголовочном файле, то можно объявить класс QMenu, а заголовочный файл подключить уже в файле исходных кодов.

Это будет выглядеть следующим образом:

widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class QMenu;

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    Ui::Widget *ui;
    QMenu *menu;
};

#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"

#include <QMenu>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    menu = new QMenu(this);
}
ДЧ

У кого при компиляции ворненги - отредактируйте код таким образом, чтобы trUtf8() не было, а было просто tr()

x
  • #
  • 18 апреля 2018 г. 8:36

Здравствуйте, извиняюсь за вопрос немного не по теме урока, а скорее по общему синтаксису Qt, связанному с активным использованием указателей.
В частности в вашем примере, разве new QMenu, QAction и QSystemTrayIconnew без их последующего delete не должно приводить к утечке памяти?

Добрый день!

В рамках самого Qt здесь утечки не будет. Особенность фреймворка в том, что при создании объектов, которыe наследованы от QObject (подавляющее большинство классов), передаётся указатель на parent объект, как в случае с меню
new QMenu(this); // this - указатель на parent объект
Когда parent-объект будет удалён, он прихватит за собой и все объекты, которые получили указатель на parent`a. То есть автоматически подчистит память.
Если не передать указатель на парента, то утечка конечно будет. Передача указателя на парента является опциональной, можете и не передавать, если этого требует архитектура приложения, но тогда нужно будет следить за утечками.

Комментарии

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

Здравствуйте, уважаемые пользователи EVILEG !!!

Если сайт вам помог, то поддержите разработку сайта финансово, пожалуйста.

Вы можете сделать это следующими способами:

Спасибо, Евгений Легоцкой

A
6 апреля 2020 г. 5:24
Alexey

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

  • Результат:86баллов,
  • Очки рейтинга6
R
5 апреля 2020 г. 14:32
Roman

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

  • Результат:91баллов,
  • Очки рейтинга8
AP
5 апреля 2020 г. 14:31
Alexander Peshkov

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

  • Результат:60баллов,
  • Очки рейтинга-1
Последние комментарии
3 апреля 2020 г. 8:06
Konstantin Grudnitskiy

Я надеюсь вы уже разобрались в чем дело, но если вдруг нет, то проблема состоит в том, что вы пытаетесь запустить программу из интерпретатора питона. Файл main.py это уже готова…
3 апреля 2020 г. 6:18
Konstantin Grudnitskiy

>>> text = 'hello world'>>> ' '.join(word for word in text.split()[:-1])'hello'>>> def remove_last_word(text):... return text and ' '.join(word for word in text.s…
27 марта 2020 г. 14:40
Евгений Легоцкой

Добрый день. В конце пятой статьи скачать можете.
27 марта 2020 г. 14:28
mkdir _

Здравствуйте, а можно, пожалуйста, ссылку на целые исходники, если есть?
27 марта 2020 г. 4:36
Евгений Легоцкой

Скорее всего также, как и для установки всех остальных переменых в CMake, через использование set
Сейчас обсуждают на форуме
6 апреля 2020 г. 4:19
Евгений Легоцкой

Попробуй не переустанавливать материал, а просто менять цвет
6 апреля 2020 г. 3:39
Евгений Легоцкой

Добрый день. Это всё работает, вот только если есть переопределения каких-то методов, то нет гарантии, что что-то не сломается. Думаю, что нужно допиливать стилевоё оформление в прокси…
5 апреля 2020 г. 11:38
Михаиллл

Так работает windeployqt --qmldir E:\QTProject\testQmlAndCpp E:\test\build-testQmlAndCpp-Desktop_Qt_5_14_1_MinGW_32_bit-Release
5 апреля 2020 г. 2:35
Михаиллл

Так работает console.log(textEmail.text) var str = textEmail.text; var n = str.search(/^((([0-9A-Za-z]{1}[-0-9A-z\.]{1,}[0-9A-Za-z]{1})|([0-9А-Яа-я]{1}[-0-9А-я\.]{1,}[…
3 апреля 2020 г. 8:48
Intruder

Евгений, добрый день. Спасибо!
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB