Евгений Легоцкой23 января 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 хостинг.
Поддержать автора Donate
m

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

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

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

Все свойства в объектах, которые наследуются от 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 так вообще уродский.

ИК

Переписал на 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_())


Комментарии

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

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
VD

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

  • Результат:73баллов,
  • Очки рейтинга1
Ds

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

  • Результат:64баллов,
  • Очки рейтинга-1
o

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

  • Результат:86баллов,
  • Очки рейтинга6
Последние комментарии
s

Django - Урок 023. Like Dislike система с помощью GenericForeignKey

все, я со всем разобрался!) Извините!)
s

Django - Урок 023. Like Dislike система с помощью GenericForeignKey

Доброго времени суток!) Я случайно набрел на вашу статью, и она помогла мне решить некоторые мои трудности, я прошел за вами по шагам, в попытках адаптировать это под себя, и возник вопрос. У ва…
R

Qt/C++ - Урок 026. Применение CallBack функции

Спасибо огромное!

Qt/C++ - Урок 026. Применение CallBack функции

Если не объявлять статической, то не соберётся. Не получится сделать привязку метода. Дело в том, что в процессе компиляции производится сборка с указанием конкретных участков кода в данном…
  • Anton
  • 4 августа 2020 г. 2:25

Django - Урок 039. Добавление личных сообщений и чатов на сайте - Часть 2 (Счётчик диалогов и чатов с непрочитанными сообщениями)

Может быть посоветуете как добавить необязательное поле + прокинуть его во вьюху что бы можно было отправлять небольшие документы.?
Сейчас обсуждают на форуме
s

Ключевое слово class

Если закоментить include , то ошибка будет такая : C2027(использование неопределённого типа "Autorization"). Если убрать ключевое слово class , то ошибки будет 3 : Ошибка C2143 син…

Как в Android открыть свернутое приложение?

Может быть поможет это

Динамическое изменение цветовой схемы material

как по мне, темы типа material хороши если вас полностью устраивает их стил, если пишете кастомный дизайн то имеет смысл отказаться

QT QGraphics преобразование координат

Добрый день. Да, можно перевести в координаты экрана. Используйте метод mapToGlobal() . В Qt документации на класс приводятся методы, которые внедрены в конкретном классе. В нач…
М

QML связь с моделью в C++

Спасибо.
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB