English 中文 Español Deutsch 日本語 Português
Графические интерфейсы I: Тестируем библиотеку в программах разных типов и в терминале MetaTrader 4 (Глава 5)

Графические интерфейсы I: Тестируем библиотеку в программах разных типов и в терминале MetaTrader 4 (Глава 5)

MetaTrader 5Примеры | 26 января 2016, 14:15
5 143 0
Anatoli Kazharski
Anatoli Kazharski

Содержание

 

Введение

Эта статья является продолжением первой части серии о графических интерфейсах. Более подробно, для чего предназначена эта библиотека, можно прочитать в самой первой статье: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). Полный список ссылок на статьи первой части находится в конце каждой главы и там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.

В предыдущей главе первой части серии о графических интерфейсах в класс формы были добавлены методы, которые позволяют управлять формой посредством нажатия на ее элементах управления. До этого тесты проводились в программе типа "эксперт" и только в терминале MetaTrader 5. В этой статье протестируем проделанную работу в разных типах MQL-программ, таких как индикаторы и скрипты. А поскольку библиотека задумывалась как кросс-платформенная (в рамках торговых платформ MetaTrader), то проведем тесты также и в MetaTrader 4.

 

Использование формы в индикаторах

В директории индикаторов терминала MetaTrader 5 (<каталог_данных>\MQL5\Indicators) создайте отдельную папку для нового индикатора. Так же, как и в случае с экспертом, которого тестировали ранее, в этой папке нужно создать главный файл программы и файл Program.mqh, в котором будет содержаться класс CProgram. На самом деле просто скопируйте в папку с индикатором файл Program.mqh из папки того эксперта. На данном этапе этого кода будет достаточно, чтобы протестировать форму элементов управления в индикаторе. А в главном файле индикатора создайте код, как показано в листинге кода ниже.

Строка, отмеченная желтым маркером, означает, что индикатор будет расположен в главном окне графика. Количество индикаторных буферов можно установить в ноль, так как сейчас мы просто тестируем элементы графического интерфейса в индикаторе и не будем производить расчеты с массивами цен.

//+------------------------------------------------------------------+
//|                                                  ChartWindow.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0
//--- Подключение класса торговой панели
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   program.OnInitEvent();  
//--- Установим торговую панель
   if(!program.CreateTradePanel())
     {
      ::Print(__FUNCTION__," > Не удалось создать графический интерфейс!");
      return(INIT_FAILED);
     }
//--- Инициализация прошла успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   program.OnDeinitEvent(reason);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int    rates_total,     // размер массива price[]
                 const int    prev_calculated, // обработано баров на предыдущем вызове
                 const int    begin,           // откуда начинаются значимые данные
                 const double &price[])        // массив для расчета
  {
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
   program.OnTimerEvent();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   program.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Скомпилируйте индикатор и загрузите его на график. В итоге вы должны получить результат, как на скриншоте ниже. Как и в эксперте, форму можно перемещать по графику, сворачивать/разворачивать, ее элементы будут реагировать на перемещение курсора, а если нажать кнопку «Закрыть» (крестик в правом верхнем углу), индикатор удалится с графика.

Рис. 1. Тест формы в индикаторе в главном окне графика.

Рис. 1. Тест формы в индикаторе в главном окне графика

Обратите также внимание на ярлык формы в ее левом верхнем углу. Программа автоматически определила тип программы и установила картинку по умолчанию. Не забывайте, что на данном этапе разработки класса CWindow уже есть возможность переопределить эту картинку.

Без каких-либо существенных изменений кода форма для элементов управления работает и в индикаторе. Но если сейчас попробовать создать индикатор с этой же формой не в главном окне графика, то не все будет работать так, как ожидается: (1) форма установится в главном окне графика, а не в подокне индикатора, как это нужно, (2) нажав на кнопку «Закрыть», не получится удалить индикатор с графика. В чем же дело? Далее будем разбираться, как это исправить.

На самом деле из того, что было сделано ранее, исправлять ничего не нужно. Достаточно создать в классе CWndEvents метод, в котором будет автоматически определяться номер окна в программе в зависимости от ее типа и количества других индикаторов на графике, которые находятся в подокнах. Назовем этот метод DetermineSubwindow(). В начале этого метода должна быть проверка, является ли программа индикатором. Если это не индикатор, то нет смысла идти дальше, так как из всех типов MQL-программ в подокнах размещаются только индикаторы.

Затем с помощью функции ChartWindowFind() нужно получить номер окна индикатора. Если это не удалось и функция вернула -1, то в журнал выводится сообщение об этом и метод завершает работу. Если же номер окна больше нуля, то есть оно является не главным окном графика, то далее нужно проверить, есть ли в этом подокне другие индикаторы. Если есть, то наш индикатор будет удаляться с графика, сообщив о причине удаления в журнал. Для этого с помощью функции ChartIndicatorsTotal() нужно получить общее количество индикаторов в указанном подокне. Получить короткое имя последнего в списке индикатора и затем, если количество индикаторов не равно 1, удалить программу с графика. Получение короткого имени индикатора обязательно, так как удалить индикатор с графика можно только указав его короткое имя.

Добавьте объявление и реализацию метода DetermineSubwindow() в класс CWndEvents, как это показано листинге кода ниже:

class CWndEvents : public CWndContainer
  {
private:
   //--- Определение номера подокна
   void              DetermineSubwindow(void);
  };
//+------------------------------------------------------------------+
//| Определение номера подокна                                       |
//+------------------------------------------------------------------+
void CWndEvents::DetermineSubwindow(void)
  {
//--- Если тип программы не индикатор, выйдем
   if(PROGRAM_TYPE!=PROGRAM_INDICATOR)
      return;
//--- Сброс последней ошибки
   ::ResetLastError();
//--- Определение номера окна индикатора
   m_subwin=::ChartWindowFind();
//--- Если не получилось определить номер, выйдем
   if(m_subwin<0)
     {
      ::Print(__FUNCTION__," > Ошибка при определении номера подокна: ",::GetLastError());
      return;
     }
//--- Если это не главное окно графика
   if(m_subwin>0)
     {
      //--- Получим общее количество индикаторов в указанном подокне
      int total=::ChartIndicatorsTotal(m_chart_id,m_subwin);
      //--- Получим короткое имя последнего индикатора в списке
      string indicator_name=::ChartIndicatorName(m_chart_id,m_subwin,total-1);
      //--- Если в подокне уже есть индикатор, то удалить программу с графика
      if(total!=1)
        {
         ::Print(__FUNCTION__," > В этом подокне уже есть индикатор.");
         ::ChartIndicatorDelete(m_chart_id,m_subwin,indicator_name);
         return;
        }
     }
  }

Вызов этого метода нужно осуществлять в конструкторе класса CWndEvents:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
//--- Определение номера подокна
   DetermineSubwindow();
  }

Вот теперь библиотека готова для индикаторов, которые создаются не в главном окне графика, и далее нужно протестировать, как это работает. Если в процессе тестирования будут обнаружены ошибки или недоработки, исправим их.

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

  1. Свободный режим. Высота подокна индикатора не фиксирована, то есть свободно может изменяться пользователем. При сворачивании формы элементов управления высота подокна индикатора не изменяется и остается свободной для изменения пользователем.
  2. Фиксированный. Можно зафиксировать высоту подокна индикатора по размеру установленной высоты формы элементов управления. При сворачивании формы высота подокна остается той же и в фиксированном режиме.
  3. Фиксированный с возможностью сворачивания. То есть при загрузке индикатора на график, размер подокна фиксируется по размеру установленной высоты формы элементов управления, но при сворачивании принимает размер заголовка формы.

Для удобной установки того или иного режима из предложенных выше ранее в классе CWindow уже был создан метод RollUpSubwindowMode(). Уже скоро будет продемонстрировано, как его нужно использовать.

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

  1. в левой части окна графика;
  2. в правой части окна графика;
  3. на всю ширину графика.

При выборе третьего варианта ширина формы должна изменяться при изменении ширины графика. На текущий момент в классе CWindow пока нет метода, который позволил бы это сделать. Создадим такой метод и назовем его ChangeWindowWidth(). В начале метода будет сравнение текущей ширины со значением, которое передано в метод, и если передано отличающееся значение, значит, нужно изменить ширину объектов формы и обновить координаты кнопок.

Объявление и реализация метода CWindow::ChangeWindowWidth():

class CWindow : public CElement
  {
public:
   //--- Изменяет ширину окна
   void              ChangeWindowWidth(const int width);
  };
//+------------------------------------------------------------------+
//| Изменяет ширину окна                                             |
//+------------------------------------------------------------------+
void CWindow::ChangeWindowWidth(const int width)
  {
//--- Если ширина не изменилась, выйдем
   if(width==m_bg.XSize())
      return;
//--- Обновим ширину для фона и заголовка
   CElement::XSize(width);
   m_bg.XSize(width);
   m_bg.X_Size(width);
   m_caption_bg.XSize(width);
   m_caption_bg.X_Size(width);
//--- Обновим координаты и отступы для всех кнопок:
//--- Кнопка закрытия
   int x=CElement::X2()-CLOSE_BUTTON_OFFSET;
   m_button_close.X(x);
   m_button_close.XGap(x-m_x);
   m_button_close.X_Distance(x);
//--- Кнопка разворачивания
   x=CElement::X2()-ROLL_BUTTON_OFFSET;
   m_button_unroll.X(x);
   m_button_unroll.XGap(x-m_x);
   m_button_unroll.X_Distance(x);
//--- Кнопка сворачивания
   m_button_rollup.X(x);
   m_button_rollup.XGap(x-m_x);
   m_button_rollup.X_Distance(x);
//--- Кнопка всплывающих подсказок (если включена)
   if(m_tooltips_button)
     {
      x=CElement::X2()-TOOLTIP_BUTTON_OFFSET;
      m_button_tooltip.X(x);
      m_button_tooltip.XGap(x-m_x);
      m_button_tooltip.X_Distance(x);
     }
  }

Сделайте три копии индикатора, который создали ранее для тестов в главном окне графика. Каждый из них будет для одного из перечисленных выше режимов. Дайте каждому уникальное название.

Например:

  • FreeHeight — для свободного режима.
  • RollUp — для фиксированного режима.
  • DownFall — для фиксированного режима с возможностью сворачивания.

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

//+------------------------------------------------------------------+
//|                                                   FreeHeight.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots   0

Далее в каждом подключаемом файле Program.mqh в самом начале нужно добавить перечисление (enum) и внешний параметр (input) для выбора режима установки формы:

//--- Перечисление режимов установки окна
enum ENUM_WINDOW_MODE
  {
   LEFT  =0,
   RIGHT =1,
   FULL  =2
  };
//--- Внешние параметры
input ENUM_WINDOW_MODE WindowMode=LEFT;

Если во внешних настройках будет выбран вариант RIGHT или FULL, то в момент, когда будет изменяться размер окна графика, программа будет корректировать координаты формы (в режиме RIGHT) либо ее размер (в режиме FULL) так, чтобы ее правый край не выходил за границу графика.

Думаю, что нет никакого смысла делать форму перемещаемой в таком ограниченном пространстве, как подокно индикатора. В текущей реализации формы, если в ее свойствах установлено, что она не перемещаема, не будет производиться корректировка при изменении размеров графика. Поэтому для неперемещаемых форм корректировку нужно делать самостоятельно во внутреннем обработчике событий графика разрабатываемого MQL-приложения. Во всех файлах Program.mqh созданных индикаторов добавьте в обработчик CProgram::OnEvent() код, который представлен в листинге ниже:

//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Если выбран режим формы на всю ширину графика
      if(WindowMode==FULL)
         m_window.ChangeWindowWidth(m_chart.WidthInPixels()-2);
      //--- Если выбран режим установки формы справа
      else if(WindowMode==RIGHT)
         m_window.X(m_chart.WidthInPixels()-(m_window.XSize()+1));
     }
  }

Как видите, на текущий момент во всех файлах создаваемых индикаторов все абсолютно идентично. И мы, наконец, подошли к ответу на вопрос, как использовать метод CWindow::RollUpSubwindowMode() для установки режима подокна индикатора, в котором размещается форма для элементов управления.

По умолчанию значения переменных класса CWindow, на которые программа ориентируется для определения режима подокна индикатора, инициализируются в конструкторе класса для использования режима, когда высоту подокна индикатора можно изменять. Поэтому для индикатора FreeHeight этот метод можно вообще не использовать. Код метода для установки формы в подокно в классе CProgram будет выглядеть, как показано в листинге ниже. Обратите внимание на то, что ширина формы и ее координаты определяются в зависимости от выбранного варианта во внешних настройках. Желтым маркером отмечено место, где нужно использовать метод CWindow::RollUpSubwindowMode() в индикаторах с другими режимами.

//+------------------------------------------------------------------+
//| Создает форму для элементов управления                           |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window);
//--- Размеры
   int x_size=(WindowMode!=FULL)? 205 : m_chart.WidthInPixels()-2;
   int y_size=243;
//--- Координаты
   int x=(WindowMode!=RIGHT)? 1 : m_chart.WidthInPixels()-(x_size+1);
   int y=1;
//--- Свойства
   m_window.XSize(x_size);
   m_window.YSize(y_size);
// ...
//--- Создание формы
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Индикатор RollUp будет использоваться в режиме, когда подокно индикатора будет фиксированной высоты и при сворачивании формы не будет изменяться. Поэтому в место, которое отмечено в листинге кода выше, нужно добавить вот такую строчку кода:

m_window.RollUpSubwindowMode(false,true);

Значение первого параметра означает, что подокно не нужно сворачивать при сворачивании формы, а значение второго параметра означает, что подокно должно быть фиксированной высоты, равной высоте формы. А для индикатора DownFall параметры в метод CWindow::RollUpSubwindowMode() нужно указывать вот так:

m_window.RollUpSubwindowMode(true,true);

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

Скомпилируйте файлы, в которые вносили изменения, и файлы индикаторов. Протестируйте каждый из них на графике. Относительно установленных в них режимов все работает корректно. Но вы заметите, что при изменении размеров окна графика координаты (в режиме RIGHT) и ширина формы (в режиме FULL) не корректируются. Ничего удивительного! Ведь сейчас в обработчик событий графика OnEvent(), в классе CProgram, поток событий не поступает. Ранее, когда конструировали основную схему библиотеки, уже упоминалось о том, что из класса CWndEvents в методе ChartEvent() можно направлять поток событий графика в локальный обработчик OnEvent(), который находится в классе CProgram, то есть в классе разрабатываемого приложения.

Разместим направление события сразу после прохода по всем обработчикам событий всех элементов управления в методе CWndEvents::CheckElementsEvents():

//+------------------------------------------------------------------+
//| Проверка событий элементов управления                            |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
//--- Направление события в файл приложения
   OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

Но и сейчас корректировка координат формы в режиме RIGHT будет осуществляться некорректно. Дело в том, что программа, зайдя в обработчик класса CWindow по событию CHARTEVENT_CHART_CHANGE, не обновит координаты формы, так как в методе UpdateWindowXY() обновление координат осуществляется, если форма перемещаема. Помним, что в индикаторах (условились ранее) форма будет зафиксирована. Закончив проход по всем элементам управления, программа перейдет в обработчик событий графика в класс приложения CProgram, как это было сделано в листинге кода выше, и произведет там обновление координаты X в соответствии с текущей шириной окна графика.

Далее программа, выйдя из метода CheckElementsEvents() в классе CWndEvents, зайдет в метод CWndEvents::ChartEventMouseMove() и сразу выйдет из него, так как событие не относится к событиям мыши. А на текущий момент обновление положения формы в соответствии с актуальными координатами в свойствах формы производится только в методе CWndEvents::ChartEventMouseMove(). Поэтому пришло время добавить в метод CWndEvents::ChartEvent() обработку события CHARTEVENT_CHART_CHANGE (в коде ниже отмечено желтым маркером).

//+------------------------------------------------------------------+
//| Обработка событий программы                                      |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Если массив пуст, выйдем
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Инициализация полей параметров событий
   InitChartEventsParams(id,lparam,dparam,sparam);
//--- Проверка событий элементов интерфейса
   CheckElementsEvents();
//--- Событие перемещения мыши
   ChartEventMouseMove();
//--- Событие изменения свойств графика
   ChartEventChartChange();
  }

В тело метода CWndEvents::ChartEventChartChange() добавьте вызов функции перемещения окна по актуальным координатам и перерисовку графика:

//+------------------------------------------------------------------+
//| Событие CHARTEVENT CHART CHANGE                                  |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventChartChange(void)
  {
//--- Событие изменения свойств графика
   if(m_id!=CHARTEVENT_CHART_CHANGE)
      return;
//--- Перемещение окна
   MovingWindow();
//--- Перерисуем график
   m_chart.Redraw();
  }

Скомпилируйте файлы библиотеки и индикаторов. Попробуйте сейчас протестировать их, загрузив на график. Теперь координаты формы в режиме RIGHT обновляются корректно.

Есть еще один нюанс, который не был учтен до этого момента. В текущей реализации при загрузке одного из созданных индикаторов с формой элементов управления на график номер подокна определяется программой автоматически в методе CWndEvents::DetermineSubwindow() в конструкторе класса. Но если загрузить один из этих индикаторов на график, когда на нем уже есть какой-нибудь индикатор, который тоже расположен в подокне, то все будет нормально работать, пока этот индикатор на графике. Стоит только его удалить, подокно нашего индикатора получит новый номер в списке окон графика, и так как библиотеке нужен актуальный номер окна, в котором она работает, например при определении относительной координаты, то некоторый функционал будет работать некорректно. Поэтому нужно отслеживать количество окон графика, и как только их количество изменилось, нужно обновлять значения в полях тех классов, где это нужно.

При загрузке и удалении программ на график генерируется событие CHARTEVENT_CHART_CHANGE. Поэтому сейчас нужно разместить проверку в методе CWndEvents::ChartEventChartChange(). Создадим новый метод, предназначенный для такой проверки, и назовем его CWndEvents::CheckSubwindowNumber(). В листинге кода ниже представлено его объявление и реализация. Сначала идет проверка на соответствие номера окна программы и номера, который был сохранен при загрузке программы на график. Если они не совпадают, нужно заново определить текущий номер окна, в котором находится программа, и затем обновить его во всех элементах управления всех форм.

class CWndEvents : public CWndContainer
  {
private:
   //--- Проверка событий элементов управления
   void              CheckSubwindowNumber(void);
   //---
  };
//+------------------------------------------------------------------+
//| Проверка и обновление номера окна программы                      |
//+------------------------------------------------------------------+
void CWndEvents::CheckSubwindowNumber(void)
  {
//--- Если программа в подокне и номера не совпадают
   if(m_subwin!=0 && m_subwin!=::ChartWindowFind())
     {
      //--- Определить номер подокна
      DetermineSubwindow();
      //--- Сохранить во всех элементах
      int windows_total=CWndContainer::WindowsTotal();
      for(int w=0; w<windows_total; w++)
        {
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].SubwindowNumber(m_subwin);
        }
     }
  }

Вызов этого метода нужно осуществлять в методе CWndEvents::ChartEventChartChange():

//+------------------------------------------------------------------+
//| Событие CHARTEVENT CHART CHANGE                                  |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventChartChange(void)
  {
//--- Событие изменения свойств графика
   if(m_id!=CHARTEVENT_CHART_CHANGE)
      return;
//--- Проверка и обновление номера окна программы
   CheckSubwindowNumber();
//--- Перемещение окна
   MovingWindow();
//--- Перерисуем график
   m_chart.Redraw();
  }

Теперь все будет работать так, как задумывалось. Все индикаторы для тестирования рассмотренных выше режимов можно загрузить на компьютер в конце статьи.

Рис. 2. Тест формы в индикаторах в подокнах графика.

Рис. 2. Тест формы в индикаторах в подокнах графика

В текущей реализации для корректной работы на одном графике рекомендуется использовать не более одного приложения, в котором при его разработке использовалась библиотека для построения графических интерфейсов. На скриншоте выше несколько индикаторов на одном графике показаны лишь для компактной демонстрации всех рассмотренных режимов и для экономии места в статье. Конфликт между приложениями, в которых используется библиотека, будет возникать в момент включения/отключения прокрутки графика. Уже есть некоторые идеи, как можно исключить такой конфликт между приложениями, поэтому если тесты пройдут успешно и решение окажется приемлемым, в одной из будущих статей этой серии оно будет опубликовано.

 

Использование формы в скриптах

Мы уже рассмотрели использование формы в экспертах и индикаторах. Но можно ли использовать форму в скриптах? Ответ — да. Конечно же, в скриптах все будет работать в очень ограниченном виде. В скриптах нет обработчиков событий. Поэтому (1) форму нельзя будет перемещать, (2) нет смысла устанавливать на форму элементы управления, которые предназначены для каких-то манипуляций, (3) графические объекты не будут реагировать на перемещение мыши.

Но если вам нужно быстро создать информационную панель, которая работает во время работы скрипта и показывает пользователю, например, какие-то статистические данные, то почему бы не использовать форму как основу для этого? При этом, за счет использования одной библиотеки, все ваши приложения будут иметь один дизайн, независимо от того, к какому типу MQL-программ они относятся.

Создайте папку, в которой будет главный файл скрипта и файл Program.mqh. Класс CProgram в этом файле также будет производным от CWndEvents, как это рассматривалось выше в эксперте и индикаторах. В общем, все то же самое, кроме того, что будет только один обработчик OnEvent(), событие для которого будет искусственно генерироваться в главном файле скрипта в вечном цикле. У этого метода будет только один параметр, который означает количество миллисекунд для паузы, прежде чем выйти из метода.

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- Окно
   CWindow           m_window;
   //---
public:
                     CProgram(void);
                    ~CProgram(void);
   //--- Обработчик события
   virtual void      OnEvent(const int milliseconds);
   //--- Создает информационную панель
   bool              CreateInfoPanel(void);
   //---
protected:
   //--- Создает форму
   bool              CreateWindow(const string text);
  };

Для примера, в качестве имитации хоть какого-то процесса, в методе CProgram::OnEvent() через интервалы времени, равные количеству переданных миллисекунд, будем изменять текст заголовка формы. Сделаем бегущее многоточие, которое будет показывать, что происходит какой-то процесс. В листинге кода ниже показано, как это реализовано:

//+------------------------------------------------------------------+
//| События                                                          |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int milliseconds)
  {
   static int count =0;  // Счетчик
   string     str   =""; // Строка для заголовка
//--- Формирование заголовка с ходом процесса
   switch(count)
     {
      case 0 : str="SCRIPT PANEL";     break;
      case 1 : str="SCRIPT PANEL .";   break;
      case 2 : str="SCRIPT PANEL ..";  break;
      case 3 : str="SCRIPT PANEL ..."; break;
     }
//--- Обновить строку заголовка
   m_window.CaptionText(str);
//--- Перерисовка графика
   m_chart.Redraw();
//--- Увеличим счетчик
   count++;
//--- Если больше трех, обнулить
   if(count>3)
      count=0;
//--- Пауза
   ::Sleep(milliseconds);
  }

В главном файле скрипта в функции OnStart() теперь достаточно создать форму и организовать постоянный вызов метода CProgram::OnEvent(). В коде ниже показано, что этот метод будет вызываться каждые 250 миллисекунд. И это будет будет работать, пока функция IsStopped() возвращает значение false. Чтобы остановить этот вечный цикл, нужно просто вручную удалить скрипт с графика. Функция IsStopped() при удалении программы пользователем вернет значение true, цикл остановится, и скрипт завершит свою работу.

//+------------------------------------------------------------------+
//|                                                    InfoPanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.0"
//--- Подключение класса торговой панели
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart(void)
  {
//--- Установим информационную панель
   if(!program.CreateInfoPanel())
     {
      ::Print(__FUNCTION__," > Не удалось создать графический интерфейс!");
      return;
     }
//--- Скрипт будет работать пока его не удалят
   while(!::IsStopped())
     {
      //--- Генерировать событие каждые 250 миллисекунд
      program.OnEvent(250);
     }
  }
//+------------------------------------------------------------------+

Скомпилируйте файлы и загрузите скрипт на график:

Рис. 3. Тест формы в скрипте.

Рис. 3. Тест формы в скрипте.

На скриншоте выше видно, что библиотека определила тип программы и в качестве ярлыка формы установила картинку по умолчанию. Этот скрипт можно скачать в конце статьи. В дальнейшем для других примеров он будет дополнен элементами, которые относятся к информационной группе. Этот скрипт, а также эксперт и индикаторы, которые рассматривались в этой статье, можно использовать как шаблоны при разработке своих программ.

 

Использование библиотеки в MetaTrader 4

Библиотеку для создания графических интерфейсов можно в этом же виде использовать и в торговой платформе MetaTrader 4. Скопируйте все файлы библиотеки и программы, которые были созданы в статье, из директорий терминала MetaTrader 5 в директории терминала MetaTrader 4. И библиотека практически уже готова к использованию.

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

#property strict

Сделайте это в файлах WndContainer.mqh, Element.mqh, а также в главных файлах программ. Теперь при компиляции кода в этих файлах не возникнет ошибок.

Если все сделали правильно и уже скомпилировали все файлы, протестируйте все программы из статьи в терминале MetaTrader 4. Все должно работать так же, как и в MetaTrader 5.

Рис. 4. Тест библиотеки в терминале Metatrader 4.

Рис. 4. Тест библиотеки в терминале MetaTrader 4

 

Заключение

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

Рис. 5. Структура библиотеки на текущей стадии разработки

Рис. 5. Структура библиотеки на текущей стадии разработки

Но все же кратко напомню, что означают элементы в данной схеме:

  • Синие стрелки означают взаимосвязь между классами, как: базовый -> производный 1 -> производный 2 -> производный N.
  • Желтыми стрелки обозначается подключение файла к другому файлу. Но при этом, если подключаемый файл содержит класс, то он не является базовым для того класса, к файлу которого он подключается. Он может использоваться как объект внутри класса либо классов одной цепи (базовый -> производные).
  • Светло-коричневые прямоугольники с жирным шрифтом изображают классы из стандартной библиотеки языка MQL, а все остальные относятся к разрабатываемой (пользовательской) библиотеке.
  • Белые прямоугольники с серой рамкой являются либо классами в отдельном файле, либо дополнениями, имеющими принадлежность к какой-то конкретной группе, такими как: макросы (define) или перечисления (enum).
  • Если у серого прямоугольника с толстой синей рамкой есть заголовок с именем файла, то это значит, что все классы, изображенные внутри белыми прямоугольниками с синей рамкой, содержатся в указанном в заголовке файле.
  • Если у серого прямоугольника с толстой синей рамкой нет заголовка, то все классы, которые находятся внутри него, расположены в разных файлах, но имеют один базовый класс. То есть, если к серому прямоугольнику с толстой синей рамкой подключен класс синей стрелкой, то это означает, что этот класс является базовым для всех классов, которые находятся внутри серого.
  • Светло-коричневый прямоугольник с текстом CChartObject… в схеме выше, присоединенный к серому прямоугольнику с толстой синей рамкой означает, что один из классов стандартной библиотеки, который является объектом-примитивом, может быть базовым для одного из тех классов, которые находятся внутри серого прямоугольника.

В первой части данной серии статей был подробно показан процесс подготовки основной структуры библиотеки для создания графических интерфейсов, а также создание главного элемента интерфейса, которым является «форма» либо «окно». К этой форме будут присоединяться другие элементы управления. Процесс их создания будет продемонстрирован в следующих статьях из этой серии. Продолжение серии будет полезно тем, что в нем будет многократно и подробно показано, как создать свой элемент управления и как добавить его к этой библиотеке так, чтобы он слился с ней в единое целое и не конфликтовал с другими элементами интерфейса.

Ниже вы можете загрузить на свой компьютер архивы с файлами библиотеки на текущей стадии разработки, изображения и файлы рассматриваемых в статье программ (эксперта, индикаторов и скрипта) для тестов в терминалах MetaTrader 4 и MetaTrader 5. Если у вас возникают вопросы по использованию материала, предоставленного в этих файлах, то вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей в представленном списке ниже или задать вопрос в комментариях к статье.

Список статей (глав) первой части:

Прикрепленные файлы |
Применение контейнеров для компоновки графического интерфейса: класс CGrid Применение контейнеров для компоновки графического интерфейса: класс CGrid
В данной статье описан альтернативный метод создания графического интерфейса на основе компоновки и контейнеров при помощи менеджера компоновки — класса CGrid. Класс CGrid представляет собой вспомогательный элемент управления, который действует как контейнер для других контейнеров и элементов управления с применением табличной компоновки.
Универсальный торговый эксперт: Пользовательские стратегии и вспомогательные торговые классы (Часть 3) Универсальный торговый эксперт: Пользовательские стратегии и вспомогательные торговые классы (Часть 3)
В этой статье мы продолжим описание алгоритмов торгового движка CStrategy. В третьей части серии статей подробно разобраны примеры написания конкретных торговых стратегий с использованием данного подхода. Также большое внимание уделено вспомогательным алгоритмам — системе логирования эксперта и доступу к биржевым данным с помощью обычного индексатора (Close[1], Open[0] и т.п.).
Универсальный торговый эксперт: Торговля в группе и управление портфелем стратегий (Часть 4) Универсальный торговый эксперт: Торговля в группе и управление портфелем стратегий (Часть 4)
В заключительной части серии статей о торговом движке CStrategy мы рассмотрим одновременную работу нескольких торговых алгоритмов, научимся загружать стратегии из XML-файлов, а также представим простую панель для выбора экспертов, находящихся внутри одного исполняемого модуля, и управления их торговыми режимами.
Универсальный торговый эксперт: Событийная модель и прототип торговой стратегии (Часть 2) Универсальный торговый эксперт: Событийная модель и прототип торговой стратегии (Часть 2)
Данная статья продолжает серию заметок, посвященных универсальной модели эксперта. В этой части описывается оригинальная событийная модель на основе централизованной обработки данных, а также рассматривается структура базового класса движка — CStrategy.