Оформление
Закругление, фон, рамка, отступы
Теперь фон, рамки и отступы есть не только у контейнеров, но и у других элементов, таких как надписи, например. Таким образом, можно делать такие визуальные элементы, как «теги», например – т.е. надпись на фоне, выглядящая как некий объект.
Кроме того, появилась возможность делать закругление, указав радиус. Закругление сейчас модно – с ним дизайн выглядит более завершенным, как будто доработанным напильником. И наконец, указав радиус «-1», можно довести закругление до полного круга.
Оформление в списках
Все эти возможности применимы и к оформлению кастомных таблиц (кастомных списков карточек – не в полной мере). Таким образом можно нарисовать свой неповторимый дизайн.
SetRootLayout
Многие новшества последних релизов направлены на повышение быстродействия и эффективности, и данная команда из этой серии. Смысл подхода – замена наполнения экрана из кода, т.е. не открытие другого экрана, а просто замена визуала на другое содержимое. Таким образом, с помощью команды SetRootLayout корневой контейнер экрана получает другое содержимое, но экран со всеми обработчиками и подключенными опциями остается тем же. По сути подход тот же, что и layout элемента списка или диалога.
Списки
Горизонтальные списки
Появилась возможность указать ориентацию списка – опция "horizontal":True делает список горизонтальным. Также, если требуется, чтобы карточка была не на всю ширину экрана, а, допустим, на какую-то его часть, нужно указать опцию "width_ratio" – с процентом от ширины экрана, например, 50 - будет половина экрана. Если не указать, то карточка будет на всю ширину
Догрузка в списке карточек и таблице
Для больших списков можно сделать догрузку – получение порций данных, когда пользователь промотал до конца. При промотке дальше возникает событие LoadMoreItems, оформленное прогресс баром, в котором разработчик может определить обработчик добавления новой порции строк в переменную AdditionalItemsData.
Позиционирование на позицию в списке карточек и таблице
Теперь можно мгновенно или плавно переместиться на выбранную позицию двумя простыми командами:
- ListGoTo, номер позиции – мгновенное перемещение на позицию
- ListGoToSmooth, номер позиции – анимированное перемещение на позицию
Хранение. Pelicane
Переработал SimpleBase, чтобы избавиться от компромиссов по производительности. Теперь проект называется Pelicane – это по прежнему безсерверная NoSQL с точно таким же синтаксисом 1 в 1 (документация подходит старая), но в плане скорости все поменялось кардинально. Если раньше она умела быстро только добавлять, то теперь upsert, update, delete точно также не зависят от объема таблиц – любое изменение записывается одинаковое время хоть в пустую коллекцию, хоть в коллекцию со 100млн записей - в р-не 1-2 мсек. Плюс появилось много всего – версионирование, новые транзакции и т.д. СУБД прежде всего для Simple-клиентов (альтернатива SQL), для будущей шины и для решений бека или промежуточного бека, но в принципе для чего угодно.
Я полностью заменяю SQL на своих проектах Simple на NoSQL – на клиентах сейчас будет Pelican, на сервере – MongoDB. У SQL 2 проблемы – сложная разработка(что не соответствует принципу симплификации Simple) и отсутствие гибкости при изменении схем хранения данных(SQL как будто заточен под водопад-проекты, которых сейчас днем с огнем)
Синтаксис Pelican (ранее SimpleBase) скопирован с MongoDB. Без описания, чисто по примерам все понятно (полный пример тут: https://github.com/dvdocumentation/pelican_dbms/blob/main/samples_pelican_ru.py ) :
from pelicandb import Pelican,DBSession,feed
import os
from pathlib import Path
"""
Базовые примеры : CRUD-операции без транзакций, индексов
"""
#Инициализация БД, path= путь к каталогу БД
db = Pelican("samples_db1",path=os.path.dirname(Path(__file__).parent))
#добавление документа без ИД
id = db["goods"].insert({"name":"Банан"})
print("Добавлено:",id,sep=" ")
#добавление документа с ИД
try:
id = db["goods"].insert({"name":"Банан", "_id":"1"})
except:
print("Такой документ уже есть")
#Upsert документа
db["goods"].insert({"name":"Персик", "price":100, "_id":"2"}, upsert=True)
db["goods"].insert({"name":"Персик", "price":99, "_id":"2"}, upsert=True)
#Добавление набора
ids = db["goods"].insert([{"name":"Яблоко", "price":60}, {"name":"Груша", "price":70}], upsert=True)
print("Добавлено:",ids,sep=" ")
#Все документы коллекции
result = db["goods"].all()
print(result)
#Получить по id
result = db["goods"].get("2")
print(result)
#тоже самое через find
result = db["goods"].find({"_id":"2"})
print(result)
#Получить по id конкретную версию документа
result = db["goods"].get_version("2",0)
print(result)
#поиск по условию #1
result = db["goods"].find({"name":"Персик"})
print(result)
#поиск по условию #2
result = db["goods"].find({"price":{"$lte":70}})
print(result)
#поиск по условию #3
result = db["goods"].find({"name":{"$regex":"Пер"}})
print(result)
Принцип действия
У меня было 2 цели:
- Обеспечить максимально простую и гибкую систему хранения документов, справочников локально на клиентах Simple. Так как все в Simple делается через JSON, а основным инструментом оффлайн кода является Python, то JSON еще и отлично совмещается с внутренними типами Python – dict, list. Т.е. это однозначно должна быть документно ориентированная и JSON-ориентированная NoSQL такая как MongoDB, Couch. Но, в отличии от них – безсерверная, т.е. сервером является само приложение клиента.
- Обеспечить производительность сравнимую с SQL по основным критическим операциям
С задачей 1 все понятно, сложность была с задачей 2. Далее, какое решение было применено.
Данные хранятся в файлах. Они нетипизированные как в SQL, записи занимают разное количество байтов. Если делать позиционирование на записях при update, delete то на поиск, вырезание, вставку записей уходит слишком много времени, чем больше размер таблиц, тем больше времени уходит. Т.е. мы получаем ситуацию, когда производительность зависит от объема данных.
Но, если сделать так, чтобы при операциях записи все изменения всегда писались строго в конец файла, то это занимает минимальное время и, самое главное, это время не зависит от размера файла! Таким образом, я сделал так, чтобы update, upsert писались в конец файла, insert и так писался в конец, а delete не делает ничего с данными, а просто убирает указатель на данные. За счет чего update и upsert пишутся в конец? За счет версионирования – создается новая версия и пишется всегда в конец.
Таким образом, в pelicane по дефолту версионирование, причем это неотъемлемая часть процесса. Да, размер файлов растет, но выигрывается быстродействие при любых изменениях.
Каждая коллекция хранится не в одном, а в трех файлах (не считая индексов):
- Бинарный файл данных *.dt
- Файл указателя *.ptr, в котом указывается начало и конец блока данных каждой записи и версии данных
- Файл текущей версии данных (словарь) *.idx – в котором на ID хранится текущий номер версии
На самом деле там есть и другие механизмы увеличения быстродействия записи изменений, они рассмотрены в примерах. Все решение направлено на то, чтобы обеспечить быстрый и плавный интерфейс пользователя при работе с локальным хранением.
Остается выборка данных. Да, потрясающую производительность любых SELECT-ов тут не обеспечить, но для мобильного клиента это не нужно. А что нужно? Нужно чтобы был мгновенный поиск, например, основного средства по штрихкоду в базе из 10 миллионов основных средств. Или быстрый поиск товара по нескольким символам его наименования. Вот такие задачи примерно. Аналитические отчеты мало кто смотрит на локальной БД, если уж они нужны, то используются запросы сервера.
Для этого используются индексы двух типов: хаш-индексы для индексации значений (например, штрихкоды товаров для поиска по штрихкоду), бинарные деревья для нечеткого поиска для индексации текстовых значений.
Как Pelicane встроен в SimpleUI
Пару слов о том, как использовать Pelicane в мобильной платформе. До этого я встраивал SimpleBase путем разработки на java встроенных механизвмов СУБД, при том что сама библиотека на питоне.
Теперь я от этого отказался – Pelicane существует только в виде python-библиотеки, но встроен в SimpleUI так, чтобы его использовать максимально эффективно. Для этого обработчики не обращаются к самой библиотеке, а обращаются к некому прокси-классу, который уже вшит в SimpleUI и использует принцип стека инстансов СУБД – pelicans , т.е. в памяти постоянно сидят используемые в конфигурации СУБД (объекты Pelican), они инициализируются 1 раз, они – синглтоны. Через них обращаемся к коллекциям, пишем, читаем. Т.е. в памяти постоянно висят подгруженные и актуальные инстансы коллекций и индексов.
Еще одна особенность – команда feed – пакетная работа с СУБД. Сделано это не для разработки обработчиков UI, а больше для синхронизаций. С помощью нее можно пакетно передать CRUD-команды и также пакетно получить ответы. Одной командой в виде JSON входящего и исходящего. Например, если 1С надо вставить в локальное хранилище на устройстве данные, то она может это сделать через FeedPelican (аналог feed для переменных). Поддерживаются в т.ч. транзакции – т.е. например, если какая то из команд не будет выполнена – произойдет откат.
Ну и также можно работать с библиотекой pelicandb напрямую через библиотеку.
Все это и другие нюансы рассказаны в видео и разобраны на примерах тут https://youtu.be/aEAzLWPgN2c
Ссылки и документы по Pelicane
Библиотека: https://pypi.org/project/pelicandbms/
Гитхаб: https://github.com/dvdocumentation/pelican_dbms
Документация от SimpleBase (ждет, когда я ее перепишу на Pelicane): https://simplebase.readthedocs.io/en/latest/index.html
Видео-пояснение к примерам именно в SimpeUI тут https://youtu.be/aEAzLWPgN2c
Управление
ShowScreen процесс|экран
Это одна из мощнейших команд Simple после RunEvent. В этом релизе появился новый синтаксис параметра ShowScreen - процесс|экран. Раньше был просто экран текущего процесса, теперь можно указать любой процесс, и взять из него экран. Экран именно «заимствуется», т.е. присоединяется к текущему процессу без запуска экрана. Это сделано для того чтобы мгновенно без потери производительности получать и использовать любой экран системы (т.е. сам процесс не запускается). Таким образом можно например организовать «общие формы» - неких скрытый процесс для хранения общих экранов.
Прямой рефреш. android.refresh_screen(hashMap)
Еще одна мощная команда. Ранее появился модуль android, который позволяет взаимодействовать с платформой напрямую, а не через стек переменных. Т.е. там собраны команды, который будут выполнены в той же строчке кода обработчика, а не по окончанию обработчика, через стек переменных. И android.refresh_screen позволяет в процессе этого обработчика передавать на экран значения и обновлять его в течении всего обработчика, до завершения. Это можно делать как из кода обработчика экрана, так и из любого фонового процесса. Особенно это актуально для каких-то долгих процессов.
Эта команда работает отовсюду - не обязательно из обработчика экрана. Она обновит экран из любого фонового процесса, если экран открыть. Например, из события веб-сервера приложения или через событие веб-сокета.
Команда доступна в двух вариантах:
android.refresh_screen(hashMap) - запускает рефреш и передает стек. Дело в том, что раз стек передается в конце обработчика, а рефреш надо надо сделать внутри, то надо каким-то образом поменять стек, иначе на экране будет выведено то же состояние переменных
android.refresh_screen() - запускает рефреш экрана. Предполагается, что стек будет установлен методами, описанными ниже
Получение стека, текущий запущенный режим, работа со стеком извне
Также появилось несколько полезных команд, для прямой работы со стеками переменных и анализа состояния запущенных процессов:
get_process_hashmap() – получает стек переменных экрана из любого места
get_cv_hashmap()– получает стек переменных ActiveCV из любого места
get_service_hashmap()– получает стек переменных фонового сервиса из любого места
process_started() – получает признак, запущен ли процесс в данный момент
cv_started() – получает признак, запущено ли ActiveCV в данный момент
put_process_hashMap(key,value) помещает значение в стек процесса
remove_process_hashMap(key) – удаляет значение из стека процесса
Событие проверки ошибок
Теперь любую run-time ошибку можно перехватить в общем событии onHandlerError, а сообщение об ошибке пишется в переменную HandlerErrorMessage. И написать свой обработчик для этой ошибки.
Событие закрытие процесса
На закрытие любого процесса теперь возникает событие onProcessClose, а имя закрытого процесса помещается в переменную _closed_process
Связь без связи. p2p – методы связи в Simple UI
Оба способа представлены пока как экспериментальные (beta), но вполне работоспособные
Вариант 1: «оптический между двумя мобильными устройствами через фронтальную камеру»
Сразу оговорюсь, этот и следующий «оптический» вариант не предназначены для передачи прям совсем больших объемов, т.е. «Войну и мир» ими лучше не передавать, но условную «накладную» - без проблем. Принцип действия основан на расширении QR-кода. Так как QR-код вмещает ограниченное количество информации, а потребоваться может больше, то требуется просто разбить информацию на несколько QR-кодов. т.е. буквально, допустим, исходной информацией, в которой содержится «накладная», является JSON, мы его записываем в виде строки, а строку режем примерно по 300-500 символов и получаем несколько QR кодов.
Дальше, т.к. на экран влезает только один, мы должны воспроизвести следующий алгоритм
- Разбиваем JSON на список строк не более N-символов(допустим, 300)
- Располагаем 2 устройства друг напротив друга так, чтобы они видели экран друг друга (нужна фронтальная камера)
- Показываем на устройстве отправителе 1-й QR-код, в котором закодирован номер части
- На устройстве отправителе считываем код и показываем номер считанной части
- На устройстве получателе считываем код и показываем QR-код по списку с номером части+1
- Это повторяется N-раз, пока не закончатся коды и не будет принято подтверждение о последнем коде
В чем преимущества данного способа? Несмотря на невысокую скорость передачи, у этого способа быстрая скорость подготовки – запустились камеры и произошел обмен. Не требуется соединения, он не капризный в отличие от Direct WIFI.
К этому же способу относится ранее представленный вариант "с печатной формы на устройство" https://youtu.be/X-bzZ7L-whQ
Вариант 2: WiFi Direct
Реализовано это так, что оба устройства одновременно поднимают точки доступа и являются клиентами, чтобы сделать обмен одноранговым. Обмен может быть инициирован из любого бизнес процесса из кода, т.е. можно подготовить данные в коде обработчика, запустить командой окно обмена, в котором выполняется поиск устройств рядом, и на устройстве приемнике также запустить функцию обмена для приема из процесса или просто из общего меню приложения. На получение данных есть соответственно тоже свое событие и можно написать обработчик, чтобы его записать, например, в СУБД как в примере.
Примеры и ссылки
Я решил больше не собирать комплект разработчика в основной статье по SimpleUI //infostart.ru/1c/tools/1153616/, а выкладывать файлы к статьям по релизам.
Еще одно новшество - примеры больше не содержат python- файлов, т.к. код в pythonscript-обработчиках, т.е. все, что нужно - в *.ui-файлах
Еще одно вынужденное новшество - в релизе для GooglePlay нет "больших" библиотек python - OpenCV, BeautifulSoap и Pandas. Их можно скачать в отдельной версии в виде apk. Также там python 3.8 а не 3.11 из за OpenCV
Посмотреть примеры можно на https://seditor.ru:1555/ или развернув редактор локально (https://github.com/dvdocumentation/web_simple_editor). В этом же редакторе учитываются все изменения по конфигурации.
Конечно же, Телеграм-канал проекта, в котором масса всего полезного: https://t.me/devsimpleui