Полезные библиотеки для QML
[TOC]
Много всего полезного https://github.com/mikalv/awesome-qt-qml
Для моделей
- https://github.com/benlau/qsyncable QSyncable - Synchronize data between models
- https://github.com/diro/qml_underscorejs Для работы с моделями на JS
Точно поллезные qml “приблуды”
- FontAwesome для упрощения верстки , значки https://github.com/benlau/fontawesome.pri
- ими можно заменить все
qrc:/icons/some.svg
чтобы отвязаться от с++svg_item.h
- ими можно заменить все
- Диалог открытия файлов https://github.com/paulondc/quicknative
Компоненты // библиотеки компонентов
наш Industrial controls …. другая ссылка
- не хорошо:
- есть серьезная с++ зависимость, которая требует линковки с библиотекой чтобы “особым образом” проинициализировать.
- официально все круто
- у нас это отключили
- зато есть активация темы особым способом
- т.е. тема и ее загрузка появилась, но библиотека перестала работать как библиотека отдельно… наверное надо просто дожать это, чтобы нам было удобно. сама эта библиотека - лучшее, что я видел на qml
- компоненты так сделаны, что их использование повлечет привязку логики приложения к этой библиотеке и потом не перейти на другую.
- есть серьезная с++ зависимость, которая требует линковки с библиотекой чтобы “особым образом” проинициализировать.
- хорошо:
- есть темы и стили
- это библиотека с компонентами, которых по идее должно быть достаточно чтобы что-то делать.
- не хорошо:
industrial_indicators … линк1 линк2
- хорошо: ситуация лучше, чем у industrial_controls. это чистый qml + js.
- не очень прекрасно: темы сделаны хотя и схожим образом, но настройки значений темы не совпадают, форма и состав полей темы не совпадают. (но и не обязан…)
https://github.com/brexis/qml-bootstrap типа Бутстрап компоненты , БЕЗ с++
- плохо:
- к сожалению сделан не как библиотека, а как проект в котором трудно понять, что библиотека, а что сделано стандартными средствами
- из него библиотеку надо еще сделать… структура плоха.
- хорошо: чистый qml + js, жить можно, но простовато
- плохо:
https://github.com/lirios/fluid много компонентов, библиотека, есть с++
- qml компоненты описаны тут https://docs.liri.io/sdk/fluid/develop/ описание плохое, без картинок
- c++ компоненты:
- DatePicker
- DateSelector
- Color
- Utils
- WindowDecoration
- IconCategoryModel
- IconNameModel
https://github.com/benlau/quickios для iOS несколько базовых компонентов чтобы начать простое приложение
- плохо: компоненты на столько базовые, что считай их нет.
https://github.com/benlau/sparkqml Таблицы с данными
https://github.com/chili-epfl/qml-cache аналог LocalStorage для QML
Docking system
https://github.com/Skycoder42/QtMvvm подборка с целым видением как делать и проект и виджеты
https://github.com/Skycoder42/QtRestClient REST запросы где JSON ответы сами превращаются в QObject свойства. Полезная штука, даже как паттерн
https://github.com/Waqar144/QSourceHighlite быстрая подсветка кода многих языков программирования
Комментарии по компонентам
Это все не компоненты в нормальном смысле., даже industrial_controls.
Там нет никакой изоляции, никакой типизации… с редюсерами все плохо, с обновлением по сигналам не очень хорошо.
Изворотливый человек сможет их так использоовать, как не предполагалось разработчиками и это создаст новые проблемы. но это проблемы скорее самого qml.
Замечание по языку QML: (субъективно, но верно)
Когда определено содержимое блока/компонента внутри его qml файла оно должно содержать явное описание параметров, которые приходят снаружи.
!!! Возможность, используя блок/компонент снаружи ПЕРЕОПРЕДЕЛИТЬ и даже ДОПОЛНИТЬ блок/компонент вызывает фрустрацию.
- переопредлять внутренности блока/компонента нельзя! Если хочется: то нужно иметь параметры, которые самому компоненту говорят как он сам должен это делать. перегруппируйте код: Выделите общие блоки в отдельные компоненты, используйте общие части в своем компонентие и тот, от которого пошла идея, переписав его по новому, используя общие части.
- удалять (заменяя пустым) часть блока нельзя. Если хочется - нужно иметь параметры, читая которые блок/компнент сам не будет их рисовать
- Дополнять блок/компонент нельзя. Если хочется, то ты должен сделать новый блок/компонент, который и будет являться композитом, который ты хотел. Должно произойти “порождение компонента”. Дополнение компонента, добавлением ВНУТРЬ ему вякого извне - плохой стиль. добавляется не толко ВИД, но и даже логика и состояния… Это ужасно!
- в то же время это нормальная практика для лайаутов, или базовых не видимых блоков (сложное позиционирование)
- обращение по id внутри модуля или в другой qml файл. Это скорее баг чем фича. неявная связь.
property alias
имеет синтаксис, который используетid
(= способствует засилю id в qml ) поэтому дополнтиельно запутывает структуру и запутывает автоматический анализ qml кода. Получается, это плохая практика, пусть даже она “нравится программистам” - не все что нравится программистам есть хорошоКак определить что из нежелательного происходит? Пример плохих признаков:
- новые свойства не на самом верхнем уровне qml вложенности. говорят о создании состояния не на уровне компонента а ниже - возможно часть кода стоит вынести в отдельный компонент, декомпозировать
- Если компонент определен в отдельном файле а мы, используя его, внутри вкладываем в него не один а на больше уровней других компонентов, то лучше перегруппировать все так, чтобы все эти элементы были сами вложены в лайаут или невидимые блоки (используемые для позиционирования), чем друг в друга. Другими словами: определенные нами компоненты должны быть листьями дерева, а не узлами (лайауты - исключения).
- обращения по id “куда не следует” трудно засечь.
Мысль / идея автоматический анализ QML
чтобы обнаруживать это автоматом надо парсер QML.
Есть уже такой : https://github.com/qmlweb/qmlweb-parser .
Примеры src & dest here: https://github.com/qmlweb/qmlweb-parser/tree/master/tests/qml .
Используя даже
jq
и bash можно писать тесты, выявляющиестранныенеудачные места в qml
Мысль / идея палитра всех компонентов
Чтобы сделать браузер компонентов есть на плохое шаблонное решение https://github.com/benlau/sparkqml это браузер компонентов у которых выделены отдельно еще и состояния. Хорошо для тех кто делает свои компоненты. этакий “демо”
“Философские” вопросы
Как определить границу между бекендом и фронтендом ?
Идеологически:
- все что за API - бекенд
- перед API - фронтенд.
Апи у нас предоставляют “сервисы”… даже внутри standalone desktop приложения.
Глобальность состояния (стоит ли все помещать в с++?)
два способа мыслить:
- Комполненты. все есть новый компонент, мы их перестраиваем, а не управляем ими. тогда надо умело создавать “локальные” состояния и гибко их двигать и перегруппировывать при изменении ситуации.
- Виджеты. все есть композит готовых типовых элементов, которыми надо управлять. тогда логику во фронтенде писать бужет не особо удобно - возникнет желание поместить все состоняие на “самый верх” (или даже в с++).
2-й путь - тупиковый. и так мы не сможем быстро и отдельно разрабатывать…
- для тестирования придется делать отдельные состония в с++ (не быстро потому что компиляция занимает время) или в js (не быстро потому что это аналог с++ состояния и будет плохо стыковаться друг с другом и все будет на костылях)
- в с++ будет все “прокинуто” и там , изоляцию трудно контролировать - возникнет система сайд-эффектов.
- кривая скорости разработки идет вниз - практика так показывает, чего только непридумывали с этим. Проект начинает пробуксовывать, “вязнет” в своем же коде.
1-й путь лучше.
- У компонентов выше степень изоляции логики, состояние меньше, проще, управляемость выше.
- состояние будет на js в и для работы и для тестов. (важне не где оно будет, а какое… не могу получше слова подбрать. не то сказал сейчас.)
- состояние будет родным для qml и будет легко разрабатывать ГИП без с++ слоя
- можно будет а) генерировать код для синхронизации состояния 2) использовать разные шины 3) видеть состояние шин и данных - удобнее наладка.
- меньше с++ копиляций для работы с фронтендом (хотя этого все равно не избежать)
Анимации
Общее описание видов и параметров анимаций https://doc.qt.io/qt-5/qml-qtquick-propertyanimation.html
При изменении высоты \ плавное изменение свойства
1 | Rectangle { |
Реакция на сигнал (или метод?)
1 | ListView { |
Еще пример со списком:
1 | ListView { |
обсуждение этих анимаций ссылка: https://stackoverflow.com/questions/62109636/qml-remove-animation-of-listview-item-with-a-qsqltablemodel
Последовательность анимаций
Вызято отсюда https://stackoverflow.com/a/62149708/2355077
1 | remove: Transition { |
Бесконечное повторение нескольких анимаций
1 | Rectangle { |
QSyncable
Как вариант для с++ моделей qml приложения. Уменьшает рутину
Design Principle - Separation of “updates” and “queries
QSyncable предназначен для решения фундаментальной проблемы приложений C++ и QML: обмен данными между C++ и QML
Модель списка
QObject*
определенно плохая идея. Ужасно управлять их жизненным циклом и владением (сфера применения QML/C++). Нужно знать о сборке мусора в вашей модели спискаQObject*
, даже если она написана на C++.Лучше использовать модель
variant list
, но она неудобна для C++. И сложно справиться с моделью вложенного списка.Все будет проще, если разделить «обновления» и «запросы» на разные компоненты. Прежде всего, вам даже не нужно рассматривать подход модели списка
QObject*
. Нет никаких преимуществ использования модели спискаQObject*
, если вы используете другой компонент для обновления.Нет необходимости использовать модель
variant list
в качестве центрального источника данных. Можно использовать любую структуру данных, которая вам нравится. Можно оставьте модельvariant list
только для отображения.QSyncable делает шаг вперед. Он упрощает процесс обновления модель
variant list
из источника данных, объединяя операции вставки, удаления, перемещения и изменения в один процесс — исправление, сохраняя при этом правильность ответа пользовательского интерфейса. Он решает не только проблему совместного использования данных C++ и QML, но и решение модели вложенных списков в QML.
Моя, пока просто, мысль:
Можно сделать плагин из этого проекта такой, чтобы внутри была модель в виде Variant модифицируемая внешним кодом через шину, тогда возможно будет делать интерфейсы к любым данным расположенным где угодно, например к данным рарсположенным в базе данных и изменяемых там любым способом. - опять двухзвенка? знаю, что это прошлый век, но для разработки это полезная возможность упростить себе жизнь в ряде случаев
Общая идея
- на с++ стороне пишется любая модель которая вам нравится. там может происходить, что угодно. примеры: board.h card.h list.h
- на верхнем уровне с++ определяются действия для мутации модели (Actions) , ссылка на строку и ниже несколько методов эти методы и вызываются из qml - они меняют с++ модель так, как определено в методах. при этом это никак не отражается на qml интерфейсе
- у с++ модели определен метод sync который и предназначен для изменения/применения модели в qml. не надо звать его автоматически везхде - его надо звать в конце Actions действий если даннные для отображения актуальны (например когда они пришли по сети) пример как это делается
- данный метод описа ниже и вот что он делает:
пример реализации метода sync
1 | // It is the only way to update QML model, you don't need |
- вся наша с++ модель преобразуется в
QVariantList
(там должны быть проставленый уникальные метки/поля объектам для быстрого сравнения, в данном случае это полеuuid
) - считаем ПАТЧИ между там, что должно быть в qml и что сейчас отображается
- применяем патчи к отображаемой модели типа
QSListModel* cardListStore;
чтобы изменить ее оптимальным образом. Алгортмы в этом процессе действительно быстры (это аналог виртуального DOM в недрах jsx reactjs) Пруф№1 Пруф№2 (но это только в палнах, пока не реализовано)
Итого, у нас на с++ стороне определено два поля. Одно - модели с++ второе - отображаемая в QML модель
1 | private: |
При этом наши модели имеют примерно такую “сериализацию”:
1 | QVariantMap Board::toMap() const |
Пример как работают с Actions - хорошая практика
Регистрация всячины и запуск приложения:
1 | QString persistFilePath = persistDir + "/persist.json"; |
Обращения к Actions по UUID: (важно, фигурируют только id
):
1 | App.removeCard(listUuid, cardUuid); |
Уже на стороне с++ происходит связываение и поиск объектов бизнес-логики, для вызова соответствующих изменений.
Оценка общей идеи
- Похоже на двусторонние биндинги AngularLight версии 0,8 (до 0.10 включительно). Частично KnockoutJs . Вот функция аналог
sync
данной библиотеки, это$scan()
Angular light https://github.com/lega911/alight_docs/blob/master/docs/source/scope.rst#scopescancallback-or-option - Плюсы:
- позволяет избежать массы лишних отновлений интерфейса
- позволяет выделить Actions на верхнем уровне
- нормализует “однанапрвленный поток” данных в приложении
- позволяет иметь простую (plain old object) модель данных в с++ , не привязывая ее к QML (это, кстати, очень важно). Модели можно применять сборку мусора (управлять памятью произвольно) или даже не иметь ёё а получать
QVariantList
с сервера и сразу отображать - высокая призводительность работы и разработки т.к.
- нет прокидывания самих моделей в qml как типов
- не прокидывания сигналов моделей в qml
- не требуется описывать в моделях свойства и логику работы с ними так, чтобы это было видно в qml
- на много меньше кода в с++
- полная управляемость
- относительные минусы (на самом деле не минусы)
- надо отмечать все объекты в списках уникальным образом
- нужно собирать с++ часть. но все это для приложений которые имеют с++ часть
- минусы
- все минусы решений типа AngularLight , где контекст сканируется в ручном режиме
- Если модель большая и много actions , то часто возникают лишние сканирования (в этом случае все нормально т.к. тут патчи перестанут возникать в тех местах где изменений не было.)
- если данные модели должна зависеть по логике приложения от отображения (например нужно знать реально занимаемые размеры, напрмер высоту), то этот метод уже не подходит или надо разбивать на две части модель (два и более контекстов). Впрочем никакое решение не подходит хорошо в этом случае
- все минусы решений типа AngularLight , где контекст сканируется в ручном режиме
Необходимо написать преобразование моделей в QVariantMap
, кажется, что это единственная дополнительная работа.
Важно: такое преобразование хорошо сочетается с проектом “сериализатора” который я сейчас делаю. Даже промежуточные классы удачно совпалдии. Суть проекта сериализатора:
Задача: есть plain с++ class | struct с обычными полями и с полями списками (список / вектор). Надо его сериализовать и десериализовать.
предлагается автоматическое решение вида:
- специальный консольный инструмент анализирует c++ *.h файл с определением структур и классов.
- для классов и структур анализирует структуру (парсит структуру, поля, список итд), генерирует model.json файл, который описывает структуру с++ кода.
- консольный генератор по шаблону и с параметрами генерирует для этих классов *.h & *.cpp с сериализаторами и десериализаторами в типы (или из типов), например а) QJsonValue ,) boost::property_tree , которые легко преобразуются в/из строк и или бинарных структур.
т.е. это инструмент разработки, который к обычным прикладным классам добавляет генерируемый по шаблону код, который попадает в проект. скрипт лежит в проекте и обновляет автоматически сгенерированные файлы.
Получается, что в случае использования такого проекта как “генератор сериализаторов по с++ структуре” код перевода модели в QVariantMap
даже не потребуется писать.
т.е. в этом случае мы получим
- чистый с++ код на стороне с++ , не обязательно на QT классах
- скрипт который сам за нас генерирует все дополнительные преобразоваия наших с++ моделей в промежуточный QVariantMap
- библиотека (как бы виртуального dom) для оптимального обновления QML модели после анипуляций с с++ данными, которая сама и эффективно синхронизирут с++ модели на qml сторону
- QML код, который
- может быть разработан отдельно от приложения
- работает на JS своих родных моделях
- не содержит логики верхнего уровня бизнес-логики (обработка actions находится на с++ уровне или в JS скриптах верхнего уровня для тестирования)
- остается: вся логика которая есть в qml - логика компонентов и отображения (локализованная view логика)
QSListModel - c++ модель для отображения в QML
1 | QSListModel* cardListStore; |
По сути это помомок QAbstractListModel
которого можно “патчить”. Это просто Q_OBJECT
Описание дополнительных удобных методов класса https://github.com/benlau/qsyncable/blob/master/qslistmodel.h#L33 ссылка на строку
JsonListModel
Имеется такая штука как JsonListModel
.
1 | model: JsonListModel { |
Это враппер для синхронизируемых списов… Для отдельных частей единой большой модели.
Зачем:
JsonListModel is a syncable list model.
It could be used as a wrapper of other Javascript array object.
Whatever the source is updated, it will trigger synchronization automatically.
It will emit signals of insertion, removal and moving automatically.
Т.е. это удобная враппер штука, чтобы выделять в данных “подконтексты”, так сказать, которые сами будут обновляться если их родитель обновился.
Важно, что у нее есть свой собственный ::sync()
Underscore.js
https://github.com/diro/qml_underscorejs - Порт популярной блиблиотеки для QML. Underscore это функциональный подход к структурам и потокам.
На фронтенде она удобна для вычисления “частных” отображаемых значений. позволяет просто собрать отображаемое значение, построить JS выражение , котороые вычисляет требуемые данные или находит требуемый объект, фильтрует списки итд.
Документация на исходный оригинал https://underscorejs.org/
Обучаться лучше всего на примерах… или запуская тесты.
Учитывая что QSyncable предоставляет базовые модели с данными а отображение может требовать специальных представлений этих данных - на этой библиотеке могут быть реализованы эффективные вычисления таких представлений. (“силовые” преобразования моделей)
Mobx
На пальцах | concept | site | github | quck example
Эта ссылка - mobx + React - очень полезно понять
Последние новости
- Портирован на Dart https://mobx.netlify.app/
- Портирован на C# https://github.com/skclusive/Skclusive.Mobx.Observable
- портирован на surivejs https://github.com/survivejs-demos/mobx-demo
Как поиметь это в qml
https://github.com/bhdouglass/qml-browserify - Browserify for QML.
Идея / мысль:
надо Mobx получить в qml в виде JS файла и использовать в своей работе
TODO тут надо сделать это и дописать (не срочно, структура проекта и потоков данных важнее, чем частная реактивность в моделях)
Quickflux - TODO
Линк https://github.com/benlau/quickflux
Правильно сделана структура QML проекта, тут есть чему поучиться, это и сделаем.
Некоторые вещи здесь я буду критиковать, а важное и однозначно полезное выделю.
TODO
Selenium for QML - perfect idea
https://github.com/cisco-open-source/qtwebdriver/wiki/Use-QtWebDriver-to-run-your-application
Данный проект создан для автоматизированного тестирования qml приложений в стиле тестирования веб приложений. Интеграционного тестирования в том числе.
Описываются принципиальные возможности
- подключение webDriver к существующему приложению
- дайвер сам “создает” приложение
Подключение к существующему приложению - более реалистичная затея.
++:
- “Проще настроить” :-D
- жизненный цикл драйвера и приложения связаны
–:
- перезапуск драйвера вместе с приложением - накладывает сложностина тестирование
но понадобится встроить драйвер в код и влинковать в программу
Способы это сделать:
в результате откроется порт (как минимум)
1 | WebDriver driver = new RemoteWebDriver(new URL("http://192.168.24.1:9517"), capabilities); |
Созадние приложения из драйвера
++:
- не надо изменять код приложения… типа
–:
- драйвер не надо пересобирать
- можно сделить за циклом приложения при тестировании
Пример с запуском калькулятора https://github.com/cisco-open-source/qtwebdriver/pull/27
Селениум QML Тесты… какие они тут?
В web версии можно спокойно бродить по DOM а тут ? что тут можно из JS ? какие по виду и возможностям будут тесты?
Ответ предположительно такой:
1 | WebDriver driver = new RemoteWebDriver(new URL("http://192.168.24.1:9517"), capabilities); |
XPATH … ладно. но я поискал еше примеры:
- заглядываем внутрь WebKit … это не совсем то , что искал
- перехват логирования для анализа - получше, но “костыль” все таки
- Тут хороший пример теста https://github.com/cisco-open-source/qtwebdriver/wiki/Hybridity-And-View-Management есть таблица с локаторами
- скриншот элемента возможно сработает не только для веб элемента, надо проверять.
- медиа компоненты, плееры
Итого по “selenium qml”
Жить можно. Сам не пробовал, но возможности, учитывая xpath и работу с аттрибутами и идентификаторами + скриншоты, в целом позволяют делать номальные тесты.