Evgenii Legotckoi
Evgenii Legotckoi12 серпня 2015 р. 12:27

Qt/C++ - Урок 002. QSystemTrayIcon - Як згорнути програму в системний трей?

Аналогічна стаття на 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 хостинг.

Вам це подобається? Поділіться в соціальних мережах!

BlinCT
  • 16 квітня 2016 р. 16:14
Вопрос такой, на сколько большая разница в реализации трея на Qt или на QML как у вас есть еще такое видео?
И то и другое можно применить для десктопного приложения, и как лучше строить выбор?
Evgenii Legotckoi
  • 16 квітня 2016 р. 23:40
В QML нет собственного типа для системного трея. Поэтому нужно регистрировать через qmlRegisterType сам QSystemTrayIcon, либо класс, в который обёрнут QSystemTrayIcon.
Два примера реализации системного трея с использованием QML есть вот в этом уроке: http://www.evileg.ru/baza-znanij/qt-qml-android/rabota-s-system-tray-v-qml-qt-prilozhenii.html
СС
  • 21 лютого 2017 р. 00:00

Добрый день.
При попытке запуска приложения в 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>


Спасибо за урок!
Evgenii Legotckoi
  • 21 лютого 2017 р. 00:16

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

СС
  • 22 лютого 2017 р. 00:41

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

Evgenii Legotckoi
  • 22 лютого 2017 р. 01:38

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

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

Также есть ещё один хороший способ увеличить скорость компиляции, если объект какого-либо класса, например того же самого 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);
}
ДЧ
  • 09 червня 2017 р. 09:07

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

x
  • 18 квітня 2018 р. 08:36

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

Evgenii Legotckoi
  • 18 квітня 2018 р. 10:24

Добрый день!

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах