Функционала стандартных уведомлений системного трея порой может не хватать для реализации смелых задумок по стилизации приложения. Поэтому рассмотрим вариант реализации всплывающего сообщения в стиле 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
В результате сообщение будет выглядеть следующим образом:
В Astra Linux вместо прозрачности черный фон. Не знаеете, что может быть?
Недоработки, вряд ли этот зверь вообще является официально поддерживаемым
Евгений, не совсем понимаю, как связывается свойство полупрозрачности Q_PROPERTY(float popupOpacity READ getPopupOpacity WRITE setPopupOpacity) со свойством анимации QPropertyAnimation animation. Проясните момент. И где про это прочитать поподробнее.
Все свойства в объектах, которые наследуются от Q_OBJECT и помечены макросом Q_PROPERTY, могут вызываться по своему имени с помощью QMetaObject::invokeMethod . Подробнее можете почитать в документации на QMetaObject .
Именно это и используется для вызова свойста внутри QPropertyAnimation. Исходники я не смотрел, но уверен но 100% что там будет QMetaObject::invokeMethod или что-то похожее.
Обычно используется так
А о том, какое свойство вызывать QPropertyAnimation узнаёт через установку целевого объекта и имени свойства объекта, которое нужно вызывать.
Видимо это был такой способ обойти шаблонизацию в своё время у них. Думаю, что сейчас это можно с помощью шаблонов решать более изящно. Но вообще, очень не удобно, когда в проекте повсеместно используют QMetaObject::invokeMethod . Трудно отлаживать и иногда вообще не ясно откуда прилетает вызов функции, а callstack так вообще уродский.
Переписал на python, но почему-то не закрывается при вызове hide()? А при вызове из другого модуля вообще не отображается окно?
Доброго времени суток.
Возник вопрос - установлена Qt 6.1.2
popup.cpp
Выводит ошибку: popup.cpp:5:10: error: 'QDesktopWidget' file not found
... в Qt 5.15 использовал метод QApplication::desktop()
На данный момент ошибка: popup.cpp:81:31: error: no member named 'desktop' in 'QApplication'
В документации - метод QApplication::desktop() устарел - никак не могу понять какой метод использовать ?
QApplication имеет метод screens() , который возвращает список экранов. А класс QScreen имеет методы geometry() и availableGeometry() . Можете попробовать через них добиться нужного результата.
Включите прозрачность в композит менеджере fly-admin-theme : fly-admin-theme ->Эффекты и всё заработает.
Добрый день, взял за основу ваш PopUp notification , и немного доработал его под свои нужды.
Добавил в отдельном eventloop'e всплывающую очередь уведомлений с анимацией и таймером удаления.
С вашего позволения оставляю здесь ссылку на git