QML

Anchors и layout-системы Qt Quick: позиционирование без абсолютных координат

Введение

Предыдущие статьи цикла иногда использовали абсолютные координаты (x: 50; y: 50) для простоты иллюстрации — в реальных приложениях позиционирование почти всегда строится через anchors (якоря) или layout-элементы, дающие декларативное, автоматически адаптирующееся позиционирование без явного, «хрупкого» указания точных пиксельных координат.

Концепция

Anchors позволяют привязать край или центр одного элемента к краю/центру другого элемента (обычно родителя или соседа) — anchors.left: parent.left, anchors.centerIn: parent, anchors.fill: parent — что автоматически поддерживает корректное позиционирование при изменении размеров родителя или других элементов, на которые сделана привязка, без необходимости вручную пересчитывать координаты. Layout-элементы (Row, Column, Grid, а также более продвинутые RowLayout/ColumnLayout/GridLayout из модуля Qt Quick Layouts) автоматически располагают дочерние элементы согласно заданной схеме (в ряд, в столбец, в сетку), беря на себя автоматический пересчёт позиций при изменении количества дочерних элементов или их размеров, что особенно ценно при динамическом, изменяющемся во время выполнения наборе элементов.

Пример кода: Anchors и layout-системы Qt Quick

// Anchors — позиционирование через привязку краёв, без абсолютных координат
import QtQuick

Rectangle {
    id: root
    width: 400; height: 300

    Rectangle {
        id: header
        anchors.top: root.top
        anchors.left: root.left
        anchors.right: root.right
        height: 60
        color: "darkslateblue"
    }

    Rectangle {
        id: sidebar
        anchors.top: header.bottom // привязка к НИЖНЕМУ краю header, а не к root напрямую
        anchors.bottom: root.bottom
        anchors.left: root.left
        width: 100
        color: "lightgray"
    }

    Rectangle {
        id: content
        anchors.top: header.bottom
        anchors.bottom: root.bottom
        anchors.left: sidebar.right // привязка к ПРАВОМУ краю sidebar
        anchors.right: root.right
        color: "white"
    }
    // При изменении root.width/height ВСЯ компоновка автоматически адаптируется —
    // ни одна координата не была указана в абсолютных пикселях напрямую
}
// RowLayout/ColumnLayout — Qt Quick Layouts для автоматического распределения пространства
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls

ColumnLayout {
    anchors.fill: parent
    spacing: 8

    RowLayout {
        Layout.fillWidth: true
        Label { text: "Имя:" }
        TextField {
            Layout.fillWidth: true // занимает всё ОСТАВШЕЕСЯ горизонтальное пространство
        }
    }

    Button {
        text: "Отправить"
        Layout.alignment: Qt.AlignRight
    }
}

Пояснения к коду

Цепочка привязок header → sidebar (привязанный к header.bottom) → content (привязанный к header.bottom и sidebar.right) показывает типичный паттерн построения макета «шапка-боковая панель-контент» — каждый элемент привязан к соседним элементам, а не к абсолютным координатам, и весь макет автоматически адаптируется при изменении размеров root без какого-либо дополнительного кода для пересчёта позиций. Layout.fillWidth: true для TextField внутри RowLayout демонстрирует ключевую возможность Qt Quick Layouts, недоступную через простые anchors напрямую — автоматическое распределение оставшегося доступного пространства между несколькими дочерними элементами, что особенно важно для форм и других интерфейсов с переменным количеством или размером элементов, заранее не предсказуемым жёстко зашитыми anchors.

Подводные камни

  • Смешивание anchors и явных абсолютных координат (x/y/width/height) для одного и того же элемента без понимания, как они взаимодействуют — если для одного измерения (например, ширины) задан как явный width, так и одновременно противоречащая ему пара anchors.left/anchors.right (которая сама определяет ширину через расстояние между двумя точками привязки), возникает конфликт, разрешаемый движком QML не всегда интуитивно очевидным образом, и для предсказуемого результата стоит явно выбрать один из подходов (либо anchors определяют размер, либо явный width) для каждого конкретного измерения каждого элемента.
  • Создание циклических привязок anchors (элемент A привязан к элементу B, который одновременно привязан обратно к элементу A) — аналогично циклическим привязкам свойств (статья 481), это приводит к неопределённому или ошибочному поведению, и граф зависимостей anchors должен оставаться ацикличным, как и для обычных привязок свойств.
  • Использование anchors для элементов без общего родителя (или не находящихся в отношении «родитель-потомок»/»соседи через общего родителя») — anchors корректно работают только для элементов в определённых, ограниченных отношениях друг к другу (один к другому при общем родителе, или потомок к собственному родителю), и попытка привязать произвольные, не связанные подобным образом элементы друг к другу не сработает или приведёт к ошибке.
  • Использование исключительно anchors для сложных, динамических по содержимому макетов, где более подходящим решением были бы Layout-элементы — anchors отлично подходят для относительно статичной, заранее известной структуры (как пример «шапка-сайдбар-контент»), но для динамически изменяющегося количества или порядка элементов (формы с переменным набором полей, списки настраиваемой длины) ручное построение цепочки anchors значительно более хрупко и многословно по сравнению с использованием RowLayout/ColumnLayout/GridLayout, автоматически адаптирующихся к изменению набора дочерних элементов.