Evgenii Legotckoi
Jan. 23, 2016, 9:48 p.m.

Qt/C++ - Lesson 042. PopUp notification in the Gnome style using Qt

Functionality of the standard notification system tray at times can be insufficient for the implementation of ambitious pans for styling applications. We therefore consider the embodiment of pop-up messages in the style of PopUp DE Gnome notification, namely, as shown in the following figure.

PopUp notification Gnome style

To demonstrate the notification I propose to create an application, which will be a field for entering text, and a button by pressing which will be called a pop-up message.

The message will be displayed in the lower right corner of the tray system tray. This notice must be sure to scale the contents.

Fade-in it will be implemented within 150 milliseconds and the disappearance, after three seconds.


Project structure

  • PopupWindow.pro - the profile of the project;
  • mainwindow.h - header file of the main application window;
  • mainwindow.cpp - file source code of the main application window;
  • mainwindow.ui - form the main application window;
  • main.cpp - start file the application source code;
  • popup.h - header file notifications;
  • popup.cpp - file source pop-up message.

PopupWindow.pro и mainwindow.ui

The profile of the project, do not connect anything special, but in the main application window, just put a button and a text entry field.

mainwindow.h

The header of the main window of the application you must include the header file PopUp notification and declare the notification object itself, and there will be declared a slot for processing pressing the start button a pop-up notification. This slot will be installed in the text notification and position change notifications on the computer screen in the notification size.

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3.  
  4. #include <QMainWindow>
  5. #include "popup.h"
  6.  
  7. namespace Ui {
  8. class MainWindow;
  9. }
  10.  
  11. class MainWindow : public QMainWindow
  12. {
  13. Q_OBJECT
  14.  
  15. public:
  16. explicit MainWindow(QWidget *parent = 0);
  17. ~MainWindow();
  18.  
  19. private slots:
  20. void on_pushButton_clicked();
  21.  
  22. private:
  23. Ui::MainWindow *ui;
  24. PopUp *popUp;
  25. };
  26.  
  27. #endif // MAINWINDOW_H

mainwindow.cpp

  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3.  
  4. MainWindow::MainWindow(QWidget *parent) :
  5. QMainWindow(parent),
  6. ui(new Ui::MainWindow)
  7. {
  8. ui->setupUi(this);
  9. popUp = new PopUp();
  10.  
  11. }
  12.  
  13. MainWindow::~MainWindow()
  14. {
  15. delete ui;
  16. }
  17.  
  18. void MainWindow::on_pushButton_clicked()
  19. {
  20. popUp->setPopupText(ui->textEdit->toPlainText());
  21.  
  22. popUp->show();
  23. }

popup.h

In order to prepare the notification must be inherited from QWidget class that you want to disable window decoration, and put a transparent background. You also need to set it up so that a notice was always on top of all windows. Rendering translucent background notification will be made in the method paintEvent() , in which the full width and height of the notification widget is drawn semi-transparent black rectangle with rounded edges.

Animation appearance and disappearance of the notification will be made through QPropertyAnimation object.

What is important: fitting the size of the widget should be made at the time the text in the notification, and not during the redrawing or installation location notifications on the screen, or to be received is not correct size and magnitude of the notice, it will not in the expected location, or not with the expected size.

To implement time-limited display notifications on the screen applies QTimer .

  1. #ifndef POPUP_H
  2. #define POPUP_H
  3.  
  4. #include <QWidget>
  5. #include <QLabel>
  6. #include <QGridLayout>
  7. #include <QPropertyAnimation>
  8. #include <QTimer>
  9.  
  10. class PopUp : public QWidget
  11. {
  12. Q_OBJECT
  13.  
  14. Q_PROPERTY(float popupOpacity READ getPopupOpacity WRITE setPopupOpacity)
  15.  
  16. void setPopupOpacity(float opacity);
  17. float getPopupOpacity() const;
  18.  
  19. public:
  20. explicit PopUp(QWidget *parent = 0);
  21.  
  22. protected:
  23. void paintEvent(QPaintEvent *event); // The background will be drawn through the redraw method
  24.  
  25. public slots:
  26. void setPopupText(const QString& text); // Setting text notification
  27. void show(); /* own widget displaying method
  28.   * It is necessary to pre-animation settings
  29. * */
  30.  
  31. private slots:
  32. void hideAnimation(); // Slot start the animation hide
  33. void hide(); /* At the end of the animation, it is checked in a given slot,
  34.                                              * Does the widget visible, or to hide
  35. * */
  36.  
  37. private:
  38. QLabel label;
  39. QGridLayout layout;
  40. QPropertyAnimation animation;
  41. float popupOpacity;
  42. QTimer *timer;
  43. };
  44.  
  45. #endif // POPUP_H

popup.cpp

  1. #include "popup.h"
  2. #include <QPainter>
  3. #include <QApplication>
  4. #include <QDesktopWidget>
  5. #include <QDebug>
  6.  
  7. PopUp::PopUp(QWidget *parent) : QWidget(parent)
  8. {
  9. setWindowFlags(Qt::FramelessWindowHint | // Disable window decoration
  10. Qt::Tool | // Discard display in a separate window
  11. Qt::WindowStaysOnTopHint); // Set on top of all windows
  12. setAttribute(Qt::WA_TranslucentBackground); // Indicates that the background will be transparent
  13. setAttribute(Qt::WA_ShowWithoutActivating); // At the show, the widget does not get the focus automatically
  14.  
  15. animation.setTargetObject(this); // Set the target animation
  16. animation.setPropertyName("popupOpacity"); //
  17. connect(&animation, &QAbstractAnimation::finished, this, &PopUp::hide);
  18.  
  19. label.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
  20. label.setStyleSheet("QLabel { color : white; "
  21. "margin-top: 6px;"
  22. "margin-bottom: 6px;"
  23. "margin-left: 10px;"
  24. "margin-right: 10px; }");
  25.  
  26. layout.addWidget(&label, 0, 0);
  27. setLayout(&layout);
  28.  
  29. timer = new QTimer();
  30. connect(timer, &QTimer::timeout, this, &PopUp::hideAnimation);
  31. }
  32.  
  33. void PopUp::paintEvent(QPaintEvent *event)
  34. {
  35. Q_UNUSED(event)
  36.  
  37. QPainter painter(this);
  38. painter.setRenderHint(QPainter::Antialiasing);
  39.  
  40. QRect roundedRect;
  41. roundedRect.setX(rect().x() + 5);
  42. roundedRect.setY(rect().y() + 5);
  43. roundedRect.setWidth(rect().width() - 10);
  44. roundedRect.setHeight(rect().height() - 10);
  45.  
  46. painter.setBrush(QBrush(QColor(0,0,0,180)));
  47. painter.setPen(Qt::NoPen);
  48.  
  49. painter.drawRoundedRect(roundedRect, 10, 10);
  50. }
  51.  
  52. void PopUp::setPopupText(const QString &text)
  53. {
  54. label.setText(text); // Set the text in the Label
  55. adjustSize(); // With the recalculation notice sizes
  56. }
  57.  
  58. void PopUp::show()
  59. {
  60. setWindowOpacity(0.0); // Set the transparency to zero
  61.  
  62. animation.setDuration(150); // Configuring the duration of the animation
  63. animation.setStartValue(0.0); // The start value is 0 (fully transparent widget)
  64. animation.setEndValue(1.0); // End - completely opaque widget
  65.  
  66. setGeometry(QApplication::desktop()->availableGeometry().width() - 36 - width() + QApplication::desktop() -> availableGeometry().x(),
  67. QApplication::desktop()->availableGeometry().height() - 36 - height() + QApplication::desktop() -> availableGeometry().y(),
  68. width(),
  69. height());
  70. QWidget::show();
  71.  
  72. animation.start();
  73. timer->start(3000);
  74. }
  75.  
  76. void PopUp::hideAnimation()
  77. {
  78. timer->stop();
  79. animation.setDuration(1000);
  80. animation.setStartValue(1.0);
  81. animation.setEndValue(0.0);
  82. animation.start();
  83. }
  84.  
  85. void PopUp::hide()
  86. {
  87. // If the widget is transparent, then hide it
  88. if(getPopupOpacity() == 0.0){
  89. QWidget::hide();
  90. }
  91. }
  92.  
  93. void PopUp::setPopupOpacity(float opacity)
  94. {
  95. popupOpacity = opacity;
  96.  
  97. setWindowOpacity(opacity);
  98. }
  99.  
  100. float PopUp::getPopupOpacity() const
  101. {
  102. return popupOpacity;
  103. }

Result

Archive of the source: PopupWindow

As a result, the message will be as follows:

Video

Do you like it? Share on social networks!

m
  • June 14, 2018, 8:40 p.m.

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

Evgenii Legotckoi
  • June 18, 2018, 1:10 p.m.

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

IscanderChe
  • July 11, 2019, 10:27 p.m.

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

Evgenii Legotckoi
  • July 12, 2019, 2:24 a.m.

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

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

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

  1. QString retVal;
  2. QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection,
  3. Q_RETURN_ARG(QString, retVal),
  4. Q_ARG(QString, "sqrt"),
  5. Q_ARG(int, 42),
  6. Q_ARG(double, 9.7));

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

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

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

ИК
  • June 20, 2020, 7:15 p.m.
  • (edited)

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

  1. # This Python file uses the following encoding: utf-8
  2. import sys
  3. import os
  4.  
  5. from PySide2.QtWidgets import QWidget, QLabel, QGridLayout, QApplication
  6. from PySide2.QtCore import QTimer, QPropertyAnimation, Qt, QRect
  7. from PySide2.QtGui import QPainter, QBrush, QColor, QBackingStore
  8.  
  9. class Info(QWidget):
  10. def __init__(self, text = "", parent = None):
  11. super().__init__(parent)
  12.  
  13. self.text = text
  14. self.label = QLabel(text) # Label с сообщением
  15. self.adjustSize()
  16. self.layout = QGridLayout() # Размещение для лейбла
  17. self.animation = QPropertyAnimation(self) # Свойство анимации для всплывающего сообщения
  18. # Свойства полупрозрачности виджета
  19. self.timer = QTimer() # Таймер, по которому виджет будет скрыт
  20.  
  21. self.setWindowFlags(Qt.FramelessWindowHint | # Отключаем оформление окна
  22. Qt.Tool | # Отменяем показ в качестве отдельного окна
  23. Qt.WindowStaysOnTopHint) # Устанавливаем поверх всех окон
  24. self.setAttribute(Qt.WA_TranslucentBackground) # Указываем, что фон будет прозрачным
  25. self.setAttribute(Qt.WA_ShowWithoutActivating) # При показе, виджет не получается фокуса автоматически
  26.  
  27.  
  28. self.animation.setTargetObject(self) # Устанавливаем целевой объект анимации
  29. self.animation.setPropertyName(b'windowOpacity') # Устанавливаем анимируемое свойство
  30.  
  31. #self.animation.stateChanged.connect(self.hide()) # Подключаем сигнал окончания анимации к слоты скрытия
  32. #connect(&animation, &QAbstractAnimation::finished, this, &PopUp::hide);
  33.  
  34. # Настройка текста уведомления
  35. self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # Устанавливаем по центру
  36. # И настраиваем стили
  37. self.label.setStyleSheet("QLabel { color : white; "
  38. "margin-top: 6px;"
  39. "margin-bottom: 6px;"
  40. "margin-left: 10px;"
  41. "margin-right: 10px; }")
  42.  
  43. # Производим установку текста в размещение, ...
  44. self.layout.addWidget(self.label, 0, 0)
  45. self.setLayout(self.layout) # которое помещаем в виджет
  46.  
  47. # По сигналу таймера будет произведено скрытие уведомления, если оно видимо
  48. #self.timer = QTimer()
  49. self.timer.timeout.connect(self.hideAnimation)
  50.  
  51. #connect(timer, &QTimer::timeout, this, &PopUp::hideAnimation);
  52.  
  53. #@property
  54. #def popupOpacity(self):
  55. # return self._popupOpacity
  56.  
  57. #@popupOpacity.setter
  58. #def popupOpacity(self, opacity):
  59. # self._popupOpacity = opacity
  60. # self.setWindowOpacity(opacity)
  61.  
  62. def paintEvent(self, event):
  63. painter = QPainter(self)
  64. painter.setRenderHint(QPainter.Antialiasing) # Включаем сглаживание
  65.  
  66. # Подготавливаем фон. rect() возвращает внутреннюю геометрию виджета уведомления, по содержимому
  67. roundedRect = QRect()
  68. roundedRect.setX(self.rect().x() + 5)
  69. roundedRect.setY(self.rect().y() + 5)
  70. roundedRect.setWidth(self.rect().width() - 10)
  71. roundedRect.setHeight(self.rect().height() - 10)
  72.  
  73. # Кисть настраиваем на чёрный цвет в режиме полупрозрачности 180 из 255
  74. painter.setBrush(QBrush(QColor(0,0,0,180)))
  75. painter.setPen(Qt.NoPen) # Край уведомления не будет выделен
  76.  
  77. # Отрисовываем фон с закруглением краёв в 10px
  78. painter.drawRoundedRect(roundedRect, 10, 10)
  79.  
  80. def setPopupText(self, text):
  81. self.label.setText(text) # Устанавливаем текст в Label
  82. self.adjustSize() # С пересчётом размеров уведомления
  83.  
  84.  
  85. def show(self):
  86. self.setWindowOpacity(0.0) # Устанавливаем прозрачность в ноль
  87.  
  88. self.animation.setDuration(150) # Настраиваем длительность анимации
  89. self.animation.setStartValue(0.0) # Стартовое значение будет 0 (полностью прозрачный виджет)
  90. self.animation.setEndValue(1.0) # Конечное - полностью непрозрачный виджет
  91.  
  92. self.setGeometry(QApplication.desktop().availableGeometry().width() - 36 - self.width() + QApplication.desktop().availableGeometry().x(),
  93. QApplication.desktop().availableGeometry().height() - 36 - self.height() + QApplication.desktop().availableGeometry().y(),
  94. self.width(),
  95. self.height())
  96.  
  97. QWidget.show(self) # Отображаем виджет, который полностью прозрачен
  98.  
  99. self.animation.start() # И запускаем анимацию
  100. self.timer.start(3000) # А также стартуем таймер, который запустит скрытие уведомления через 3 секунды
  101.  
  102.  
  103. def hideAnimation(self):
  104. self.timer.stop() # Останавливаем таймер
  105. self.timer.timeout.disconnect(self.hideAnimation)
  106. self.animation.setDuration(100) # Настраиваем длительность анимации
  107. self.animation.setStartValue(1.0) # Стартовое значение будет 1 (полностью непрозрачный виджет)
  108. self.animation.setEndValue(0.0) # Конечное - полностью прозрачный виджет
  109. self.timer.timeout.connect(self.hide)
  110. self.timer.start(3000)
  111. self.animation.start() # И запускаем анимацию
  112.  
  113.  
  114. def hide(self):
  115. # Если виджет прозрачный, то скрываем его
  116. print('hide')
  117. #QWidget.show(self)
  118. self.timer.stop()
  119. self.animation.stop()
  120. self.close()
  121.  
  122. def __repr__(self):
  123. return 'Info class'
  124.  
  125. if __name__ == "__main__":
  126. # app disexec теперь на pyqt
  127. app = QApplication(sys.argv)
  128. app.setStyle('Fusion')
  129.  
  130. window = Info("This works fine")
  131. window.show()
  132.  
  133. sys.exit(app.exec_())
  134.  
  135.  
AC
  • Aug. 6, 2021, 10:22 p.m.

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

popup.cpp

  1. #include <QDesktopWidget>

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

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

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

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

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

Evgenii Legotckoi
  • Oct. 11, 2021, 11:50 a.m.

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

АН
  • March 26, 2023, 7:04 p.m.

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

АН
  • March 26, 2023, 7:10 p.m.
  • (edited)

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • IscanderChe
    April 12, 2025, 5:12 p.m.
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    April 1, 2025, 11:41 a.m.
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…