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

19 января 2023, 18:34
Maxim Kuznetsov
0
64

Завершающая статья из мини-цикла про разработку  GUI  на tcl/tk

Для общего понимания о чём тут, настоятельно рекомендую прочесть первую и вторую часть

Итак, мы пишем скрипт form3_embed.tcl и в нём процеду Embed. До того как добавлять непосредственно в советник запускаем его в консоли.

В торговый советник добавляем только работающий и стабильный код

Изучаем полезную документацию :

  1. команда wm http://www.tcl-lang.org/man/tcl8.6/TkCmd/wm.htm - интерфейс с оконным менеджером,
  2. winfo http://www.tcl-lang.org/man/tcl8.6/TkCmd/winfo.htm - получение данных окон Tk
  3. toplevel http://www.tcl-lang.org/man/tcl8.6/TkCmd/toplevel.htm - окна верхнего уровня Tk (их настройки)
  4. bind http://www.tcl-lang.org/man/tcl8.6/TkCmd/bind.htm - «перехват» сообщений Tk

В первой итерации наша процедура Embed пусть выводит на печать все что можно вывести:

# встроить в чарт окошко
# chart - handle окна чарта
# w - встраиваемое окно
# level - уровень (передаваемый далее в reparent) 0-как виджет 1-как окно
package require reparent
proc noop {} {}
proc Embed { chart w level } {
        # убираем у него всякие границы
        $w configure -borderwidth 0
        $w configure -padx 0
        $w configure -pady 0
        # забираем окно у wm (он теперь не отвечает за декор)
        wm overrideredirect $w 1
        update
        if { [ winfo exists .tkcon ] } {
                # если ведётся отладка в консоли
                # будем печатать события
                # протольное событие (see wm)
                wm protocol $w WM_TAKE_FOCUS [ list puts "WM_TAKE_FOCUS" ]
                wm protocol $w WM_DELETE_WINDOW [ list noop ]
                # оконные события (see bind)
                bind $w <Activate> [ list puts "%W Activate" ]
                bind $w <Deactivate> [ list puts "%W Deactivate" ]
                bind $w <MouseWheel> [ list puts "%W MouseWheel" ]
                bind $w <ButtonPress> [ list puts "%W ButtonPress" ]
                bind $w <ButtonRelease> [ list puts "%W ButtonRelease" ]
                bind $w <FocusOut> [ list puts "%W FocusOut" ]
                bind $w <FocusIn> [ list puts "%W FocusIn" ]
                bind $w <KeyPress> [ list puts "%W KeyPress" ]
                bind $w <Configure> [ list puts "%W Configure" ]
                bind $w <ResizeRequest> [ list puts "%W ResizeRequest" ]
                bind $w <ConfigureRequest> [ list puts "%W ConfigureRequest" ]
                bind $w <MapRequest> [ list puts "%W MapRequest" ]
                bind $w <Map> [ list puts "%W Map" ]
        }
        reparent $chart $w $level
        update
}

смотрим, что происходит и как можно преодолеть проблемы с фокусом ввода и отрисовками окна. В консоли загружаем скрипт  source mql5/experts/form2_embed.tcl  и запускаем процедуру  Embed $chartWin .win 1  (и с параметром 0 тоже) :

проблема с фокусом ввода становится понятной: когда наше окно отдаёт фокус ввода родительскому окну (чарту), обратно он уже не возвращается. Но мы можем его забрать по клику на элемент формы.

а вот с режимом окно-в-окне, несколько сложнее - события windows  move   resize  не перетраслируются в tk. Явно что на уровне скриптов tcl или mql это не исправить, поэтому обратимся в автору ATcl (или дабавим тикет в проект :https://chiselapp.com/user/nektomk/repository/atcl-lib/rptview?rn=1) - «необходимо чтобы после reparent 1 транслировались события виндовс move/resize и неплохо-бы запрещать пользователю менять размеры окна»;

до разрешения этих проблем, фичу окно-в-окне пока не будем демонстрировать конечному пользователю

доделываем form2_embed.tcl добавив обработку фокуса ввода:

package require reparent
proc noop {} {}

# признак, что фокус ввода у нашей формы
set hasFocus 0  
# обработка оконных сообщений
# w - окно tk (toplevel формы или дочерний виджет)
proc OnWindowEvent { w ev } {
        # если включена консоль - напечатаем
        if { [ winfo exists .tkcon ] } {
                puts "$w $ev"
        }
        global hasFocus
        if { $hasFocus && $ev == "FocusOut" } {
                # потеряли фокус ввода
                set hasFocus 0
        }
        if { !$hasFocus && ( $ev in { "Activate" "FocusIn" "MouseWheel" "ButtonPress" "KeyPress" } ) } {
                # произошло событие когда фокус ввода стоит забрать
                # сообщаем windows что хотим забрать фокус
                winfocus .win
                # сообщаем tk куда его поместить
                focus $w
                set hasFocus 1
        }
}

# встроить в чарт окошко
# chart - handle окна чарта
# w - встраиваемое окно
# level - уровень (передаваемый далее в reparent) 0-как виджет 1-как окно
proc Embed { chart w level } {
        # убираем у него всякие границы
        $w configure -borderwidth 0
        $w configure -padx 0
        $w configure -pady 0

        # забираем окно у wm (он теперь не отвечает за декор)
        wm overrideredirect $w 1
        # если ведётся отладка в консоли
        # будем печатать события
        # протольное событие (see wm)
        #wm protocol $w WM_TAKE_FOCUS [ list puts "WM_TAKE_FOCUS" ]
        wm protocol $w WM_DELETE_WINDOW [ list noop ]
        # оконные события (see bind)
        bind $w <Activate> +[ list OnWindowEvent %W Activate ]
        bind $w <Deactivate> +[ list OnWindowEvent %W Deactivate ]
        bind $w <MouseWheel> +[ list OnWindowEvent %W MouseWheel ]
        bind $w <ButtonPress> +[ list OnWindowEvent %W ButtonPress ]
        bind $w <ButtonRelease> +[ list OnWindowEvent %W ButtonRelease ]
        bind $w <FocusOut> +[ list OnWindowEvent %W FocusOut ]
        bind $w <FocusIn> +[ list OnWindowEvent %W FocusIn ]
        bind $w <KeyPress> +[ list OnWindowEvent %W KeyPress ]
        bind $w <Configure> +[ list OnWindowEvent %W Configure ]
        bind $w <ResizeRequest> +[ list OnWindowEvent %W ResizeRequest ]
        bind $w <ConfigureRequest> +[ list OnWindowEvent %W ConfigureRequest ]
        bind $w <MapRequest> +[ list OnWindowEvent %W MapRequest ]
        bind $w <Map> +[ list OnWindowEvent %W Map ]
        # меняем родителя
        wm withdraw .win
        reparent $chart $w $level
        catch { wm deiconify .win }
        raise .win
}

Из консоли работает, теперь добавляем в советник.

Изменения в советнике

Как и планировали - добавим параметр про встраивание формы, и если указано то вызовем Embed

enum ENUM_FORM_MODE {
   FORM_SYSTEM,      // системное окно
// FORM_CHART_WINDOW // окно в чарте   
   FORM_CHART_WIDGET // элемент чарта
};
input ENUM_FORM_MODE FORM_MODE=FORM_SYSTEM; // Расположение формы:
 
#resource "form2.tcl" as string Form_tcl; // заберём скрипт в виде ресурса
#resource "form2_embed.tcl" as string Form2_embed_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;
   }
   // скрипт встраивания
   if (tcl.Eval(Form2_embed_tcl)!=TCL_OK) {
      PrintFormat("error in script:%s",tcl.StringResult());
      return INIT_FAILED;
   }
   // встраивание формы в чарт
   if (FORM_MODE==FORM_CHART_WIDGET) {
      if (tcl.Eval("Embed $chartWin .win 0")!=TCL_OK) {
         PrintFormat("error in embed:%s",tcl.StringResult());
         return INIT_FAILED;
      }
      // и сразу передвинем
      tcl.Eval("winmove .win 10 15"); 
   }
   // задаём таймер
   EventSetMillisecondTimer(50);
   return(INIT_SUCCEEDED);
}

включаем, всё вроде хорошо, но что-то опять фокус подключивает

тут уже надо «подыграть» со стороны советника:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // если TAB получен как кнопка
   // занчит закончен переход фокуса по контролам чарта
   // отдадим фокус форме
   if (id == CHARTEVENT_KEYDOWN && lparam==9) {
      tcl.Eval("winfocus .win");
   }
   tcl.Update();
}

вот и закончили c GUI:

форма отображается, советник может реагировать на ввод в форму. Дополнительно сделали что форма может быть элементом чарта или окном виндовс.

Более развёрнутый пример и использования разных виджетов в интерфейсе, посмотрите в TradePanel в соcтаве библиотеки:

 

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

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

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

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

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

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


Поделитесь с друзьями: