Evgenii Legotckoi
Evgenii Legotckoi23 января 2016 г. 10:48

Qt/C++ - Урок 042. PopUp уведомление в стиле Gnome с помощью Qt

Функционала стандартных уведомлений системного трея порой может не хватать для реализации смелых задумок по стилизации приложения. Поэтому рассмотрим вариант реализации всплывающего сообщения в стиле PopUp уведомления DE Gnome , а именно, как показано на ниже следующем рисунке.

PopUp уведомление в стиле Gnome

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

Сообщение будет показываться в нижнем правом углу экрана над лотком системного трея. При этом уведомление должно будет обязательно масштабироваться по содержимому.

Будет реализовано плавное появление в течении 150 миллисекунд и исчезновение, через три секунды.


Структура проекта

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

PopupWindow.pro и mainwindow.ui

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

mainwindow.h

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

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "popup.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    PopUp *popUp;       // Объявляем объект всплывающего сообщения
};

#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);
    popUp = new PopUp();

}

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

void MainWindow::on_pushButton_clicked()
{
    popUp->setPopupText(ui->textEdit->toPlainText());

    popUp->show();
}

popup.h

Для разработки уведомления необходимо наследоваться от класса QWidget, у которого необходимо отключить оформление окна, и выставить прозрачный фон. Также необходимо настроить его так, чтобы уведомление всегда находилось поверх все окон. Отрисовка полупрозрачного фона уведомления будет производиться в методе paintEvent(), в котором на всю ширину и высоту виджета уведомления будет нарисован полупрозрачный прямоугольник чёрного цвета с закруглённым краями.

Анимация появления и исчезновения уведомления будет производиться посредством объекта QPropertyAnimation.

Что важно: подгонка размеров виджета должна производиться в момент установки текста в уведомление, а не во время перерисовки или  установки местоположения уведомления на экране, иначе будут получены не верный величины размеров уведомления и оно окажется не в ожидаемом месте или не с ожидаемыми размерами.

Для реализации ограниченного по времени показа уведомления на экране компьютера применим QTimer .

#ifndef POPUP_H
#define POPUP_H

#include <QWidget>
#include <QLabel>
#include <QGridLayout>
#include <QPropertyAnimation>
#include <QTimer>

class PopUp : public QWidget
{
    Q_OBJECT

    // Свойство полупрозрачности
    Q_PROPERTY(float popupOpacity READ getPopupOpacity WRITE setPopupOpacity)

    void setPopupOpacity(float opacity);
    float getPopupOpacity() const;

public:
    explicit PopUp(QWidget *parent = 0);

protected:
    void paintEvent(QPaintEvent *event);    // Фон будет отрисовываться через метод перерисовки

public slots:
    void setPopupText(const QString& text); // Установка текста в уведомление
    void show();                            /* Собственный метод показа виджета
                                             * Необходимо для преварительной настройки анимации
                                             * */

private slots:
    void hideAnimation();                   // Слот для запуска анимации скрытия
    void hide();                            /* По окончании анимации, в данном слоте делается проверка,
                                             * виден ли виджет, или его необходимо скрыть
                                             * */

private:
    QLabel label;           // Label с сообщением
    QGridLayout layout;     // Размещение для лейбла
    QPropertyAnimation animation;   // Свойство анимации для всплывающего сообщения
    float popupOpacity;     // Свойства полупрозрачности виджета
    QTimer *timer;          // Таймер, по которому виджет будет скрыт
};

#endif // POPUP_H

popup.cpp

#include "popup.h"
#include <QPainter>
#include <QApplication>
#include <QDesktopWidget>
#include <QDebug>

PopUp::PopUp(QWidget *parent) : QWidget(parent)
{
    setWindowFlags(Qt::FramelessWindowHint |        // Отключаем оформление окна
                   Qt::Tool |                       // Отменяем показ в качестве отдельного окна
                   Qt::WindowStaysOnTopHint);       // Устанавливаем поверх всех окон
    setAttribute(Qt::WA_TranslucentBackground);     // Указываем, что фон будет прозрачным
    setAttribute(Qt::WA_ShowWithoutActivating);     // При показе, виджет не получается фокуса автоматически

    animation.setTargetObject(this);                // Устанавливаем целевой объект анимации
    animation.setPropertyName("popupOpacity");      // Устанавливаем анимируемое свойство
    connect(&animation, &QAbstractAnimation::finished, this, &PopUp::hide); /* Подключаем сигнал окончания
                                                                             * анимации к слоты скрытия
                                                                             * */

    // Настройка текста уведомления
    label.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); // Устанавливаем по центру
    // И настраиваем стили
    label.setStyleSheet("QLabel { color : white; "
                        "margin-top: 6px;"
                        "margin-bottom: 6px;"
                        "margin-left: 10px;"
                        "margin-right: 10px; }");

    // Производим установку текста в размещение, ...
    layout.addWidget(&label, 0, 0);
    setLayout(&layout); // которое помещаем в виджет

    // По сигналу таймера будет произведено скрытие уведомления, если оно видимо
    timer = new QTimer();
    connect(timer, &QTimer::timeout, this, &PopUp::hideAnimation);
}

void PopUp::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    /* А теперь настраиваем фон уведомления,
     * который является прямоугольником с чёрным фоном
     * */
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing); // Включаем сглаживание

    // Подготавливаем фон. rect() возвращает внутреннюю геометрию виджета уведомления, по содержимому
    QRect roundedRect;
    roundedRect.setX(rect().x() + 5);
    roundedRect.setY(rect().y() + 5);
    roundedRect.setWidth(rect().width() - 10);
    roundedRect.setHeight(rect().height() - 10);

    // Кисть настраиваем на чёрный цвет в режиме полупрозрачности 180 из 255
    painter.setBrush(QBrush(QColor(0,0,0,180)));
    painter.setPen(Qt::NoPen); // Край уведомления не будет выделен

    // Отрисовываем фон с закруглением краёв в 10px
    painter.drawRoundedRect(roundedRect, 10, 10);
}

void PopUp::setPopupText(const QString &text)
{
    label.setText(text);    // Устанавливаем текст в Label
    adjustSize();           // С пересчётом размеров уведомления
}

void PopUp::show()
{
    setWindowOpacity(0.0);  // Устанавливаем прозрачность в ноль

    animation.setDuration(150);     // Настраиваем длительность анимации
    animation.setStartValue(0.0);   // Стартовое значение будет 0 (полностью прозрачный виджет)
    animation.setEndValue(1.0);     // Конечное - полностью непрозрачный виджет

    setGeometry(QApplication::desktop()->availableGeometry().width() - 36 - width() + QApplication::desktop() -> availableGeometry().x(),
                QApplication::desktop()->availableGeometry().height() - 36 - height() + QApplication::desktop() -> availableGeometry().y(),
                width(),
                height());
    QWidget::show();                // Отображаем виджет, который полностью прозрачен

    animation.start();              // И запускаем анимацию
    timer->start(3000);             // А также стартуем таймер, который запустит скрытие уведомления через 3 секунды
}

void PopUp::hideAnimation()
{
    timer->stop();                  // Останавливаем таймер
    animation.setDuration(1000);    // Настраиваем длительность анимации
    animation.setStartValue(1.0);   // Стартовое значение будет 1 (полностью непрозрачный виджет)
    animation.setEndValue(0.0);     // Конечное - полностью прозрачный виджет
    animation.start();              // И запускаем анимацию
}

void PopUp::hide()
{
    // Если виджет прозрачный, то скрываем его
    if(getPopupOpacity() == 0.0){
        QWidget::hide();
    }
}

void PopUp::setPopupOpacity(float opacity)
{
    popupOpacity = opacity;

    setWindowOpacity(opacity);
}

float PopUp::getPopupOpacity() const
{
    return popupOpacity;
}

Результат

Архив с исходниками: PopupWindow

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

Видеоурок

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

Вам это нравится? Поделитесь в социальных сетях!

m
  • 14 июня 2018 г. 10:40

В Astra Linux вместо прозрачности черный фон. Не знаеете, что может быть?

Evgenii Legotckoi
  • 18 июня 2018 г. 3:10

Недоработки, вряд ли этот зверь вообще является официально поддерживаемым

IscanderChe
  • 11 июля 2019 г. 12:27

Евгений, не совсем понимаю, как связывается свойство полупрозрачности Q_PROPERTY(float popupOpacity READ getPopupOpacity WRITE setPopupOpacity) со свойством анимации QPropertyAnimation animation. Проясните момент. И где про это прочитать поподробнее.

Evgenii Legotckoi
  • 11 июля 2019 г. 16:24

Все свойства в объектах, которые наследуются от Q_OBJECT и помечены макросом Q_PROPERTY, могут вызываться по своему имени с помощью QMetaObject::invokeMethod . Подробнее можете почитать в документации на QMetaObject .

Именно это и используется для вызова свойста внутри QPropertyAnimation. Исходники я не смотрел, но уверен но 100% что там будет QMetaObject::invokeMethod или что-то похожее.

Обычно используется так

QString retVal;
QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection,
                          Q_RETURN_ARG(QString, retVal),
                          Q_ARG(QString, "sqrt"),
                          Q_ARG(int, 42),
                          Q_ARG(double, 9.7));

А о том, какое свойство вызывать QPropertyAnimation узнаёт через установку целевого объекта и имени свойства объекта, которое нужно вызывать.

animation.setTargetObject(this);                // Устанавливаем целевой объект анимации
animation.setPropertyName("popupOpacity");      // Устанавливаем анимируемое свойство

Видимо это был такой способ обойти шаблонизацию в своё время у них. Думаю, что сейчас это можно с помощью шаблонов решать более изящно. Но вообще, очень не удобно, когда в проекте повсеместно используют QMetaObject::invokeMethod . Трудно отлаживать и иногда вообще не ясно откуда прилетает вызов функции, а callstack так вообще уродский.

ИК
  • 20 июня 2020 г. 9:15
  • (ред.)

Переписал на python, но почему-то не закрывается при вызове hide()? А при вызове из другого модуля вообще не отображается окно?

# This Python file uses the following encoding: utf-8
import sys
import os

from PySide2.QtWidgets import QWidget, QLabel, QGridLayout, QApplication 
from PySide2.QtCore import QTimer, QPropertyAnimation, Qt, QRect
from PySide2.QtGui import QPainter, QBrush, QColor, QBackingStore

class Info(QWidget):
    def __init__(self, text = "", parent = None):
        super().__init__(parent)

        self.text = text
        self.label = QLabel(text)                        # Label с сообщением
        self.adjustSize()
        self.layout = QGridLayout()                  # Размещение для лейбла
        self.animation = QPropertyAnimation(self)    # Свойство анимации для всплывающего сообщения
                         # Свойства полупрозрачности виджета
        self.timer = QTimer()                        # Таймер, по которому виджет будет скрыт

        self.setWindowFlags(Qt.FramelessWindowHint |       # Отключаем оформление окна
                   Qt.Tool |                               # Отменяем показ в качестве отдельного окна
                   Qt.WindowStaysOnTopHint)                # Устанавливаем поверх всех окон
        self.setAttribute(Qt.WA_TranslucentBackground)     # Указываем, что фон будет прозрачным
        self.setAttribute(Qt.WA_ShowWithoutActivating)     # При показе, виджет не получается фокуса автоматически


        self.animation.setTargetObject(self)                 # Устанавливаем целевой объект анимации
        self.animation.setPropertyName(b'windowOpacity')       # Устанавливаем анимируемое свойство

        #self.animation.stateChanged.connect(self.hide())        # Подключаем сигнал окончания анимации к слоты скрытия     
        #connect(&animation, &QAbstractAnimation::finished, this, &PopUp::hide); 

        # Настройка текста уведомления
        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # Устанавливаем по центру
        # И настраиваем стили
        self.label.setStyleSheet("QLabel { color : white; "
                            "margin-top: 6px;"
                            "margin-bottom: 6px;"
                            "margin-left: 10px;"
                            "margin-right: 10px; }")

        # Производим установку текста в размещение, ...
        self.layout.addWidget(self.label, 0, 0)
        self.setLayout(self.layout)  # которое помещаем в виджет

        # По сигналу таймера будет произведено скрытие уведомления, если оно видимо
        #self.timer = QTimer()
        self.timer.timeout.connect(self.hideAnimation)

        #connect(timer, &QTimer::timeout, this, &PopUp::hideAnimation);

    #@property
    #def popupOpacity(self):   
    #   return self._popupOpacity

    #@popupOpacity.setter
    #def popupOpacity(self, opacity):
    #   self._popupOpacity = opacity
    #   self.setWindowOpacity(opacity)

    def paintEvent(self, event):
        painter = QPainter(self)        
        painter.setRenderHint(QPainter.Antialiasing)    # Включаем сглаживание

        # Подготавливаем фон. rect() возвращает внутреннюю геометрию виджета уведомления, по содержимому
        roundedRect = QRect()
        roundedRect.setX(self.rect().x() + 5)
        roundedRect.setY(self.rect().y() + 5)
        roundedRect.setWidth(self.rect().width() - 10)
        roundedRect.setHeight(self.rect().height() - 10)

        # Кисть настраиваем на чёрный цвет в режиме полупрозрачности 180 из 255
        painter.setBrush(QBrush(QColor(0,0,0,180)))
        painter.setPen(Qt.NoPen)     # Край уведомления не будет выделен

        # Отрисовываем фон с закруглением краёв в 10px
        painter.drawRoundedRect(roundedRect, 10, 10)

    def setPopupText(self, text):
        self.label.setText(text)       # Устанавливаем текст в Label
        self.adjustSize()          # С пересчётом размеров уведомления


    def show(self):
        self.setWindowOpacity(0.0)          # Устанавливаем прозрачность в ноль

        self.animation.setDuration(150)     # Настраиваем длительность анимации
        self.animation.setStartValue(0.0)   # Стартовое значение будет 0 (полностью прозрачный виджет)
        self.animation.setEndValue(1.0)     # Конечное - полностью непрозрачный виджет

        self.setGeometry(QApplication.desktop().availableGeometry().width() - 36 - self.width() + QApplication.desktop().availableGeometry().x(),
                    QApplication.desktop().availableGeometry().height() - 36 - self.height() + QApplication.desktop().availableGeometry().y(),
                    self.width(),
                    self.height())

        QWidget.show(self)              # Отображаем виджет, который полностью прозрачен

        self.animation.start()      # И запускаем анимацию
        self.timer.start(3000)      # А также стартуем таймер, который запустит скрытие уведомления через 3 секунды


    def hideAnimation(self):
        self.timer.stop()                   # Останавливаем таймер
        self.timer.timeout.disconnect(self.hideAnimation)
        self.animation.setDuration(100)     # Настраиваем длительность анимации
        self.animation.setStartValue(1.0)   # Стартовое значение будет 1 (полностью непрозрачный виджет)
        self.animation.setEndValue(0.0)     # Конечное - полностью прозрачный виджет
        self.timer.timeout.connect(self.hide)
        self.timer.start(3000)
        self.animation.start()              # И запускаем анимацию


    def hide(self):
        # Если виджет прозрачный, то скрываем его
        print('hide')       
        #QWidget.show(self) 
        self.timer.stop()
        self.animation.stop()
        self.close()

    def __repr__(self):
        return 'Info class'

if __name__ == "__main__":
    # app disexec теперь на pyqt
    app = QApplication(sys.argv)
    app.setStyle('Fusion')

    window = Info("This works fine")
    window.show()  

    sys.exit(app.exec_())


AC
  • 6 августа 2021 г. 12:22

Доброго времени суток.
Возник вопрос - установлена Qt 6.1.2

popup.cpp

#include <QDesktopWidget>

Выводит ошибку: popup.cpp:5:10: error: 'QDesktopWidget' file not found

... в Qt 5.15 использовал метод QApplication::desktop()

    setGeometry(QApplication::desktop()->geometry().width() - 36 - width() + QApplication::desktop()->geometry().x(),
                QApplication::desktop()->geometry().height() - 56 - height() + QApplication::desktop()->geometry().y(),
                width(),
                height());

На данный момент ошибка: popup.cpp:81:31: error: no member named 'desktop' in 'QApplication'

В документации - метод QApplication::desktop() устарел - никак не могу понять какой метод использовать ?

Evgenii Legotckoi
  • 11 октября 2021 г. 1:50

QApplication имеет метод screens() , который возвращает список экранов. А класс QScreen имеет методы geometry() и availableGeometry() . Можете попробовать через них добиться нужного результата.

АН
  • 26 марта 2023 г. 9:04

Включите прозрачность в композит менеджере fly-admin-theme : fly-admin-theme ->Эффекты и всё заработает.

АН
  • 26 марта 2023 г. 9:10
  • (ред.)

Добрый день, взял за основу ваш PopUp notification , и немного доработал его под свои нужды.
Добавил в отдельном eventloop'e всплывающую очередь уведомлений с анимацией и таймером удаления.
С вашего позволения оставляю здесь ссылку на git

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
г
  • ги
  • 23 апреля 2024 г. 15:51

C++ - Тест 005. Структуры и Классы

  • Результат:41баллов,
  • Очки рейтинга-8
l
  • laei
  • 23 апреля 2024 г. 9:19

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

  • Результат:10баллов,
  • Очки рейтинга-10
l
  • laei
  • 23 апреля 2024 г. 9:17

C++ - Тест 003. Условия и циклы

  • Результат:50баллов,
  • Очки рейтинга-4
Последние комментарии
k
kmssr8 февраля 2024 г. 18:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 1:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 10:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 8:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 декабря 2023 г. 21:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
G
Gar22 апреля 2024 г. 5:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 апреля 2024 г. 7:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 апреля 2024 г. 6:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 апреля 2024 г. 2:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex4 апреля 2024 г. 4:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Следите за нами в социальных сетях