Пишем GUI, часть 2

18 января 2023, 04:54
Maxim Kuznetsov
0
75

В предыдущей части начали писать простой  GUI , теперь продолжим. В этой части про тестирование, отладку и попробуем встроить форму в чарт

Тестирование

В первой части мы сделали простой скрипт tcl/tk реализующий форму и зацепили его в советник. Разделили работу между дизайном  GUI  и разработкой советника. Кто не читал, советую ознакомиться с первой частью

Предположим получили новый вариант формы от дизайнера: form2.tcl . Запускаем,  tclsh -encoding utf-8 form2.tcl  Вроде как нормально - тёмная тема, добавились часы и ещё одно поле ввода. К кнопкам высвечиваются подсказки. Теперь сравнивним полученный скрипт со старым вариантом (запускаем diff и бегло смотрим что поменялось):

тоже устраивает - изменений немного и они прокоментированны. Можно принимать.

Но ДО ТОГО как вставлять код в советник, неплохо-бы его протестировать. Напишем небольшой скрипт который эмулирует взаимодействие советника и  GUI :

# загружаем скрипт GUI
source -encoding utf-8 form2.tcl

# отладка - печать переменной при её изменении
proc print_BOX { name key mode } {
        global BOX
        puts "BOX=$BOX"
}
trace add variable BOX write print_BOX

# эмулируем цикл работы советника
while true {
        after 30
        update
        if { $BOX != {} } {
                puts "данные формы: $BOX"
                set BOX {}
        }
}

и многократно запускаем тест  tclsh -encoding utf-8 form2_test.tcl , проверяя работу  GUI  вдоль и поперёк.

Всё что можно протестировать вне советника, должно быть протестировано и проверено до добавления к основному коду.

В данном случае было замечено что добавленное поле ввода не очищается при нажатии cancel и подсказки плохо работает у полей ввода. Делаем соответствующее замечание нашему супер-дизайнеру

Отладка и улучшения

Пока вернёмся к MQL5. Текущие цели: добавить средства отладки, интегрировать новые изменения и запланировать дальнейшее

Основное и самое удобное средство отладки это консоль. У нас она есть: надо просто добавить вызовы  package require tkcon  и  tkcon show  после инициализаии интерепретатора. Получим непосредственный командный доступ и можем в любой момент читать/писать переменные, добавлять и изменять процедуры, вызывать команды и прочее прочее. И для дальнейшего передадим хендл(идентификатор) окна чарта. В OnInit добавили 4 строчки и в качестве ресурса взяли новый form2.tcl

#resource "form2.tcl" as string Form_tcl; // заберём скрипт в виде ресурса
 
#include <ATcl/ATcl.mqh>
ATcl *tcl=NULL;
 
int OnInit()
{
   // инициализуем библиотеку
   if (ATcl_OnInit()!=INIT_SUCCEEDED) {
      return INIT_FAILED;
   }
   // создаём интерпретатор 
   tcl=new ATcl();
   if (tcl==NULL || tcl.OnInit()!=INIT_SUCCEEDED) {
      return INIT_FAILED;
   }
   // и делаем себе консоль для отладки
   if (tcl.Eval("package require tkcon")==TCL_OK) {
      tcl.Eval("tkcon show");
   }
   // передаём идентификатор окна
   tcl.Set("chartWin",ChartGetInteger(0,CHART_WINDOW_HANDLE));
   // исполняем скрипт из ресурса
   if (tcl.Eval(Form_tcl)!=TCL_OK) {
      PrintFormat("error in script:%s",tcl.StringResult());
      return INIT_FAILED;
   }
   // задаём таймер
   EventSetMillisecondTimer(50);
   return(INIT_SUCCEEDED);
}

После этих небольших изменений сеанс отладки выглядит вот так:

открылась консоль, в неё командой source я загрузил отладочный скрипт для печати переменной BOX (через которую сейчас идёт приём данных от  GUI ):

# отладка - печать переменной при её изменении
proc print_BOX { name key mode } {
        global BOX
        puts "BOX=$BOX"
}
trace add variable BOX write print_BOX

автоматизируйте свою работу : пишите отладочные скрипты

а пока воспользуемся возможностями консоли и встроим окно в чарт. Воспользуемся пакетом reparent библиотеки ATcl. В консоли во первых убедимся что правильно передали идентификатор окна, затем поменяем окну форму родителя:


теперь окно формы «живёт» внутри чарта. Оно перемещается только внутри него, сворачивается там-же и имеет системные декорации. Если команде reparent последним аргументом задать 0, то наша форма будет без декораций и смотреться станет как элемент чарта. Но когда декораций нет, то перемещать и изменять размер можно только командами. Для управления окнами доступны следующие команды:

  • winmove .win X Y - переместить форму на новую позицию внутри её родителя
  • winsize .win width height - изменить размеры формы
  • winrect .win - вернёт прямоугольник окна
  • winparent .win - вернёт ид.родительского окна
  • winchilds .win - вернёт список дочерних окон
  • reparent id .win 0|1 - сменит родителя .win на окно id

во всех процедурах вместо .win можно указывать идентификатор (хендл)

экспериментируйте и планируйте!

в процессе опытов видны некоторые преодолимые недостатки: 1) при reparent 1 , есть проблемы с перерисовкой окна за пределами формы, впрочем изначально фича с окно-внутри-окна и не планировалась 2) в обоих случаях некоторые странности с фокусом ввода. Как обходятся проблемы с фокусом можно подсмотреть в TradePanel.tcl библиотеки ATcl, про размеры формы и окна будем подсматривать там-же и дополнительно подумаем.

Пока спланируем следующий этап :

  1. В параметрах советнику добавим варианты размещения формы : a) как системное окно б) как окно внутри чарта в) элемент чарта
  2. При инициализации (a) ничего дополнительно делать не надо, при (б) и (в) после создания формы нужны дополнительные действия. Сделаем скрипт, в нём процедуру Embed, скрипт будет тестировать и отлаживать через консоль. Как только результат более-менее удовлетворит добавим в советник.

Оптимизация

Чуть-чуть (почти незаметную малость,но) ускорим работу советника с tcl и нашей формой. Обратим внимание что в таймере постоянно (то есть часто-часто) читаем переменную из интерпретатора и вызываем tcl.Eval то есть делаем полную интрепретацию строки. У нас в наличии есть более быстрые варианты:

  • Tcl нам предоставляет возможность связать переменную tcl и элемент массива double или long. При изменении одного измениться другое и наоборот. Проверить числовой флаг заведомо быстрее чем считать переменную из интерпретатора.
  • Вызов tcl.Call(Tcl_Obj,…) производит вызов объекта и сохраняет внутренние ссылки и байт-код внутри объекта. Это быстрее чем Eval.

Чтобы этим воспользоваться:

  1. в советник добавим массив long flags[1] , в интерпретаторе заведём переменную FLAGS, свяжем их через метод Link; При нажатии на кнопку Ok FLAGS должен инкрементироваться, при чтении в советнике сбрасываться в 0. Забегая сильно вперёд - это заодно позволит потом переместить форму с отдельную нить и вообще разгрузить советник. В итоге  GUI  будет работать отдельно.
  2. чтобы использовать преимущества tcl.Call , при инициализации создадим объекты Tcl_Obj с именами часто используемых процедур и переменных (литералы) и будем их подставлять.
#resource "form2.tcl" as string Form_tcl; // заберём скрипт в виде ресурса
 
#include <ATcl/ATcl.mqh>
ATcl *tcl=NULL;
 
long flags[1]={0};   // флаги
Tcl_Obj var_BOX=0;   // имя переменной BOX
 
int OnInit()
{
   // инициализуем библиотеку
   if (ATcl_OnInit()!=INIT_SUCCEEDED) {
      return INIT_FAILED;
   }
   // создаём интерпретатор 
   tcl=new ATcl();
   if (tcl==NULL || tcl.OnInit()!=INIT_SUCCEEDED) {
      return INIT_FAILED;
   }
   tcl.Eval("set FLAGS 0");   // создаём переменную 
   tcl.Link(flags,0,"FLAGS"); // связываем с элементом массива
   var_BOX=tcl.Obj("BOX");    // литерал для переменной BOX
   tcl.Ref(var_BOX);
   // и делаем себе консоль для отладки
   if (tcl.Eval("package require tkcon")==TCL_OK) {
      tcl.Eval("tkcon show");
   }
   // передаём идентификатор окна
   tcl.Set("chartWin",ChartGetInteger(0,CHART_WINDOW_HANDLE));
   // исполняем скрипт из ресурса
   if (tcl.Eval(Form_tcl)!=TCL_OK) {
      PrintFormat("error in script:%s",tcl.StringResult());
      return INIT_FAILED;
   }
   // задаём таймер
   EventSetMillisecondTimer(50);
   return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
   if (tcl!=NULL) {
      tcl.Unlink("FLAGS");
      tcl.Unref(var_BOX);
      tcl.OnDeinit(reason);
      delete tcl;
   }
   EventKillTimer();
}
void OnTick()
{
   // пока ничего
}
void OnTimer()
{
   tcl.Update();  
   if (flags[0]!=0) {   
      // GUI инкрементировал FLAGS - надо проверить что там в BOX
 
      Tcl_Obj box=tcl.Get(var_BOX); // получаем значение переменной по её литералу
      if (box!=0) {
         tcl.Ref(box);   // будем работать с объектом tcl - увеличим ему число ссылок
         if (tcl.Count(box)>0) {
            // не пустое значение - пользвоатель нажал кнопку
            // код у нас первым в списке
            long code=tcl.Long(box,0);
            // вторым - значение из поля ввода
            string value=tcl.String(box,1);
            // третьим - новое добавленное поле
            string value2=tcl.String(box,2);
            if (code==1) {
               // как сам-с-собой договорился код 1 - нажата кнопка Ok
               Alert("Текст в форме:"+value);
            }
            // и очистим переменную (зададим пустое значение)
            tcl.Eval("set BOX {}");
         }
         tcl.Unref(box);   // закончили работать с объектом - уменьшили число ссылок
      }
      flags[0]=0;
   }
}

вносим коррективы (печать переменной FLAGS при её изменении из gui, по аналогии с BOX) в скрипт отладки и проверяем с консолью :

окончательное встраивание формы в чарт завершим в следующей части, по намеченному выше плану, а сейчас промежуточный итог:

  • замечания про  GUI , чуть подправленный form2.tcl (с инкрементом флага) и скрипты form2_test.tcl и form2_debug.tcl отправим дизайнеру/программисту  GUI . Он будет исправлять замечания, проверять по form2_test.tcl и когда прверки проходят передавать результат.
  • советник с формой замечательно и быстро работают, и ещё обрели средство отладки
  • будем делать скрипт form2_embed.tcl в ней процедуру Embed . Как только результат запусков через консоль нас устроит будем добавлять в советник
  • есть перспектива вынести форму в отдельный тред, чтобы её работа как можно меньше сказывалась на реакции советника

все исходники, то есть советник, форма и тестирование/отладка приложены

Установить библиотеку ATcl можно через инсталлятор или следуя инструкциям в репозитории проекта

PS/ несколько часов назад веб-мастера mql начали блокировать переход на инсталлятор.

Скачать можно по резервному адресу https://www.filefactory.com/file/6zj49q87vhkk/atclsetup.exe

ещё вариант https://dfiles.eu/files/9muo987ad

или через эскизный сайт проекта http://atcl.unaux.com/download/

приношу свои извинения


Файлы:
form2.zip  5 kb