Часть 1 - компьютер
часть 2 - мобильный
часть 3 - телевизор
В предыдущей статье мы в общих чертах коснулись того, почему вам следует использовать Qt / QML для разработки приложений, и предоставили небольшой код, чтобы показать, чего можно ожидать.
В этом мы собираемся разработать один из наиболее часто используемых примеров - приложение TODO, работающее на нескольких платформах.
Хотя я не совсем фанат такого приложения, показывающего возможность некоторого фреймворка, люди все же привыкли к нему, чтобы быстро почувствовать вкус фреймворка.
Чтобы увидеть больше и лучших примеров, посетите официальную документацию!
Макет пользовательского интерфейса
Прежде всего, мы должны подумать о том, как структурировано наше приложение. В общем, мы говорим о структурировании компонентов пользовательского интерфейса. В этом нет ничего нового, и здесь мы принимаем во внимание, что у вас есть опыт разработки пользовательского интерфейса и фреймворков пользовательского интерфейса, так что это то, к чему вы привыкли.
Поскольку это очень простое приложение, мы создаем наш простой макет:
Мы сразу видим макет и компоненты, составляющие наше приложение.
Есть одно поле ввода и одна кнопка для добавления новых элементов. Они будут расположены рядом друг с другом.
Затем у нас есть один компонент для представления добавленных данных. Это будет (прокручиваемый) список наших задач, каждую из которых можно рассматривать как один компонент, снова содержащий одно поле ввода и одну кнопку.
Настройка приложения QML
Как упоминалось в предыдущей статье, вы можете найти примеры того, как настроить среду, в официальной документации.
Когда ваша среда будет готова, мы создадим окно нашего приложения.
В этих примерах мы ожидаем Qt 5.12 LTS (вы можете использовать более старые версии, но тогда вам нужно уменьшить версию импорта, используемую в этих примерах, то есть использовать QtQuick 2.9).
Это ничем не отличается от того, что сделано в официальной документации, поэтому мы начнем с этого:
import QtQuick 2.12 import QtQuick.Window 2.12 Window { visible: true width: 480 height: 640 color: "#333333" title: qsTr("Todo") }
Здесь мы немного продвинулись вперед, но давайте объясним это. Компонент Окно имеет атрибут цвета. Это мы можем использовать для установки цвета фона. По умолчанию белый, поэтому мы изменили его на темно-серый. Обратите внимание, что вы можете писать серый черный или использовать значение RGB, как подробно описано в https://doc.qt.io/qt-5/qml-color.html.
qsTr используется как часть локализации. Хотя это выходит за рамки данного руководства, не стесняйтесь посетить связанную документацию, чтобы лучше понять. Здесь мы показываем это, поскольку в этом нет вреда, поэтому всегда полезно использовать его, даже если вы не планируете поддерживать несколько языков вначале, поскольку это, вероятно, произойдет в будущем.
Теперь, если вы запустите это на своем ПК, вы получите что-то вроде:
Подожди, ПК?
Да! Таким образом, даже если мы пишем мобильное приложение для Android / iOS, преимущество состоит в том, что мы можем запускать точно такое же приложение на нашем ПК, будь то Linux, Windows или MacOS.
Атрибуты ширины / высоты будут автоматически адаптированы по умолчанию в соответствии с размером вашего мобильного устройства при его запуске, а на ПК оно будет использовать указанные нами значения.
Таким образом, даже если мы нацелены только на мобильные приложения, мы можем быстро создавать прототипы, тестировать и разрабатывать, не тестируя их на реальном устройстве или эмуляторе.
Подробнее о развертывании приложений Qt для различных платформ можно найти в официальных документах.
QMLScene
Так что даже если у вас есть готовая установка приложения, есть еще более простой способ протестировать фрагмент кода и посмотреть, как он будет выглядеть.
QMLScene - это двоичный файл, который вы можете использовать для создания приложений QML, просто записав QML в файл - как всегда, см. Официальную документацию для отличных примеров.
Например, поместив это в app.qml
:
import QtQuick 2.12 import QtQuick.Controls 2.12 TextField { placeholderText: qsTr("New") onTextChanged: text === "quit" && Qt.quit() }
и запуск его как qmlscene app.qml
приведет к:
А написание «выйти» закроет приложение.
Компоненты пользовательского интерфейса
Мы знаем, что у нас есть поле ввода и кнопка, которая позволит нам добавлять новый контент.
QML поставляется с большим набором доступных элементов управления, которые мы можем использовать для этой цели.
При использовании элементов управления QML убедитесь, что вы используете версию 2 https://doc.qt.io/qt-5/qtquickcontrols-index.html, поскольку вы можете запутаться со старой версией, которая имеет аналогичные компоненты.
Поле ввода и кнопка добавления
В данном случае нас интересуют TextField и RoundButton. Для более широкого списка и того, что они могут делать, см. Официальную документацию.
Прочитав документацию к обоим, довольно легко увидеть, как они используются.
Чтобы использовать их, мы сначала импортируем их:
import QtQuick.Controls 2.12
Теперь мы можем просто написать:
TextField { placeholderText: qsTr("New") }
Это очевидно, но при этом будет создано текстовое поле с текстом-заполнителем внутри: «New».
Еще нам нужна кнопка:
RoundButton { text: "+" }
Этот, вероятно, даже более понятен.
Прокручиваемый список
Последний и самый интересный компонент - это наш прокручиваемый список, для которого мы собираемся использовать ListView.
Здесь начинается настоящая работа, потому что в списке должны отображаться некоторые данные. Как часть этого, нужна модель для хранения наших данных и их рендеринга. Это может быть простой массив, полученный с сервера, но QML также предоставляет нам эффективный невизуальный компонент ListModel.
Этот ListModel можно использовать для разных целей, но ListView прекрасно может использовать его для визуализации данных, содержащихся в ListModel.
Чтобы узнать больше о возможных моделях, посетите официальную документацию по теме.
Итак, в этом случае мы бы создали что-то вроде:
ListModel { id: todoModel } ListView { model: todoModel }
Но чтобы лучше представить, что происходит, давайте свяжем официальные документы по теме:
Проще говоря, приложениям необходимо формировать данные и отображать данные. Qt Quick имеет понятие моделей, представлений и делегатов для отображения данных. Они модулируют визуализацию данных, чтобы дать разработчику или дизайнеру контроль над различными аспектами данных. Разработчик может заменить представление списка представлением сетки с небольшими изменениями данных. Точно так же инкапсуляция экземпляра данных в делегате позволяет разработчику диктовать, как представлять или обрабатывать данные.
Чтобы продолжить, мы фактически будем управлять данными в модели, в то время как рендеринг происходит автоматически с предоставленным делегатом, который содержит визуальное представление.
Когда мы добавляем или обновляем данные в нашей ListModel с помощью одного из таких методов, как .append, .insert или .set, это будет автоматически отображаться без дополнительной логики для делегата.
Верстка
В QML есть несколько способов позиционирования элементов. Как всегда, посетите официальную документацию по теме Важные концепции Qt Quick - Позиционирование.
В этом примере мы будем использовать Макеты. Они специально разработаны для отзывчивых интерфейсов, и когда вы освоите их, с ними действительно приятно работать. После многих лет работы со всеми способами позиционирования элементов в QML я всегда обращаюсь к макетам, когда что-то становится сложным. Они также требуют меньше кода в целом из-за разумных значений по умолчанию, и их легко понять, просто заметив их в коде, не вдаваясь в подробности.
Итак, давайте расширим наш пример, чтобы показать, что можно сразу понять, что произойдет:
RowLayout { TextField { placeholderText: qsTr("New") } RoundButton { text: "+" } }
Легко объяснить, что мы получим текстовое поле рядом с кнопкой (в той же строке), даже не читая свойства, установленные для каждого поля.
Макеты удобны еще и тем, что у них есть значения по умолчанию, которые подходят для создания элементов в интерфейсах. Например, интервал уже включен (который можно изменить с помощью свойства «интервал»), и они выполняют автоматическое центрирование по вертикали / горизонтали.
Объединяя все вместе
import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 Window { visible: true width: 480 height: 640 color: "#333333" title: qsTr("Todo") // prevent window resize below these values minimumWidth: 200 minimumHeight: 200 // model to manage our data ListModel { id: todoModel } // show children in a column (one below another) // this will show input field and button above // and scrolling list below ColumnLayout { anchors.fill: parent anchors.margins: 15 // show children in a row (one next to each other) RowLayout { Layout.fillWidth: true spacing: 15 // show this above our todoList view z: 1 TextField { id: todoInputField placeholderText: qsTr("New") Layout.fillWidth: true } RoundButton { text: "\u2795" // unicode heavy plus sign onClicked: { // add data to our model todoModel.append({ content: todoInputField.text }) todoInputField.text = '' } } } ListView { id: todoList // use the model we defined above to render list model: todoModel Layout.fillWidth: true Layout.fillHeight: true delegate: ColumnLayout { width: todoList.width height: 55 RowLayout { Layout.fillWidth: true spacing: 15 TextField { color: "#fff" background: Item {} // render data from model text: model.content Layout.fillWidth: true } RoundButton { text: "\u2796" // unicode heavy minus sign // remove this element from model onClicked: todoModel.remove(model.index) height: parent.height } } Rectangle { color: "#666"; height: 1; Layout.fillWidth: true } } } } }
Что приводит к:
Так что мы добавили немного больше, но давайте рассмотрим это по крупицам, поскольку здесь нет ничего особенного.
Мы используем ColumnLayout, чтобы расположить наши элементы друг над другом. Таким образом, мы рассматриваем поле ввода и кнопку как один элемент - завернутый в RowLayout, чтобы они располагались рядом друг с другом, и ListView как другой элемент.
Мы установили некоторые свойства для всех из них, но все они довольно просты. z:1
- единственный, который, вероятно, нуждается в дополнительных объяснениях, и, как всегда, официальные документы помогут вам.
Просто имейте в виду, что z-порядок по умолчанию установлен в том порядке, в котором вы определили свои элементы для рендеринга. Итак, в этом случае удалите свойство z, чтобы увидеть поведение:
Это не то, что мы хотим. Теперь мы могли бы использовать свойство clip и поставить clip:true
, чтобы не позволять ListView отображать за его границы, но это имеет некоторые последствия для производительности, поэтому гораздо лучше установить правильный z-порядок.
Итак, с увеличением z:1
мы получаем желаемое поведение:
Теперь самое интересное начинает происходить с TextField, которое мы идентифицировали с помощью свойства id todoInputField.
. Это позволяет нам легко получить доступ к этому компоненту и каждому открытому методу.
Поэтому мы используем его просто при нажатии на кнопку:
onClicked: { // add data to our model todoModel.append({ content: todoInputField.text }) todoInputField.text = '' }
Сначала мы помещаем данные в нашу модель, подключая логику к сигналу onClicked компонента RoundButton. Сразу же мы можем начать писать код Javascript, который также имеет доступ к нашему todoInputField
(см. Область QML), поэтому после добавления данных мы также очищаем текстовое поле.
Другие интересные вещи происходят в ListView. Когда мы предоставляем модель ListView, он пытается отобразить данные, отображая компонент в делегате для каждого элемента в ListModel.
Всякий раз, когда в ListModel добавляется, удаляется или обновляется элемент, ListView получает информацию и соответствующим образом обновляет представление.
Как теперь получить доступ к данным в делегате из модели?
Что ж, это очень просто. Когда мы помещаем некоторые данные в ListModel, мы создаем их с некоторыми свойствами, в нашем случае {content: "something"}
.
Это автоматически включит использование этого ключевого слова в делегате. Так что просто попытка использовать свойство content
будет работать. Однако, чтобы было понятнее, использование model.content
также будет работать и не создает проблем с областью видимости (у вас может быть свойство содержимого в некоторых внешних компонентах, к которым вы также хотите получить доступ).
Используя массив вместо ListModel, вам нужно будет написать modelData.content
Развертывание
Итак, наше приложение запущено и работает на ПК, поэтому мы хотели бы сделать это для нашего телефона.
Эта тема выходит за рамки приложения только потому, что она снова широко освещена в официальных документах, поэтому повторение ее здесь не очень полезно и может устареть в будущем.
Следуйте инструкциям для Android, iOS или любой другой поддерживаемой платформы.
Вот запускаем его на android:
Заключение
Документы Qt потрясающие! Если вы пропустили большинство связанных документов (я вас не виню, мы все это делаем!), У вас может возникнуть много вопросов.
Тем не менее, чтение документации по каждой из тем даст вам хорошее понимание. Если это слишком много или вы хотите делать это шаг за шагом, то я снова рекомендую официальные документы для начала работы с QML и / или https://qmlbook.github.io.
В этом примере мы только поцарапали поверхность. Мы все равно хотели бы сохранить эти задачи и разрешить правильную модификацию после сохранения. Мы можем хранить его локально, используя LocalStorage или используя знакомый XMLHttpRequest, чтобы сделать сетевой запрос к какому-либо API для синхронизации между платформами.
Также было бы удобно поддерживать клавиши, поэтому нажатие Enter / Return добавит новую задачу. К счастью, это очень просто, используя Свойство ключей.
Но, в конце концов, это всего лишь небольшое введение в QML, и после него начинается настоящая разработка! Удачного кодирования!
Вы можете получить полный исходный код приложения на github.
Следите за новостями о Части 3, в которой мы займемся разработкой (Android) TV!