Топ контрибуторов
loading
loading
Знаете ли Вы, что

Вы можете подписаться на RSS ленту новых тестов сервиса Quizful, в том числе и отдельно по каждой категории

Лента обновлений
ссылка Aug 17 21:20
Комментарий от QUIZlogin:
+1
ссылка Aug 17 20:46
Комментарий от QUIZlogin:
+1
ссылка Aug 17 14:21
Комментарий от QUIZlogin:
Имхо пояснение к вопросу недостаточно чётко.
Для преди...
ссылка Aug 17 13:51
Комментарий от QUIZlogin:
Нельзя ли добавить разъяснение причины в контексте раз...
ссылка Aug 16 14:34
Комментарий от Inna_86:
Правильный ответ: окружность. Потому, что вложенный спис...
Статистика

Тестов: 153, вопросов: 8596. Пройдено: 438931 / 2154536.

Qt. Накрываем виджеты стеклом

head tail Статья
категория
C++
дата01.10.2010
авторVaiMR
голосов8

Проблема

Часто при разработке приложений сильно зависящих от ресурсов (серверов, устройств, файлов и т. п.), возникает необходимость сообщить пользователю об их недоступности. А иногда и полностью заблокировать работу с приложением до освобождения или начала работы ресурса. Такая необходимость возникает при создании различных тонких клиентов и использовании модели DaaS.

Подходы

Существует множество подходов для решения этой задачи и все они зависят от поставленных требований. Приведем основные направления решений:

  1. Блокировка элемента управления предоставляющего недоступный сервис (этот подход используется когда клиент многофункционален, и при этом недоступность одного из сервисов не является критичной для работы клиента);
  2. Показ модального/не модального предупреждающего сообщения (используется когда возможна дальнейшая работа клиента при недоступности сервиса);
  3. Блокировка всех действий пользователя (если без недоступного сервиса дальнейшая работа клиента невозможна);
  4. Завершение работы приложения.
В данной статье будет приведен пример реализации третьего направления решения подобной проблемы. За основу разработки взята библиотека Qt.

Решение

Попробуем реализовать блокировку произвольного виджета с возможностью отображения поверх него текста и анимированной графики. В качестве ознакомления с существующими проблемами в реализации можно ознакомиться со статьей Рисование поверх дочерних виджетов.

В библиотеке Qt очень богатый инструментарий. Существуют специальные классы для создания графических эффектов: QGraphicsBlurEffect, GraphicsColorizeEffect, QGraphicsDropShadowEffect и QGraphicsOpacityEffect. Приведенные графические эффекты можно применить к любым виджетам с помощью метода setGraphicsEffect(QGraphicsEffect* effect). Так как мы собираемся реализовать полупрозрачное стекло, то нам необходим класс QGraphicsOpacityEffect.

С анимацией дела обстоят еще проще. У класса QLabel есть слот setMovie(QMovie* movie). С помощью этого слота можно отобразить на QLabel любую загруженную в QMovie анимацию.

Идея метода состоит в том, чтобы поверх блокируемого виджета поместить QLabel с настраиваемыми параметрами прозрачности и цвета и заблокировать все возможные действия пользователя над нижними графическими компонентами. После реализации должно получиться нечто подобное:

Вид стекла установленного над

Реализация

Для начала определим класс стекла с необходимыми методами. Нам необходимы методы установки и снятия стекла с виджета. Методы, управляющие прозрачностью, цветом и информационным блоком. Информационный блок будет содержать в себе пояснительный текст и некоторое изображение.

Объявим класс:

  1. class Glass: public QObject {
  2.     Q_OBJECT
  3. public:
  4.     Glass();
  5.     virtual ~Glass();
Объявим методы установки и снятия стекла с виджета:
  1.     virtual void install(QWidget* widget);
  2.     virtual void remove();
Добавим методы управления цветом и прозрачностью:
  1.     void enableColor(const QColor& color = QColor(111, 111, 100));
  2.     void disableColor();
  3.  
  4.     void enableOpacity(qreal opacity = 0.5);
  5.     void disableOpacity();
Информационный блок будет составным. Состоит он из двух объектов QLabel. Если на писать и рисовать на самом стекле, то текст и графика будут принимать цвет стекла или становиться прозрачными.
  1.     void enableInfoBlock(QMovie* movie = 0, const QString& text = QString::null);
  2.     void disableInfoBlock();
  3.  
  4.     void enableAnimationBlock(QMovie* movie = 0);
  5.     void disableAnimationBlock();
  6.  
  7.     void enableInfoTextBlock(const QString& text = QString::null);
  8.     void disableInfoTextBlock();
  9.     QLabel& getInfoTextBlock();
  10.     void setMovie(QMovie* movie);
Для того, чтобы блокировать любые действия с виджетом, изменять размеры стекла, поверх которого помещено стекло, определим фильтр событий. Его мы будем устанавливать в блокируемый виджет.
  1. protected:
  2.     bool eventFilter(QObject* object, QEvent* event);
Добавим методы показа отдельных частей информационного блока и его позиционирования:
  1. private:
  2.     void showInfoTextBlock(const QString& text = QString::null);
  3.     void showAnimationBlock(QMovie* movie = 0);
  4.     void infoBlockPositioning();
И поля компонент стекла:
  1.     QLabel* glass;
  2.     QLabel* animationContainer;
  3.     QMovie* movie;
  4.     QMovie* defaultMovie;
  5.     QLabel* infoTextContaiter;
  6.     QColor glassColor;
Добавим флаги, по которым при повторной установке стекла будем определять показывать ли информационный блок:
  1.     bool infoTextBlockEnabled;
  2.     bool animationBlockEnabled;
Последовательно реализуем все необходимые методы. Для начала конструктор и деструктор:
  1. Glass::Glass(): movie(0), defaultMovie(0), countPerSecond(0) {
  2. // Создадим сразу стекло и компоненты информационного блока. Они без
  3. // родительского объекта, потому что во время установки стекла поверх виджета
  4. // будут изменяться предки.
  5.     glass = new QLabel();
  6.     animationContainer = new QLabel();
  7.     infoTextContaiter = new QLabel();
  8.  
  9. // Загрузим анимацию по умолчанию
  10.     defaultMovie = new QMovie(":/Anim.mng", QByteArray(), animationContainer);
  11. }
  12.  
  13. // Так как мы не задали родителей для компонент стекла, необходимо
  14. // самостоятельно позаботиться об освобождении памяти
  15. Glass::~Glass() {
  16.     glass->deleteLater();
  17.     animationContainer->deleteLater();
  18.     infoTextContaiter->deleteLater();
  19.     defaultMovie->deleteLater();
  20. }
  21.  
  22. // Теперь реализуем один из самых важных методов. Установку стекла поверх
  23. // виджета
  24. void Glass::install(QWidget* widget) {
  25. // Для начала удалим его с предыдущего виджета
  26.     remove();
  27.  
  28. // Установим стекло поверх виджета
  29.     glass->setParent(widget);
  30.  
  31. // Начнем отлавливать все события, установив фильтр    widget->installEventFilter(this);
  32.  
  33. // Покажем стекло и информационный блок, если это необходимо
  34.     glass->show();
  35.     if (infoTextBlockEnabled)
  36.         showInfoTextBlock();
  37.  
  38.     if (animationBlockEnabled)
  39.         showAnimationBlock();
  40.    
  41. // Пошлем событие сами себе, чтобы стекло и информационный блок правильно
  42. // разместились на виджете
  43.     QEvent event(QEvent::Resize);
  44.     eventFilter(0, &event);
  45. }
  46.  
  47. // Удаление виджета противоположно установке
  48. void Glass::remove() {
  49. // Если стекло было установлено, то удаляем его
  50.     if (glass->parentWidget() != 0) {
  51. // Перестаем отлавливать события на низлежащем виджете
  52.         glass->parentWidget()->removeEventFilter(this);
  53.  
  54. // Скрываем все компоненты стекла
  55.         glass->hide();
  56.         glass->setParent(0);
  57.         animationContainer->hide();
  58.         infoTextContaiter->hide();
  59.         animationContainer->setParent(0);
  60.         infoTextContaiter->setParent(0);
  61.     }
  62. }
  63.  
  64. // Реализуем фильтр событий на виджете. Назначение этого фильтра - не
  65. // допустить работу с виджетом под стеклом и обеспечить актуальные размеры и перерисовку стекла
  66. bool Glass::eventFilter(QObject* /* object */, QEvent* event) {
  67. // Если показывается виджет или изменились его размеры, изменяем размеры
  68. // стекла и позиционируем информационный блок
  69.     if ((event->type() == QEvent::Show) || (event->type() == QEvent::Resize)) {
  70.         glass->resize(glass->parentWidget()->size());
  71.         glass->move(0, 0);
  72.         infoBlockPositioning();
  73.         return true;
  74.     }
  75. // Всегда отбираем фокус, чтобы запретить работу с виджетом под стеклом
  76.     glass->setFocus();
  77.     return false;
  78. }
  79.  
  80. // Реализуем позиционирование информационного блока. Он будет размещаться по
    // центру стекла. Под анимацией будем отображать пояснительный текст.
  81. void Glass::infoBlockPositioning() {
  82.     if (animationContainer->isVisible() && infoTextContaiter->isVisible()) {
  83.         animationContainer->move((glass->width() - animationContainer->width()) / 2,
  84.                 glass->height() / 2 - animationContainer->height());
  85.         infoTextContaiter->move((glass->width() - infoTextContaiter->width()) / 2,
  86.                 glass->height() / 2 + infoTextContaiter->height());
  87.     } else {
  88.         if (animationContainer->isVisible())
  89.             animationContainer->move((glass->width() - animationContainer->width()) / 2,
  90.                     (glass->height() - animationContainer->height()) / 2);
  91.  
  92.         if (infoTextContaiter->isVisible())
  93.             infoTextContaiter->move((glass->width() - infoTextContaiter->width()) / 2,
  94.                     (glass->height() - infoTextContaiter->height()) / 2);
  95.     }
  96. }
  97.  
  98. // Реализуем методы показа информационного блока
  99. void Glass::showInfoTextBlock(const QString& text) {
  100. // Устанавливаем пояснительный текст поверх стекла и накрытого виджета. Здесь
  101. // используется особенность размещения графических компонентов Qt: последний
  102. // добавленный графический компонент будет выше всех остальных
  103.     infoTextContaiter->setParent(glass->parentWidget());
  104.     if (!text.isNull())
  105.         infoTextContaiter->setText(text);
  106.  
  107.     infoTextContaiter->show();
  108.     infoTextContaiter->adjustSize();
  109. // Корректируем позицию
  110.     infoBlockPositioning();
  111. }
  112.  
  113. void Glass::showAnimationBlock(QMovie* movie) {
  114. // Устанавливаем анимационный контейнер поверх стекла и накрытого виджета
  115.     animationContainer->setParent(glass->parentWidget());
  116.     if (movie != 0)
  117.         setMovie(movie);
  118.     animationContainer->show();
  119.     infoBlockPositioning();
  120. }

Реализацию методов установки свойств стекла и информационного блока оставим читателю, они простые.

Развитие

В приведенном примере реализованы лишь базовые возможности стекла. В дальнейшем можно добавить к стеклам другие эффекты (размытия, тени, уменьшения разрешения) или добавить возможность установки стекла сразу поверх нескольких виджетов. Очень функциональным будет стекло с эффектом мигания рамкой или произвольной фигурой в обучающих системах при совершении ошибок.

В среде рабочего стола KDE используется похожий подход для приложений, которые не отвечают некоторое время. Данный пример имеет более широкие возможности и может применяться во множестве приложений как стильный и практичный компонент.

Если Вам понравилась статья, проголосуйте за нее

Голосов: 8  loading...
pmogilevskiy   homjak   sanchez   sava_vodka   climber   sulikolia   tigra_nadin   vizir1989