Отображение одного QWidget над другим

Отображение одного QWidget над другим

Потребность в расположении одного виджета над другим может возникнуть, если вы хотите использовать нестандартные дизайнерские приемы или пытаетесь сэкономить место в насыщенном графическом интерфейсе. У этого решения есть свои преимущества и недостатки. С одной стороны, это позволит использовать более гибкие способы компоновки виджетов, но может запутать пользователя, который не знаком с ходом ваших мыслей. Также такой интерфейс будет сложнее реализовать и сопровождать, поскольку всю работу, которую иначе проделал бы менеджер компоновки QLayout, придется делать вручную. Поэтому лучше выбрать какой-то компромисс и пользоваться представленным в заметке приемом лишь в редких случаях и только тогда, когда это реально необходимо.

Пример расположения одного виджета поверх другого

В качестве примера мы реализуем приложение, способное выводить подсказки по аналогии с Toasts на Android, которые выглядят следующим образом:

android_toasts_hello_world

В нижней части скриншота мы видим небольшое поле с текстом. Оно появляется на экране поверх остальных элементов интерфейса на некоторый промежуток времени и само исчезает. Его удобно использовать для вывода коротких сообщений и уведомлений. В отличие от диалоговых окон, пользователь со стандартным Toast взаимодействовать не может. Для Android предусмотрен специальный класс, который позволяет выводить такие уведомления. Но в QtSDK на момент написания заметки подобный функционал не предусмотрен. Попробуем исправить это.

Сразу скажу, что расположить виджет в произвольной позиции на другом виджете очень легко. Для того, чтобы это сделать, нам достаточно не добавлять дочерний виджет в layout родительского виджета, а просто установить отношение между ними, передав в конструктор дочернего виджета указатель на родительский:

В этом случае дочерний виджет будет отображаться на родительском, но за его размерами и позицией придется следить нам самим.

Таким образом, мы создадим простое приложение, которое будет выводить уведомления в нижней части главного окна. Чтобы приложение не вышло слишком скучным, раскрасим уведомления в разные цвета. Для этого создадим три кнопки, при нажатии на которые будут выводиться разные уведомления со своим стилем.

Реализация компоновки одного виджета над другим

Начнем с объявления класса:

Здесь все достаточно очевидно. Создаем обычный виджет, который наследует класс QWidget. Для него переопределяем виртуальную функцию resizeEvent(), чтобы контролировать положение подсказки при изменении размеров окна.

Для отображения самой подсказки будем использовать функцию-член showToast(). Она принимает два параметра: текст сообщения подсказки message и класс стиля styleClass, с которым подсказка должна отобразиться. Пока что на стилях останавливаться не будем, но скоро мы к ним вернемся.

Чтобы наш виджет не вышел совсем пустым, в добавок к кнопкам мы разместим на нем текстовое поле QTextEdit. При этом саму подсказку мы будем отображать с помощью обычного QLabel, который будет исчезать по сигналу таймера QTimer.

А теперь пройдемся по реализации каждой функции и слота. Но, как я и обещал, начнем со стилей:

Конечно, лучше было бы вынести содержимое константы TOAST_STYLESHEET в отдельный файл, но я решил не усложнять реализацию и сделать по-простому. В Qt реализована очень мощная и удобная система управления стилями. Вы можете поменять внешний вид любого стандартного виджета. Есть несколько вариантов добиться этого, но одним из самых удобных является применение языка QSS, который приходится близким родственником CSS. Если вы знакомы с CSS, то особых сложностей с пониманием QSS, возникнуть не должно. В представленном выше фрагменте мы сначала задаем стиль для объектов класса QLabel. Устанавливаем:

  1. Жирное начертание шрифта;
  2. Внутренние отступы по 10 пикселей сверху и снизу, и по 15 пикселей слева и справа;
  3. Сплошную границу толщиной в 2 пикселя;
  4. Скругленные края для границы с радиусом 10 пикселей.

Далее мы определяем четыре набора стилей для разных значений свойства class. Указать свойство можно для любого объекта QObject с помощью функции-члена setProperty(). Например:

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

Теперь переходим к конструктору родительского виджета:

Важным моментом здесь является определение и инициализация m_lblToast. В его конструктор мы передаем указатель this, чтобы обеспечить привязку к родительскому виджету, но ни в какие layout-ы мы его не добавляем. Далее мы:

  1. Устанавливаем текст "Hello, world!";
  2. Делаем выравнивание текста по центру;
  3. Задаем перенос слов для случая длинных строк;
  4. Определяем значение свойства class, равным Info;
  5. Не забываем назначить стиль, который мы заранее подготовили чуть выше;
  6. Вручную просим подогнать размер под содержимое, иначе виджет окажется слишком маленьким для текста.

Сигнал textChanged() поля m_edit мы подключаем к слотам hide() для m_lblToast и stop() для m_timer. Первое соединение обеспечит скрытие виджета уведомления, если оно было видимым, а второе остановит таймер, если он был запущен.

Затем мы создаем три кнопки и соединяем их последовательно со слотами onOk(), onError() и onWait().

Таймер мы переводим в режим singleShot, то есть он будет срабатывать после старта всего один раз. При этом, когда он сработает, для m_lblToast будет вызван слот hide() и он исчезнет с экрана.

Строка m_lblToast->raise() нужна для того, чтобы виджет подсказки отображался поверх всех остальных виджетов.

Теперь посмотрим на реализацию функции resizeEvent(), которая будет вызываться при любом изменении размера родительского виджета:

Координату x мы рассчитываем таким образом, чтобы виджет подсказки был размещен в середине родительского виджета по горизонтали. Для координаты y мы используем относительную позиции, для чего определили константу TOAST_CENTER_POSITION_FROM_PARENT_TOP. Она указывает, насколько середина виджета подсказки должна быть удалена от верха родительского виджета по вертикали. Значение должно быть определено в долях единицы, иначе для слишком больших или маленьких величин виджет подсказки вылезет за пределы родительского виджета. Мы задали значение 0.7, поэтому он появится в нижней части. А, например, для значения 0.5 мы бы увидели, что подсказка появляется точно в середине родительского виджета.

На этом этапе мы уже можем запустить приложение. И вот что получится:

toast_demo_widget_first_start

Слева на скриншоте мы видим наше уведомление "Hello, world". Но если мы начнем печатать в текстовом поле, то оно сразу же исчезнет, что представлено в правой части скриншота.

Теперь пришло время написать реализацию функции showToast(). Вот что получилось:

Сначала мы проверяем активность статуса. Если он запущен, то какое-то уведомление уже отображается и мы выходим из функции. Иначе мы:

  1. Устанавливаем переданный текст message в m_lblToast;
  2. Задаем значение свойства class для m_lblToast, чтобы применить нужный стиль styleClass, который был передан в функцию;
  3. Следующие несколько строк заставляет принудительно перерисовать m_lblToast с учетом изменившихся значений свойств;
  4. Затем мы вновь подгоняем размер под содержимое;
  5. Отображаем виджет подсказки m_lblToast;
  6. Запускаем таймер;
  7. Вручную вызываем resizeEvent(), чтобы обновить позицию, поскольку размер дочернего виджета изменился для соответствия новому содержимому.

При наличии функции showToast() реализация слотов, привязанных к кнопкам, остается тривиальной:

Думаю, что комментарии здесь не требуются. Поэтому приведу окончательную версию кода реализации целиком:

А вот так выглядит приложение после нажатия разных кнопок, когда в текстовом поле находится копия его же исходного кода:

toast_demo_widget

Заключение

Вот мы и разобрались с тем, как разместить один QWidget над другим. Мы рассмотрели вполне реальный пример использования такой техники, в рамках которого реализовали простой аналог системы уведомлений Toasts, которая есть в Android. Но прежде чем использовать представленную методику, еще раз подумайте. Вполне вероятно, что в вашем случае вполне достаточно стандартных методов компоновки на основе QLayout.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий