Синтаксис QML

Добавлено 1 марта 2022 в 04:34

QML – это декларативный язык, используемый для описания того, как объекты связаны друг с другом. QtQuick – это фреймворк, построенный на QML для создания пользовательского интерфейса вашего приложения. Он разбивает пользовательский интерфейс на более мелкие элементы, которые можно объединить в компоненты. QtQuick описывает внешний вид и поведение этих элементов пользовательского интерфейса. Это описание пользовательского интерфейса можно дополнить кодом JavaScript, чтобы обеспечить не только простую, но и более сложную логику. С этой точки зрения он следует шаблону HTML-JavaScript, но QML и QtQuick разработаны с нуля для описания пользовательских интерфейсов, а не текстовых документов.

В своей простейшей форме QtQuick позволяет создавать иерархию элементов. Дочерние элементы наследуют систему координат от родителя. Координаты x,y всегда выражается относительно родителя.

Подсказка

QtQuick основан на QML. Язык QML знает только об элементах, свойствах, сигналах и привязках. QtQuick – это фреймворк, построенный на QML. Используя свойства по умолчанию, можно элегантным образом построить иерархию элементов QtQuick.

Пример иерархии элементов QtQuick

Давайте начнем с примера простого файла QML, чтобы объяснить его синтаксис.

// RectangleExample.qml

import QtQuick

// Корневой элемент - это Rectangle
Rectangle {
    // назовем этот элемент root
    id: root

    // свойства: <имя>: <значение>
    width: 120; height: 240

    // свойство цвета
    color: "#4A4A4A"

    // объявляем вложенный элемент (дочерний к корневому)
    Image {
        id: triangle

        // ссылка на родительский элемент
        x: (parent.width - width)/2; y: 40

        source: 'assets/triangle_red.png'
    }

    // еще один дочерний элемент корневого элемента
    Text {
        // этот элемент безымянный

        // ссылка на элемент по id
        y: triangle.y + triangle.height + 20

        // ссылка на корневой элемент
        width: root.width

        color: 'white'
        horizontalAlignment: Text.AlignHCenter
        text: 'Triangle'
    }
}
  • Инструкция import импортирует модуль. Можно добавить необязательную версию в виде <мажорная версия>.<минорная версия>.
  • Комментарии можно делать с помощью // для однострочных комментариев или /* */ для многострочных комментариев. Так же, как в C/C++ и JavaScript
  • Каждый файл QML должен иметь ровно один корневой элемент, как в HTML.
  • Элемент объявляется по его типу, за которым следует {}.
  • Элементы могут иметь свойства, они представлены в форме имя:значение.
  • Доступ к произвольным элементам внутри документа QML можно получить, используя их id (идентификатор без кавычек).
  • Элементы могут быть вложенными, то есть родительский элемент может иметь дочерние элементы. Доступ к родительскому элементу можно получить с помощью ключевого слова parent

С помощью инструкции import вы импортируете модуль QML по имени. В Qt5 вам нужно было указать мажорную и минорную версии (например, 2.15), теперь в Qt6 это необязательно. В книге мы опускаем этот необязательный номер версии, поскольку обычно вы хотите автоматически выбрать самую новую версию, доступную в выбранной вами сборке Qt.

Совет

Часто вы хотите получить доступ к определенному элементу по id или к родительскому элементу, используя ключевое слово parent. Поэтому рекомендуется называть корневой элемент «root», используя id: root. Тогда вам не нужно будет думать о том, как называется корневой элемент в вашем документе QML.

Совет

Вы можете запустить этот пример с помощью среды выполнения Qt Quick из командной строки вашей ОС следующим образом:

$ $QTDIR/bin/qml RectangleExample.qml

Где вам нужно заменить $QTDIR на путь к вашей установке Qt. Исполняемый файл qml инициализирует среду выполнения Qt Quick и интерпретирует предоставленный файл QML.

В Qt Creator вы можете открыть соответствующий файл проекта и запустить документ RectangleExample.qml.

Свойства

Элементы объявляются с использованием имени элемента, но определяются с помощью их свойств или путем создания пользовательских свойств. Свойство представляет собой простую пару ключ-значение, например. width: 100, text: 'Greetings', color: '#FF0000'. Свойство имеет четко определенный тип и может иметь начальное значение.

Text {
    // (1) идентификатор
    id: thisLabel

    // (2) устанавливает позицию по x и y
    x: 24; y: 16

    // (3) привязать высоту к 2 * ширина
    height: 2 * width

    // (4) пользовательское свойство
    property int times: 24

    // (5) псевдоним свойства
    property alias anotherTimes: thisLabel.times

    // (6) установить текст с добавленным значением
    text: "Greetings " + times

    // (7) шрифт является сгруппированным свойством
    font.family: "Ubuntu"
    font.pixelSize: 24

    // (8) KeyNavigation - это прикрепленное свойство
    KeyNavigation.tab: otherLabel

    // (9) обработчик сигнала для изменения свойства
    onHeightChanged: console.log('height:', height)

    // фокус нужен для получения событий клавиатуры
    focus: true

    // меняем цвет в зависимости от значения фокуса
    color: focus ? "red" : "black"
}

Давайте рассмотрим различные особенности свойств:

  1. id – это специальное значение, похожее на свойство, оно используется для ссылки на элементы внутри файла QML (называемого «документом»). id не является строковым типом, а является идентификатором и частью синтаксиса QML. id должен быть уникальным внутри документа, его нельзя сбросить на другое значение или запросить (он ведет себя так же, как ссылка в мире C++).
  2. Свойству может быть присвоено значение в зависимости от его типа. Если для свойства не задано значение, будет выбрано начальное значение. Для получения дополнительной информации о начальном значении свойства вам необходимо обратиться к документации конкретного элемента.
  3. Свойство может зависеть от одного или многих других свойств. Это называется привязкой. Связанное свойство обновляется при изменении свойств, от которых оно зависит. Это работает как контракт, в этом случае высота, height, всегда должна быть в два раза больше ширины, width.
  4. Добавление новых свойств к элементу выполняется с использованием квалификатора property, за которым следует тип, имя и необязательное начальное значение (property <тип> <имя> : <значение>). Если начальное значение не задано, выбирается начальное значение по умолчанию.

Совет

Вы также можете объявить одно свойство свойством по умолчанию, используя ключевое слово default. Если внутри элемента создается другой элемент, и он явно не привязан к свойству, он привязывается к свойству по умолчанию. Например, это используется при добавлении дочерних элементов. Дочерние элементы автоматически добавляются в свойство по умолчанию children типа список, если они являются видимыми.

  1. Еще одним важным способом объявления свойств является использование ключевого слова alias (property alias <имя>: <ссылка>). Ключевое слово alias позволяет нам перенаправить свойство объекта или сам объект из типа во внешнюю область. Мы будем использовать эту технику позже при определении компонентов для экспорта внутренних свойств или идентификаторов элементов на корневой уровень. Псевдоним свойства не нуждается в типе, он использует тип свойства или объекта, на который ссылается.
  2. Свойство text зависит от пользовательского свойства times типа int. Значение int автоматически преобразуется в тип string. Само выражение является еще одним примером привязки и приводит к обновлению текста при каждом изменении свойства times.
  3. Некоторые свойства являются сгруппированными свойствами. Эта функция используется, когда свойство структурировано, и связанные свойства должны быть сгруппированы вместе. Другой способ записи сгруппированных свойств – это font { family: "Ubuntu"; pixelSize: 24 }.
  4. Некоторые свойства принадлежат самому классу элементов. Это делается для элементов глобальных настроек, которые появляются в приложении только один раз (например, ввод с клавиатуры). Они записываются следующим образом: <Элемент>.<свойство>: <значение>.
  5. Для каждого свойства можно указать обработчик сигнала. Этот обработчик вызывается после изменения свойства. Например, здесь мы хотим получать уведомления при изменении высоты и использовать встроенную консоль для логгирования сообщения в системе.

ПРЕДУПРЕЖДЕНИЕ

Идентификатор элемента следует использовать только для ссылки на элементы внутри вашего документа (например, текущий файл). QML предоставляет механизм, называемый «динамической областью видимости», при котором документы, загруженные позже, перезаписывают идентификаторы элементов из документов, загруженных ранее. Это позволяет ссылаться на идентификаторы элементов из ранее загруженных документов, если они еще не были перезаписаны. Это похоже на создание глобальных переменных. К сожалению, на практике это часто приводит к очень плохому коду, когда программа зависит от порядка выполнения. К сожалению, это нельзя отключить. Пожалуйста, используйте это с осторожностью; или, что еще лучше, вообще не используйте этот механизм. Лучше экспортировать элемент, который вы хотите предоставить внешнему миру, используя свойства корневого элемента вашего документа.

Скрипты

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

Text {
    id: label

    x: 24; y: 24

    // пользовательское свойство счетчика нажатий пробела
    property int spacePresses: 0

    text: "Space pressed: " + spacePresses + " times"

    // (1) обработчик изменений текста. 
    // Необходимо использовать функцию для захвата параметров
    onTextChanged: function(text) { 
        console.log("text changed to:", text)
    }

    // нужен фокус для получения событий клавиатуры
    focus: true

    // (2) обработчик с каким-то JS
    Keys.onSpacePressed: {
        increment()
    }

    // очищаем текст при нажатии Esc
    Keys.onEscapePressed: {
        label.text = ''
    }

    // (3) функция JS
    function increment() {
        spacePresses = spacePresses + 1
    }
}
  1. Обработчик изменения текста onTextChanged печатает текущий текст каждый раз, когда текст изменяется из-за нажатия пробела. Поскольку мы используем параметр, введенный сигналом, нам нужно использовать здесь синтаксис функции. Также можно использовать стрелочную функцию ((text) => {}), но нам кажется, что function(text) {} более удобочитаемо.
  2. Когда текстовый элемент получает нажатие клавишу пробела (потому что пользователь нажал пробел на клавиатуре), мы вызываем функцию JavaScript increment().
  3. Функция JavaScript определяется в виде function <имя>(<параметры>) { ... }, которая увеличивает наш счетчик spacePressed. Каждый раз, когда spacePressed увеличивается, связанные свойства также будут обновляться.

Привязка

Разница между QML : (привязка) и JavaScript = (присваивание) заключается в том, что привязка является контрактом и остается действующей в течение всего времени существования привязки, тогда как присваивание JavaScript (=) является однократным присвоением значения.

Срок действия привязки заканчивается, когда для свойства устанавливается новая привязка, или, даже когда свойству присваивается значение с помощью JavaScript. Например, обработчик клавиатуры, устанавливающий для свойства text пустую строку, уничтожит наше отображение увеличивающегося счетчика:

Keys.onEscapePressed: {
    label.text = ''
}

После нажатия клавиши Escape нажатие пробела больше не будет обновлять отображение, так как предыдущая привязка свойства text (text: “Space pressed: ” + spacePresses + ” times”) была уничтожена.

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

Теги

QMLQtQtQuickОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.


  • 2022-12-13Алексей

    Наверное, стоит упомянуть про Qt.binding(), которая может использоваться в блоке js кода