Spec-Zone.ru › Qt 6.0

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

Система макетов 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(window);
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);
    layout->addWidget(button4);
    layout->addWidget(button5);

    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(window);
    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->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(window);
    layout->addRow(button1, lineEdit1);
    layout->addRow(button2, lineEdit2);
    layout->addRow(button3, lineEdit3);

    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(), рекомендуется указать разумное значение sizeHint().

END_OF_DOCUMENT_MARKER ```

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

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

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

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

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

Ручной макет

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

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

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

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

Здесь мы представим пример подробно. Класс 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(int spacing): QLayout()
    { setSpacing(spacing); }
    CardLayout(int spacing, QWidget *parent): QLayout(parent)
    { setSpacing(spacing); }
    ~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 *> m_items;
};
#endif

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

//#include "card.h"

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

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

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

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

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

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

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

void CardLayout::addItem(QLayoutItem *item)
{
    m_items.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 (m_items.size() == 0)
        return;

    int w = r.width() - (m_items.count() - 1) * spacing();
    int h = r.height() - (m_items.count() - 1) * spacing();
    int i = 0;
    while (i < m_items.size()) {
        QLayoutItem *o = m_items.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 = m_items.count();
    if (n > 0)
        s = QSize(100, 70); //start with a nice default size
    int i = 0;
    while (i < n) {
        QLayoutItem *o = m_items.at(i);
        s = s.expandedTo(o->sizeHint());
        ++i;
    }
    return s + n * QSize(spacing(), spacing());
}

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

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

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

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

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

Учебник по адресной книге

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

Пример макета с границами

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

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

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

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

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

Пример плагина эха

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

Пример макета потока

Демонстрирует, как организовать виджеты для различных размеров окна.

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

Демонстрирует, как работают режимы композиции в QPainter.

Пример меню

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

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

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

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

Spec-Zone.ru

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