Spec-Zone.ru › Qt 5.11

Управление макетом

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

Введение

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

Все подклассы QWidget могут использовать макеты для управления своими дочерними элементами. Функция QWidget::setLayout() применяет макет к виджету. Когда макет таким образом устанавливается на виджете, он берет на себя следующие задачи:

  • Позиционирование дочерних виджетов
  • Определение разумных размеров по умолчанию для окон
  • Определение разумных минимальных размеров для окон
  • Обработка изменения размеров
  • Автоматическое обновление при изменении содержимого:
    • Размер шрифта, текст или другое содержимое дочерних виджетов
    • Скрытие или отображение дочернего виджета
    • Удаление дочерних виджетов

Классы управления макетом Qt

Классы управления макетом Qt были разработаны для написания кода на C++, позволяя указывать размеры в пикселях для простоты понимания и использования. Созданный код для форм, созданных с помощью Qt Designer, также использует эти классы. Qt Designer полезно использовать при экспериментировании с дизайном формы, поскольку он исключает цикл компиляции, компоновки и запуска, обычно используемый при разработке пользовательского интерфейса.

QBoxLayout

Выравнивает дочерние виджеты по горизонтали или вертикали

QButtonGroup

Контейнер для организации групп кнопочных виджетов

QFormLayout

Управляет формами входных виджетов и их соответствующих меток

QGraphicsAnchor

Представляет якорь между двумя элементами в QGraphicsAnchorLayout

QGraphicsAnchorLayout

Макет, где можно привязать виджеты друг к другу в представлении Graphics View

QGridLayout

Размещает виджеты в сетке

QGroupBox

Рамка группового поля с заголовком

QHBoxLayout

Выравнивает виджеты по горизонтали

QLayout

Базовый класс менеджеров геометрии

QLayoutItem

Абстрактный элемент, который управляет QLayout

QSizePolicy

Атрибут макета, описывающий политику изменения размеров по горизонтали и вертикали

QSpacerItem

Пустое место в макете

QStackedLayout

Стек виджетов, где только один виджет виден в данный момент

QStackedWidget

Стек виджетов, где только один виджет виден в данный момент

QVBoxLayout

Выравнивает виджеты по вертикали

QWidgetItem

Элемент макета, представляющий виджет

Горизонтальные, вертикальные, сетчатые и формовые макеты

Самый простой способ задать хороший макет для ваших виджетов — использовать встроенные менеджеры макетов: QHBoxLayout, QVBoxLayout, QGridLayout и QFormLayout. Эти классы наследуются от QLayout, который, в свою очередь, наследуется от QObject (а не QWidget). Они занимаются управлением геометрией набора виджетов. Для создания более сложных макетов можно вкладывать менеджеры макетов друг в друга.

  • QHBoxLayout выстраивает виджеты в горизонтальный ряд слева направо (или справа налево для языков с письмом справа налево).

  • QVBoxLayout выстраивает виджеты в вертикальную колонку сверху вниз.

  • QGridLayout выстраивает виджеты в двумерную сетку. Виджеты могут занимать несколько ячеек.

  • QFormLayout выстраивает виджеты в форме описательного поля — виджет в 2 столбца с меткой.

Размещение виджетов в коде

Следующий код создает QHBoxLayout, который управляет геометрией пяти QPushButtons, как показано на первом скриншоте выше:

    QWidget *window = new QWidget;
    QPushButton *button1 = new QPushButton("One");
    QPushButton *button2 = new QPushButton("Two");
    QPushButton *button3 = new QPushButton("Three");
    QPushButton *button4 = new QPushButton("Four");
    QPushButton *button5 = new QPushButton("Five");

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);
    layout->addWidget(button4);
    layout->addWidget(button5);

    window->setLayout(layout);
    window->show();

Код для QVBoxLayout идентичен, за исключением строки создания макета. Код для QGridLayout немного отличается, потому что нам нужно указать позицию строки и столбца дочернего виджета:

    QWidget *window = new QWidget;
    QPushButton *button1 = new QPushButton("One");
    QPushButton *button2 = new QPushButton("Two");
    QPushButton *button3 = new QPushButton("Three");
    QPushButton *button4 = new QPushButton("Four");
    QPushButton *button5 = new QPushButton("Five");

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(button1, 0, 0);
    layout->addWidget(button2, 0, 1);
    layout->addWidget(button3, 1, 0, 1, 2);
    layout->addWidget(button4, 2, 0);
    layout->addWidget(button5, 2, 1);

    window->setLayout(layout);
    window->show();

Третья кнопка QPushButton занимает 2 столбца. Это возможно, указав 2 в качестве пятого аргумента для QGridLayout::addWidget().

QFormLayout добавит два виджета в одну строку, обычно QLabel и QLineEdit, чтобы создать форму. Добавление QLabel и QLineEdit в одну строку установит QLineEdit как компаньона QLabel. Следующий код будет использовать QFormLayout для размещения трёх QPushButtons и соответствующего QLineEdit в одной строке.

    QWidget *window = new QWidget;
    QPushButton *button1 = new QPushButton("One");
    QLineEdit *lineEdit1 = new QLineEdit();
    QPushButton *button2 = new QPushButton("Two");
    QLineEdit *lineEdit2 = new QLineEdit();
    QPushButton *button3 = new QPushButton("Three");
    QLineEdit *lineEdit3 = new QLineEdit();

    QFormLayout *layout = new QFormLayout;
    layout->addRow(button1, lineEdit1);
    layout->addRow(button2, lineEdit2);
    layout->addRow(button3, lineEdit3);

    window->setLayout(layout);
    window->show();

Советы по использованию макетов

При использовании макета вам не нужно передавать родителя при построении дочерних виджетов. Макет автоматически переопределяет родительские виджеты (используя QWidget::setParent()), так что они будут дочерними элементами виджета, на котором установлен макет.

Примечание: Виджеты в макете являются дочерними элементами виджета, на котором установлен макет, а не самого макета. Виджеты могут иметь только другие виджеты в качестве родителя, а не макеты.

Вы можете вкладывать макеты, используя addLayout() на макете; внутренний макет затем становится дочерним элементом макета, в который он вставлен.

Добавление виджетов в макет

Когда вы добавляете виджеты в макет, процесс макета работает следующим образом:

  1. Все виджеты изначально выделяют количество пространства в соответствии с их QWidget::sizePolicy() и QWidget::sizeHint().
  2. Если у каких-либо виджетов установлены коэффициенты растяжения, с значением больше нуля, то им выделяется пространство пропорционально коэффициенту растяжения (объяснено ниже).
  3. Если коэффициенты растяжения каких-либо виджетов установлены равными нулю, то они получат дополнительное пространство только в том случае, если другие виджеты не потребуют его. Из них пространство выделяется виджетам с политикой размера Expanding в первую очередь.
  4. Любые виджеты, которым выделено меньше места, чем их минимальный размер (или минимальный размер подсказки, если минимальный размер не указан), получают этот минимальный размер.
  5. Любые виджеты, которым выделено больше места, чем их максимальный размер, получают это максимальное пространство.

Коэффициенты растяжения

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

Если у нас есть три виджета, размещённые с помощью QHBoxLayout без установленных коэффициентов растяжения, мы получим макет, похожий на этот:

Three widgets in a row

Если мы применяем коэффициенты растяжения к каждому виджету, они будут расположены пропорционально (но никогда меньше, чем их минимальная подсказка размера), например:

Three widgets with different stretch factors in a row

Настраиваемые виджеты в макетах

При создании собственного класса виджета вы также должны указать его свойства макета. Если виджет использует макеты Qt, это уже выполняется автоматически. Если виджет не имеет дочерних виджетов или использует ручной макет, вы можете изменить поведение виджета, используя любые или все из следующих механизмов:

  • Переопределите QWidget::sizeHint() для возвращения предпочтительного размера виджета.
  • Переопределите QWidget::minimumSizeHint() для возвращения минимального размера виджета.
  • Вызовите QWidget::setSizePolicy() для указания требований к пространству виджета.

Вызовите QWidget::updateGeometry() всякий раз, когда изменяются подсказка размера, минимальная подсказка размера или политика размера. Это вызовет перерасчёт макета. Несколько последовательных вызовов QWidget::updateGeometry() приведут только к одному перерасчёту макета.

Если предпочтительная высота вашего виджета зависит от его фактической ширины (например, метка с автоматическим переносом слов), установите флаг «высота по ширине» в политике размера виджета и переопределите QWidget::heightForWidth().

Даже если вы реализуете QWidget::heightForWidth(), всё равно стоит указать разумную подсказку размера.

Дополнительные рекомендации по реализации этих функций см. в статье «Торговля высотой за ширину» в журнале Qt Quarterly.

Проблемы с макетами

Использование форматированного текста в виджете метки может создать некоторые проблемы с макетом его родительского виджета. Проблемы возникают из-за того, как Qt менеджеры макета обрабатывают форматированный текст, когда метка переносит слова на следующую строку.

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

В некоторых случаях это актуально при добавлении макета в виджет. При установке виджета QDockWidget или QScrollArea (с помощью QDockWidget::setWidget() и QScrollArea::setWidget()), макет должен быть уже установлен на виджете. В противном случае виджет не будет отображаться.

Рукописный макет

Если вы создаёте уникальный специальный макет, вы также можете создать пользовательский виджет, как описано выше. Переопределите QWidget::resizeEvent(), чтобы рассчитать необходимое распределение размеров и вызвать setGeometry() для каждого дочернего элемента.

Виджет получит событие типа QEvent::LayoutRequest, когда макет необходимо пересчитать. Переопределите QWidget::event(), чтобы обработать события QEvent::LayoutRequest.

Как написать пользовательский менеджер макета

Альтернативой ручному макету является создание собственного менеджера макета путём создания подкласса QLayout. Примеры Border Layout и Flow Layout показывают, как это сделать.

Здесь мы подробно рассмотрим пример. Класс CardLayout вдохновлён одноимённым менеджером макета Java. Он размещает элементы (виджеты или вложенные макеты) друг над другом, смещая каждый элемент на величину QLayout::spacing().

Для написания собственного класса макета необходимо определить следующее:

  • Структуру данных для хранения элементов, обрабатываемых макетом. Каждый элемент является QLayoutItem. В этом примере мы будем использовать QList.
  • addItem(), способ добавления элементов в макет.
  • setGeometry(), способ выполнения макета.
  • sizeHint(), предпочтительный размер макета.
  • itemAt(), способ итерирования по макету.
  • takeAt(), способ удаления элементов из макета.

В большинстве случаев также необходимо реализовать minimumSize().

Файл заголовка (card.h)

#ifndef CARD_H
#define CARD_H

#include <QtWidgets>
#include <QList>

class CardLayout : public QLayout
{
public:
    CardLayout(QWidget *parent, int dist): QLayout(parent, 0, dist) {}
    CardLayout(QLayout *parent, int dist): QLayout(parent, dist) {}
    CardLayout(int dist): QLayout(dist) {}
    ~CardLayout();

    void addItem(QLayoutItem *item) override;
    QSize sizeHint() const override;
    QSize minimumSize() const override;
    int count() const override;
    QLayoutItem *itemAt(int) const override;
    QLayoutItem *takeAt(int) override;
    void setGeometry(const QRect &rect) override;

private:
    QList<QLayoutItem*> list;
};
#endif

Файл реализации (card.cpp)

//#include "card.h"

Сначала мы определяем count() для получения количества элементов в списке.

int CardLayout::count() const
{
    // QList::size() returns the number of QLayoutItems in the list
    return list.size();
}

Затем мы определяем две функции, которые итерируются по макету: itemAt() и takeAt(). Эти функции используются внутренним образом системой макета для обработки удаления виджетов. Они также доступны для разработчиков приложений.

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

QLayoutItem *CardLayout::itemAt(int idx) const
{
    // QList::value() performs index checking, and returns 0 if we are
    // outside the valid range
    return list.value(idx);
}

QLayoutItem *CardLayout::takeAt(int idx)
{
    // QList::take does not do index checking
    return idx >= 0 && idx < list.size() ? list.takeAt(idx) : 0;
}

addItem() реализует стратегию размещения по умолчанию для элементов макета. Эта функция должна быть реализована. Она используется QLayout::add(), а также конструктором QLayout, принимающим макет в качестве родительского элемента. Если ваш макет имеет расширенные параметры размещения, требующие параметров, вы должны предоставить дополнительные функции доступа, такие как перегрузки QGridLayout::addItem(), QGridLayout::addWidget() и QGridLayout::addLayout() для указания строки и столбца.

void CardLayout::addItem(QLayoutItem *item)
{
    list.append(item);
}

Макет берет на себя ответственность за добавленные элементы. Поскольку QLayoutItem не наследует от QObject, мы должны удалять элементы вручную. В деструкторе мы удаляем каждый элемент из списка с помощью takeAt(), а затем удаляем его.

CardLayout::~CardLayout()
{
     QLayoutItem *item;
     while ((item = takeAt(0)))
         delete item;
}

Функция setGeometry() фактически выполняет макет. Прямоугольник, переданный в качестве аргумента, не включает margin(). При необходимости используйте spacing() в качестве расстояния между элементами.

void CardLayout::setGeometry(const QRect &r)
{
    QLayout::setGeometry(r);

    if (list.size() == 0)
        return;

    int w = r.width() - (list.count() - 1) * spacing();
    int h = r.height() - (list.count() - 1) * spacing();
    int i = 0;
    while (i < list.size()) {
        QLayoutItem *o = list.at(i);
        QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
        o->setGeometry(geom);
        ++i;
    }
}

sizeHint() и minimumSize() обычно очень похожи по реализации. Возвращаемые размерами обеих функций должны включать spacing(), но не margin().

QSize CardLayout::sizeHint() const
{
    QSize s(0,0);
    int n = list.count();
    if (n > 0)
        s = QSize(100,70); //start with a nice default size
    int i = 0;
    while (i < n) {
        QLayoutItem *o = list.at(i);
        s = s.expandedTo(o->sizeHint());
        ++i;
    }
    return s + n*QSize(spacing(), spacing());
}

QSize CardLayout::minimumSize() const
{
    QSize s(0,0);
    int n = list.count();
    int i = 0;
    while (i < n) {
        QLayoutItem *o = list.at(i);
        s = s.expandedTo(o->minimumSize());
        ++i;
    }
    return s + n*QSize(spacing(), spacing());
}

Дополнительные заметки

  • Этот пользовательский макет не обрабатывает высоту для ширины.
  • Мы игнорируем QLayoutItem::isEmpty(); это означает, что макет будет рассматривать скрытые виджеты как видимые.
  • Для сложных макетов скорость можно значительно увеличить, кэшируя рассчитанные значения. В этом случае реализуйте QLayoutItem::invalidate() для маркировки кэшированных данных как устаревших.
  • Вызов QLayoutItem::sizeHint() и т. д. может быть затратным. Поэтому, если вам нужно значение снова позже в той же функции, вы должны сохранить его в локальной переменной.
  • Не следует вызывать QLayoutItem::setGeometry() дважды для одного и того же элемента в одной и той же функции. Этот вызов может быть очень затратным, если у элемента есть несколько дочерних виджетов, потому что менеджер макета должен каждый раз выполнять полный макет. Вместо этого рассчитайте геометрию и затем установите её. (Это относится не только к макетам, вы должны делать то же самое, если, например, реализуете собственную resizeEvent()).

Примеры макетов

Многие примеры виджетов Qt уже используют макеты, однако существуют несколько примеров, демонстрирующих различные типы макетов.

Обучающее пособие по адресной книге

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

Пример макета Border Layout

Показывает, как расположить дочерние виджеты вдоль границы.

Пример калькулятора

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

Пример виджета календаря

Пример виджета календаря демонстрирует использование QCalendarWidget.

Пример плагина Echo

Этот пример демонстрирует, как создать плагин Qt.

Пример макета Flow Layout

Показывает, как размещать виджеты для различных размеров окна.

Пример композиции изображений

Показывает, как работают режимы композиции в QPainter.

Пример меню

Пример меню демонстрирует, как использовать меню в приложении главного окна.

Пример простого древовидного модели

Пример простого древовидного модели показывает, как использовать иерархическую модель с стандартными классами представлений Qt.

Sub-Attaq

Этот пример демонстрирует возможность Qt сочетать механизм анимации и механизм состояния для создания игры.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.11/layout.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API