Реклама

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

РуководствоQtQSystemTrayIcon, Tray, qt, трей2367

Аналогичная статья на 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 показан на следующем видео:

Реклама

Комментарии

  • #
  • 17 апреля 2016 г. 2: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);
}

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
Последние комментарии
  • EVILEG
  • 24 мая 2017 г. 15:12

Как написать игру на Qt - Урок 3. Взаимодействие с другими объектами

Вот теперь, это будет правильнее. А теперь ответьте сами себе на вопрос. Много ли начинающих программистов, которые прочитали эту статью разбираются в приведении типов и множестве других нюанс...

Как написать игру на Qt - Урок 3. Взаимодействие с другими объектами

А если применить приведение типов? Enemy01 *itemEnemy01 = dynamic_cast (item); void Widget::slotDeleteApple(QGraphicsItem *item) { Apple *check = dynamic_cast<Apple *>...

  • EVILEG
  • 24 мая 2017 г. 14:35

Как написать игру на Qt - Урок 3. Взаимодействие с другими объектами

void Widget::slotDeleteApple(QGraphicsItem *item) { if(apple == item) { scene->removeItem(apple); delete apple; ui->lcdNumber->display(count+...

Как написать игру на Qt - Урок 3. Взаимодействие с другими объектами

При столкновении объектов отсылается сигнал signalCheckItem, который содержит указатель на объект. В ядре игры проводится проверка. Если объект == apple, то выполняется удаление объекта. vo...

  • EVILEG
  • 24 мая 2017 г. 13:28

Qt/C++ - Урок 052. Кастомизация Qt Аудио плеера в стиле AIMP

В методах mousePressEvent, mouseMoveEvent и т.д. в этом же самом уроке показано, как определять области, в которых находится курсор мыши. Это реализовано для изменения размеров, в методе checkResiz...

Сейчас обсуждают на форуме

WinApi CBTProc

Сделать бул как у тебя?

  • Kostya
  • 26 мая 2017 г. 14:10

Всплывающие подсказки в QT

Как реализована данная штука?

Отличия в рефлектограммах соседних портов

Спасибо, за информацию.

  • Arrow
  • 24 мая 2017 г. 14:09

qmake

Похоже на то! Спасибо.