Функціонала стандартних повідомлень системного трею часом може не вистачати для реалізації сміливих задумів по стилізації програми. Тому розглянемо варіант реалізації спливаючого повідомлення в стилі 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