Evgenii Legotckoi
Evgenii LegotckoiҚаң. 23, 2016, 10:48 Т.Ж.

Qt/C++ - Оқулық 042. Qt бар Gnome стиліндегі қалқымалы хабарландыру

Функционала стандартных уведомлений системного трея порой может не хватать для реализации смелых задумок по стилизации приложения. Поэтому рассмотрим вариант реализации всплывающего сообщения в стиле 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, 4: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

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
AD

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

  • Нәтиже:50ұпай,
  • Бағалау ұпайлары-4
m
  • molni99
  • Қаз. 26, 2024, 1:37 Т.Ж.

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

  • Нәтиже:80ұпай,
  • Бағалау ұпайлары4
m
  • molni99
  • Қаз. 26, 2024, 1:29 Т.Ж.

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

  • Нәтиже:20ұпай,
  • Бағалау ұпайлары-10
Соңғы пікірлер
i
innorwallҚар. 14, 2024, 9:09 Т.Ж.
Qt/C++ - 068-сабақ. CLion жүйесінде CMAKE құрастыру жүйесін пайдаланатын Hello World ditropan pristiq dosing With the Yankees leading, 4 3, Rivera jogged in from the bullpen to a standing ovation as he prepared for his final appearance in Chicago buy priligy pakistan
i
innorwallҚар. 14, 2024, 4:05 Т.Ж.
ЖАМАНДЫҚ ЯРЕК. Google reCAPTCHA пайдалану 2001; 98 29 34 priligy buy
i
innorwallҚар. 14, 2024, 2:54 Т.Ж.
Джанго - Сабақ 003. Джангодағы үлгі, үлгі және көрініс Hair follicles are believed to produce approximately 20 individual hair shafts over the life of the follicle as the follicle progresses through cycles of hair production, shedding ejection, invo…
i
innorwallҚар. 13, 2024, 11:03 Т.Қ.
Qt тілінде ойын қалай жазылады - 3-сабақ. Басқа объектілермен әрекеттесу what is priligy tablets What happens during the LASIK surgery process
Енді форумда талқылаңыз
i
innorwallҚар. 14, 2024, 3:39 Т.Ж.
добавить qlineseries в функции priligy amazon canada 93 GREB1 protein GREB1 AB011147 6
i
innorwallҚар. 11, 2024, 10:55 Т.Ж.
Всё ещё разбираюсь с кешем. priligy walgreens levitra dulcolax carbs The third ring was found to be made up of ultra relativistic electrons, which are also present in both the outer and inner rings
9
9AnonimҚаз. 25, 2024, 9:10 Т.Ж.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Бізді әлеуметтік желілерде бақылаңыз