В предыдущей части начали писать простой 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, про размеры формы и окна будем подсматривать там-же и дополнительно подумаем.
Пока спланируем следующий этап :
- В параметрах советнику добавим варианты размещения формы : a) как системное окно б) как окно внутри чарта в) элемент чарта
- При инициализации (a) ничего дополнительно делать не надо, при (б) и (в) после создания формы нужны дополнительные действия. Сделаем скрипт, в нём процедуру Embed, скрипт будет тестировать и отлаживать через консоль. Как только результат более-менее удовлетворит добавим в советник.
Оптимизация
Чуть-чуть (почти незаметную малость,но) ускорим работу советника с tcl и нашей формой. Обратим внимание что в таймере постоянно (то есть часто-часто) читаем переменную из интерпретатора и вызываем tcl.Eval то есть делаем полную интрепретацию строки. У нас в наличии есть более быстрые варианты:
- Tcl нам предоставляет возможность связать переменную tcl и элемент массива double или long. При изменении одного измениться другое и наоборот. Проверить числовой флаг заведомо быстрее чем считать переменную из интерпретатора.
- Вызов tcl.Call(Tcl_Obj,…) производит вызов объекта и сохраняет внутренние ссылки и байт-код внутри объекта. Это быстрее чем Eval.
Чтобы этим воспользоваться:
- в советник добавим массив long flags[1] , в интерпретаторе заведём переменную FLAGS, свяжем их через метод Link; При нажатии на кнопку Ok FLAGS должен инкрементироваться, при чтении в советнике сбрасываться в 0. Забегая сильно вперёд - это заодно позволит потом переместить форму с отдельную нить и вообще разгрузить советник. В итоге GUI будет работать отдельно.
- чтобы использовать преимущества 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/
приношу свои извинения