[BugFixed]
Динамическое создание элементов интерфейса пользователя
Author |
Message |
Written on: 19. 11. 2018 [18:25]
|
kantv
Антон Калюк
Topic creator
registered since: 29.11.2016
Posts: 17
|
В своем проекте хочу реализовать в интерфейсе оператора навигационную панель для перехода между кадрами разложенными по дереву логических контейнеров соответствующих различным технологическим объектам.
В процессе реализации такой панели понял, что мне потребуется динамически добавлять на интерфейс новые группы элементов управления, при этом группы могут быть вложенными поскольку логические контейнеры также могут быть вложенными.
Насколько я понял из документации на архитектуру системы визуализации, для отображения элемента в графическом интерфейсе достаточно, чтобы это элемент был добавлен в дерево элементов в соответствующее место и у него были назначены соответствующие атрибуты. В документации упоминается метод wdgAtt(), который должен добавлять элементы, попробуем его использовать.
Для отработки этого приема я решил создать на основе стандартного виджета /wlb_originals/wdg_Box свой собственный виджет /wlb_test/wdg_testRoot для корневой страницы, которую расположил в новом проекте test2.
В коде корневой страницы разместил следующий скрипт для динамического создания текстового поля:
sub = this.wdgAdd("dinamicText", "", "/wlb_originals/wdg_Text"); //добавляем к странице дочерний элемент на основе виджета /wlb_originals/wdg_Text
sub.attrSet("name", "Dinamic Text"); //задаем новому элементу имя
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("text", "success"); //задаем отображаемый текст
При запуске проекта все прекрасно работает - на странице появляется поле с текстом "success", хотя в графическом конструкторе его не было, то есть объект был добавлен динамически при выполнении кода. И в системном конфигураторе в разделе текущих сессий также можно видеть этот динамически созданный элемент вложенный в корневую страницу (смотри приложенный рисунок 1).
Но поразмыслив, понял что код страницы выполняется несколько раз в секунду, а значит и добавление дочерних текстовых полей должно выполняться несколько раз в секунду, что не хорошо, тем более что идентификатор один и тот же. Чтобы гарантировать однократное выполнение участка кода с добавлением дочернего текстового поля у корневой страницы был добавлен атрибут init типа integer, а код страницы был преобразован до вида:
if (init == 1) {
init = 2;
sub = this.wdgAdd("dinamicText", "", "/wlb_originals/wdg_Text"); //добавляем к странице дочерний элемент на основе виджета /wlb_originals/wdg_Text
sub.attrSet("name", "Dinamic Text"); //задаем новому элементу имя
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("text", "success"); //задаем отображаемый текст
} else {
init = init + 1;
}
В таком варианте при запуске проекта все работает и на главной странице динамически создается дочернее текстовый элемент, при этом код добавления элемента выполняется только один раз. Все замечательно.
Динамическое создание элементов - это очень мощный и гибкий инструмент, который по сути делает возможности реализации графического интерфейса просто безграничными.
Но вот далее у меня возникли проблемы, а именно:
Я решил отработать динамическое создание вложенных объектов. Для этого я создал еще один виджет /wlb_test/wdg_testBox на основе стандартного виджета /wlb_originals/wdg_Box. В визуальном редакторе разместил на нем одно текстовое поле Text1 (/wlb_originals/wdg_Text), чтобы можно было выводить сообщения для отладки. В атрибутах добавил дополнительный атрибут depth типа Integer, а также добавил атрибут init типа integer по аналогии с предыдущей частью. В коде разместил следующий скрипт:
Text1_text = "start sub (depth = " + depth + ")"; //отображаем сообщение для отображения того, что код скрипта запустился и выводим текущее значение depth
wdg = "/wlb_test/wdg_testBox"; //путь к дочернему виджету
if (init == 1){
init = 2;
if (depth > 0){
sub = this.wdgAdd("subItem" + depth, "", wdg); //добавляем дочерний виджет
sub.attrSet("name", "Sub Item " + depth); //назначаем имя новому объекту
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("depth", depth - 1); //у дочернего элемента назначаем значение depth на 1 меньше
}
} else {
init = init + 1;
}
Как можно видеть из скрипта, при значении атрибута depth больше нуля будет создан дочерний элемент на основе такого же виджета, при этом у дочернего элемента значение атрибута depth будет уменьшено на 1. Таким образом, атрибут depth задает глубину вложенности объектов.
На корневой странице также создадим первый элемент динамически, для этого изменим код до следующего:
if (init == 1) {
init = 2;
sub = this.wdgAdd("dinamicText", "", "/wlb_originals/wdg_Text"); //добавляем к странице дочерний элемент на основе виджета /wlb_originals/wdg_Text
sub.attrSet("name", "Dinamic Text"); //задаем новому элементу имя
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("text", "success"); //задаем отображаемый текст
//создадим вложенный динамический объект интерфейса
wdg = "/wlb_test/wdg_testBox"; //путь к дочернему виджету
depth = 1;
sub = this.wdgAdd("subItem" + depth, "", wdg); //добавляем дочерний виджет
sub.attrSet("name", "Sub Item " + depth); //назначаем имя новому объекту
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("depth", depth - 1); //у дочернего элемента назначаем значение depth на 1 меньше
} else {
init = init + 1;
}
Таким образом, можно меняя на корневой странице значение depth менять степень вложенности создаваемых дочерних элементов /wlb_test/wdg_testBox.
При запуске проекта со значением depth = 1 все работает замечательно - открывается корневая страница и создается единственный дочерний объект (смотри приложенный рисунок 2), а в системном конфигураторе данный элемент виден в дереве сессий (смотри вложенный рисунок 3).
Проблема возникает если попробовать установить значение depth равным 2 или более. Вроде бы при этом должно создаться два элемента /wlb_test/wdg_testBox, при этом один элемент должен быть вложен в другой, но при запуске проекта этого не происходит. При запуске проекта на корневой странице создается первый дочерний элемент /wlb_test/wdg_testBox (смотри приложенный рисунок 4). А в системном конфигураторе в дереве сессий можно видеть, что дочерний объект был создан (смотри приложенный рисунок 5). Но почему то этот объект имеет не установленный флаг Process и часть его атрибутов не была задана скриптом, например Name. И еще одно - при закрытии окна с запущенным проектом визуализации сессия не закрывается и в конфигураторе системы можно видеть, что она продолжает работать и снять ее удается только закрытием всей OpenSCADA.
У меня не получилось найти решение данной проблемы и к сожалению чтение документации нигде не раскрывает вопроса динамического создания вложенных элементов.
Могли бы Вы пояснить почему мне не удается создать вложенные динамические объекты подобным образом? Может при создании вложенных объектов необходимо соблюдать какую-то особенную последовательность действий? Или может быть нужно каким-то особым образом настроить атрибуты таких динамических объектов?
Очень надеюсь на Вашу подсказку.
[This article was edited 2 times, at last 19.11.2018 at 18:28.]
Attachment
1.png (File type: image/png, Size: 286.33 kilobytes) — 2167 downloads
2.png (File type: image/png, Size: 28.94 kilobytes) — 2154 downloads
3.png (File type: image/png, Size: 290.24 kilobytes) — 2155 downloads
4.png (File type: image/png, Size: 30.42 kilobytes) — 2121 downloads
5.png (File type: image/png, Size: 283.95 kilobytes) — 2152 downloads
|
Written on: 20. 11. 2018 [20:50]
|
roman
Roman Savochenko
Moderator Contributor Developer
registered since: 12.12.2007
Posts: 3750
|
"kantv" wrote:
Я решил отработать динамическое создание вложенных объектов. Для этого я создал еще один виджет /wlb_test/wdg_testBox на основе стандартного виджета /wlb_originals/wdg_Box. В визуальном редакторе разместил на нем одно текстовое поле Text1 (/wlb_originals/wdg_Text), чтобы можно было выводить сообщения для отладки. В атрибутах добавил дополнительный атрибут depth типа Integer, а также добавил атрибут init типа integer по аналогии с предыдущей частью. В коде разместил следующий скрипт:
Вообще, это рекурсия, которая всегда была проблемой и не везде есть проверки на ограничения её работы.
Собственно, тут было две проблемы: блокировка на функции, которая одна, и отсутствие ограничения рекурсии.
Исправил всё это и теперь имею изображённое на экране, для кода рекурсивного контейнера:
if(depth) {
sub = this.wdgAdd("dinamicText", "", "/wlb_originals/wdg_Text");
sub.attrSet("geomX", 20).attrSet("geomY", 20).attrSet("text", "success");
if(depth < 3) {
sub = this.wdgAdd("subItem"+depth, "", "/wlb_test/wdg_textBox");
sub.attrSet("geomX", 5).attrSet("geomY", 5).attrSet("geomW", geomW-10).attrSet("geomH", geomH-10)
.attrSet("backColor","white-100").attrSet("depth",depth+1);
}
depth = 0;
}
Learn, learn and learn better than work, work and work.
Attachment
|
Written on: 21. 11. 2018 [17:14]
|
kantv
Антон Калюк
Topic creator
registered since: 29.11.2016
Posts: 17
|
Насколько я понимаю Вам пришлось внести некоторые изменения в код самой системы OpenSCADA, чтобы рекурсивное использование динамического создания элементов управления стало возможным. И теперь, чтобы я мог эти изменения попробовать применить в своем проекте, мне нужно дождаться когда эти изменения будут выложены автоматическим сборщиком на репозиторий для Debian, после чего обновить свою систему через стандартный менеджер пакетов.
Все верно?
Когда примерно обновится сборка для Debian?
|
Written on: 22. 11. 2018 [07:59]
|
roman
Roman Savochenko
Moderator Contributor Developer
registered since: 12.12.2007
Posts: 3750
|
"kantv" wrote:
Когда примерно обновится сборка для Debian?
"roman" wrote:
Исправил, сборки будут скорее всего только в конце недели.
Learn, learn and learn better than work, work and work.
|
Written on: 27. 11. 2018 [22:39]
|
kantv
Антон Калюк
Topic creator
registered since: 29.11.2016
Posts: 17
|
Сегодня обновился до актуальной версии OpenSCADA 1+r2586. Попробовал выполнить свой проект (смотри первый пост) и динамическое создание вложенных объектов отработало нормально. Спасибо Вам большое.
Но возникла новая проблема, на этот раз с удалением объектов. Постараюсь ее описать.
Поскольку я хочу реализовать собственную панель навигации, то предполагается, что вложенные элементы будут создаваться по мере того, как пользователь будет переходить по каким-то элементам, а если пользователь вернется к более высокому уровню вложений, то созданные на нижнем уровне динамические элементы интерфейса необходимо удалить. В документации я видел упоминание о методе wdgDel(), который предназначен для удаления виджета. Насколько я понимаю виджет должен удаляться из дерева объектов текущей сессии движка визуализации. Для отработки механизма удаления я в первоначальном проекте (смотри первый пост) добавил в виджет /wlb_test/wdg_testBox дополнительный статический элемент в виде кнопки при нажатии которой вызывается метод wdgDel() и удаляет вложенный динамически созданный элемент. Вот итоговый код виджета /wlb_test/wdg_testBox:
Text1_text = "start sub (depth = " + depth + ")"; //отображаем сообщение для отображения того, что код скрипта запустился и выводим текущее значение depth
wdg = "/wlb_test/wdg_testBox"; //путь к дочернему виджету
if (init == 1){
init = 2;
if (depth > 0){
sub = this.wdgAdd("subItem" + depth, "", wdg); //добавляем дочерний виджет
sub.attrSet("name", "Sub Item " + depth); //назначаем имя новому объекту
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("depth", depth - 1); //у дочернего элемента назначаем значение depth на 1 меньше
}
} else {
init = init + 1;
}
//обработка событий
for(ev_rez = "", off =0; (sval=event.parse(0,"\n",off)).length;) {
//детектируем нажатие на кнопку Delete
if (sval == "ws_BtPress:/button") {
Text1_text = "delete"; //отображаем сообщение delete в текстовом поле, чтобы явно видеть, что событие наступило
this.wdgDel("subItem" + depth); //выполняем удаление вложенного динамически созданного виджета
} else ev_rez += sval+"\n";
}
event = ev_rez;
После запуска проекта на первый взгляд все работает нормально - создаются динамические вложенные элементы интерфейса (на приложенном рисунке 1 представлен пример с вложениями из 4х уровней) и при этом для всех отображаемых в интерфейсе элементов в конфигураторе системы в дереве сессий имеются соответствующие элементы (также видны на рисунке 1). При нажатии на кнопку Delete, например на втором уровне вложений, можно видеть, что в конфигураторе системы в дереве сессий элементы удаляются и при этом пропадают из интерфейса пользователя (приложенный рисунок 2). И все было бы хорошо, но буквально спустя 1-2 секунды в конфигураторе системы в дереве сессий неожиданно вновь появляется только что удаленный элемент (приложенный рисунок 3), при этом у самого этого элемента и родительского по отношению к нему элемента отсутствует флаг Processing (приложенные рисунки 4 и 5), а элементы интерфейса перестают нормально работать (не отрабатывают скрипты кода виджетов и не обрабатываются события).
К сожалению в документации на метод wdgDel() не описывается принципов его работы. Подскажите пожалуйста, нет ли каких-то особенностей по его использованию? Может быть при удалении объектов с его помощью мне нужно было придерживаться какой-то особой последовательности действий?
Attachment
1.png (File type: image/png, Size: 266.69 kilobytes) — 1992 downloads
2.png (File type: image/png, Size: 258.19 kilobytes) — 2128 downloads
3.png (File type: image/png, Size: 224.94 kilobytes) — 1971 downloads
4.png (File type: image/png, Size: 258.67 kilobytes) — 2149 downloads
5.png (File type: image/png, Size: 250.32 kilobytes) — 2138 downloads
|
Written on: 28. 11. 2018 [09:40]
|
roman
Roman Savochenko
Moderator Contributor Developer
registered since: 12.12.2007
Posts: 3750
|
"kantv" wrote:
После запуска проекта на первый взгляд все работает нормально - создаются динамические вложенные элементы интерфейса (на приложенном рисунке 1 представлен пример с вложениями из 4х уровней) и при этом для всех отображаемых в интерфейсе элементов в конфигураторе системы в дереве сессий имеются соответствующие элементы (также видны на рисунке 1). При нажатии на кнопку Delete, например на втором уровне вложений, можно видеть, что в конфигураторе системы в дереве сессий элементы удаляются и при этом пропадают из интерфейса пользователя (приложенный рисунок 2). И все было бы хорошо, но буквально спустя 1-2 секунды в конфигураторе системы в дереве сессий неожиданно вновь появляется только что удаленный элемент (приложенный рисунок 3), при этом у самого этого элемента и родительского по отношению к нему элемента отсутствует флаг Processing (приложенные рисунки 4 и 5), а элементы интерфейса перестают нормально работать (не отрабатывают скрипты кода виджетов и не обрабатываются события).
К сожалению в документации на метод wdgDel() не описывается принципов его работы. Подскажите пожалуйста, нет ли каких-то особенностей по его использованию? Может быть при удалении объектов с его помощью мне нужно было придерживаться какой-то особой последовательности действий?
В консоль поглядывайте и сообщайте сообщения об ошибках, чем это детально описывать и догадываться!
Собственно, динамическое создание/удаление предусматривает и освобождение объекта подключения после использования, иначе созданные рекурсивно виджеты так-же рекурсивно и будут заблокированы. В вашем случае нужно sub очистить, оператором delete или прямо скаляр записать туда:
if (init == 1){
...
sub = 0;
}
Learn, learn and learn better than work, work and work.
|
Written on: 28. 11. 2018 [17:36]
|
kantv
Антон Калюк
Topic creator
registered since: 29.11.2016
Posts: 17
|
Спасибо большое за подсказку, теперь все заработало.
Не подумал о том, что могла остаться ссылка на виджет, поскольку считал, что в каждом цикле исполнения кода виджета объявленные в нем переменные по окончании выполнения удаляются - на следующем цикле исполнения ведь в переменной sub будет пусто если код не войдет в секцию инициализации (то есть значение init будет уже более 1). Теперь буду учитывать, что переменные лучше очищать после окончания работы с ними.
Еще раз спасибо.
Для тех кто потом будет читать эту тему - все работает верно при вот таком коде виджета:
Text1_text = "start sub (depth = " + depth + ")"; //отображаем сообщение для отображения того, что код скрипта запустился и выводим текущее значение depth
wdg = "/wlb_test/wdg_testBox"; //путь к дочернему виджету
if (init == 1){
init = 2;
if (depth > 0){
sub = this.wdgAdd("subItem" + depth, "", wdg); //добавляем дочерний виджет
sub.attrSet("name", "Sub Item " + depth); //назначаем имя новому объекту
sub.attrSet("geomX", 20); //задаем положение по оси X
sub.attrSet("geomY", 20); //задаем положение по оси Y
sub.attrSet("depth", depth - 1); //у дочернего элемента назначаем значение depth на 1 меньше
sub = 0; //очищаем ссылку на созданный динамически виджет, чтобы потом его можно было без проблем удалить
}
} else {
init = init + 1;
}
//обработка событий
for(ev_rez = "", off =0; (sval=event.parse(0,"\n",off)).length;) {
//детектируем нажатие на кнопку Delete
if (sval == "ws_BtPress:/button") {
Text1_text = "delete"; //отображаем сообщение delete в текстовом поле, чтобы явно видеть, что событие наступило
this.wdgDel("subItem" + depth); //выполняем удаление вложенного динамически созданного виджета
} else ev_rez += sval+"\n";
}
event = ev_rez;
|
|
|