Скачать MetaTrader 5

Графические интерфейсы X: Обновления для библиотеки Easy And Fast (build 2)

4 августа 2016, 17:32
Anatoli Kazharski
23
2 320

Содержание

Введение

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

С момента публикации предыдущей статьи этой серии, библиотека Easy And Fast пополнилась новыми возможностями. Проведена частичная оптимизация схемы и кода библиотеки, что немного сократило потребление ресурсов процессора. Некоторые повторяющиеся методы во многих классах элементов были перенесены в базовый класс CElement. Также внесены мелкие визуальные улучшения и исправлены некоторые ошибки. Обо всём этом подробнее и рассказано в этой статье.

 

Список обновлений

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

Ниже показаны скриншоты с примерами графического интерфейса MQL-приложения на светлом и тёмном фоне:

 Рис. 1. Пример графического интерфейса с цветовой схемой по умолчанию на светлом фоне.

Рис. 1. Пример графического интерфейса с цветовой схемой по умолчанию на светлом фоне.


 Рис. 2. Пример графического интерфейса с цветовой схемой по умолчанию на тёмном фоне.

Рис. 2. Пример графического интерфейса с цветовой схемой по умолчанию на тёмном фоне.


 

2. Реализована первая версия класса CMouse для хранения параметров мыши и курсора мыши. Рассмотрим его подробнее. 

Класс CMouse содержится в файле Mouse.mqh, который расположен в той же директории, где и все файлы библиотеки: “<каталог терминала>\MQLX\Include\EasyAndFastGUI\Controls ”. Здесь понадобится экземпляр класса CChart из стандартной библиотеки. От этого класса будет использован метод CChart::SubwindowY() для расчёта Y-координаты относительно подокна, в котором курсор мыши находится на текущий момент. Привязка к графику осуществляется в конструкторе класса, а отсоединение — в деструкторе.

Отсюда же класс CChart будет доступен всем основным классам библиотеки, поэтому в схему внесены соответствующие изменения. 

//+------------------------------------------------------------------+
//|                                                        Mouse.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Класс для получения параметров мыши                              |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- Экземпляр класса для управления графиком
   CChart            m_chart;
   //---
public:
                     CMouse(void);
                    ~CMouse(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouse::CMouse(void)
  {
//--- Получим ID текущего графика
   m_chart.Attach();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CMouse::~CMouse(void)
  {
//--- Отсоединиться от графика
   m_chart.Detach();
  }

Ниже представлен список параметров мыши и курсора, которые понадобятся для работы практически во всех классах библиотеки.

  • Текущие координаты курсора.
  • Номер подокна, в котором сейчас находится курсор мыши.
  • Время относительно X-координаты курсора на графике.
  • Уровень относительно Y-координаты курсора на графике.
  • Состояние левой кнопки мыши (нажата/отжата).

Соответствующие поля и методы для сохранения и получения значений этих параметров реализованы в теле класса CMouse

class CMouse
  {
private:
   //--- Координаты
   int               m_x;
   int               m_y;
   //--- Номер окна, в котором находится курсор
   int               m_subwin;
   //--- Время, соответствующее координате X
   datetime          m_time;
   //--- Уровень (цена), соответствующий координате Y
   double            m_level;
   //--- Состояние левой кнопки мыши (зажата/отжата)
   bool              m_left_button_state;
   //---
public:
   //--- Возвращает координаты
   int               X(void)               const { return(m_x);                 }
   int               Y(void)               const { return(m_y);                 }
   //--- Возвращает (1) номер окна, в котором находится курсор, (2) время, соответствующее координате X, 
   //    (3) уровень (цена), соответствующий координате Y
   int               SubWindowNumber(void) const { return(m_subwin);            }
   datetime          Time(void)            const { return(m_time);              }
   double            Level(void)           const { return(m_level);             }
   //--- Возвращает состояние левой кнопки мыши (зажата/отжата)
   bool              LeftButtonState(void) const { return(m_left_button_state); }
  };

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

class CMouse
  {
   //--- Обработчик событий
   void              OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
  };
//+------------------------------------------------------------------+
//| Обработка событий перемещения курсора мыши                       |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события перемещения курсора
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Координаты и состояние левой кнопки мыши
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =(bool)int(sparam);
      //--- Получим местоположение курсора
      if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level))
         return;
      //--- Получим относительную координату Y
      if(m_subwin>0)
         m_y=m_y-m_chart.SubwindowY(m_subwin);
     }
  }

Экземпляр класса CMouse объявлен в базовом классе движка библиотеки (CWndContainer):

//+------------------------------------------------------------------+
//| Класс для хранения всех объектов интерфейса                      |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Экземпляр класса для получения параметров мыши
   CMouse            m_mouse;
  };

В базовом классе элементов CElement тоже объявлен экземпляр класса CMouse для хранения указателя на объект этого типа, который, в свою очередь, объявлен в классе CWndContainer. Также здесь есть методы, с помощью которых можно сохранить и получить указатель. 

//+------------------------------------------------------------------+
//| Базовый класс элемента управления                                |
//+------------------------------------------------------------------+
class CElement
  {
protected:
   //--- Экземпляр класса для получения параметров мыши
   CMouse           *m_mouse;
   //---
public:
   //--- (1) Сохраняет и (2) возвращает указатель мыши
   void              MousePointer(CMouse &object)                   { m_mouse=::GetPointer(object);       }
   CMouse           *MousePointer(void)                       const { return(::GetPointer(m_mouse));      }
  };

Указатель на объект CMouse автоматически передаётся всем элементам при создании графического интерфейса библиотеки. Для этого в методе CWndContainer::AddToObjectsArray(), где указатели всех графических объектов, из которых состоят элементы, попадают в общий массив, нужно передать объект CMouse, как показано в листинге кода ниже. 

//+------------------------------------------------------------------+
//| Добавляет указатели объектов элемента в общий массив             |
//+------------------------------------------------------------------+
template<typename T>
void CWndContainer::AddToObjectsArray(const int window_index,T &object)
  {
   int total=object.ObjectsElementTotal();
   for(int i=0; i<total; i++)
      AddToArray(window_index,object.Object(i));
//--- Сохраним указатель мыши в базовом классе элемента
   object.MousePointer(m_mouse);
  }

Теперь в каждом классе элемента есть доступ к параметрам мыши и курсора, хранящиеся в одном объекте на всю библиотеку. Для получения новых значений в обработчике событий ChartEvent() класса CWndEvents нужно вызвать обработчик объекта CMouse передав в него текущие параметры события. Так как этот вызов осуществляется до цикла обработки событий всех элементов, то для всех элементов далее будут доступны актуальные значения параметров мыши и курсора без необходимости повторного приведения типов, присвоения значений полям класса и расчёта относительной Y-координаты.

//+------------------------------------------------------------------+
//| Обработка событий программы                                      |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Если массив пуст, выйдем
//--- Инициализация полей параметров событий

//--- Получим параметры мыши
   m_mouse.OnEvent(id,lparam,dparam,sparam);

//--- Пользовательские события
//--- Проверка событий элементов интерфейса
//--- Событие перемещения мыши
//--- Событие изменения свойств графика
  }

В качестве примера приведём здесь часть кода обработчика элемента «Простая кнопка» (CSimpleButton). В листинге ниже показана сокращённая версия метода CSimpleButton::OnEvent(). Жёлтым маркером выделены строки с вызовом методов объекта CMouse для получения (1) номера подокна, в котором находится курсор мыши, (2) координат курсора и (3) состояния левой кнопки мыши.  

//+------------------------------------------------------------------+
//| Обработка событий                                                |
//+------------------------------------------------------------------+
void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события перемещения курсора
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Выйти, если элемент скрыт
      if(!CElement::IsVisible())
         return;
      //--- Выйти, если форма заблокирована
      if(m_wnd.IsLocked())
         return;
      //--- Выйти, если номера подокон не совпадают
      if(CElement::m_subwin!=CElement::m_mouse.SubWindowNumber())
         return;
      //--- Выйти, если кнопка заблокирована
      if(!m_button_state)
         return;
      //--- Определим фокус
      CElement::MouseFocus(CElement::m_mouse.X()>X() && CElement::m_mouse.X()<X2() && 
                           CElement::m_mouse.Y()>Y() && CElement::m_mouse.Y()<Y2());
      //--- Выйти, если кнопка мыши отжата
      if(!CElement::m_mouse.LeftButtonState())
         return;
      //--- 
      ...
      return;
     }
//...
  }

Соответствующие исправления были внесены во все классы элементов управления библиотеки.  


3. Ранее во многих классах элементов библиотеки повторялись методы для получения идентификатора и индекса элемента из имени графических объектов элемента. Теперь, чтобы исключить повторения, эти методы помещены в базовый класс CElement.  

//+------------------------------------------------------------------+
//| Базовый класс элемента управления                                |
//+------------------------------------------------------------------+
class CElement
  {
protected:
   //--- Получение идентификатора из имени кнопки
   int               IdFromObjectName(const string object_name);
   //--- Получение индекса из имени пункта меню
   int               IndexFromObjectName(const string object_name);
  };

 

4. Добавлены методы в классе CWindow для включения кнопки сворачивания/разворачивания окна и для позиционирования текстовой метки заголовка.  

//+------------------------------------------------------------------+
//| Класс создания формы для элементов управления                    |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Отступы текстовой метки заголовка
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Наличие кнопки для сворачивания/разворачивания окна
   bool              m_roll_button;
   //---
public:
   //--- Отступы текстовой метки
   void              LabelXGap(const int x_gap)                              { m_label_x_gap=x_gap;                }
   void              LabelYGap(const int y_gap)                              { m_label_y_gap=y_gap;                }
   //--- Использовать кнопку подсказок
   void              UseRollButton(void)                                     { m_roll_button=true;                 }
  };

 

5. В класс CElement добавлен метод CheckWindowPointer() для проверки наличия указателя окна (CWindow). В этот метод нужно с помощью системного метода CheckPointer() передать тип указателя для проверки его корректности. До этого один и тот же код повторялся во всех классах элементов управления в главном методе создания элемента перед его созданием. Метод CElement::CheckWindowPointer() упрощает этот процесс, сокращает объём кода и делает его более универсальным. 

//+------------------------------------------------------------------+
//| Базовый класс элемента управления                                |
//+------------------------------------------------------------------+
class CElement
  {
protected:
   //--- Проверка наличия указателя на форму
   bool              CheckWindowPointer(ENUM_POINTER_TYPE pointer_type);
  };
//+------------------------------------------------------------------+
//| Проверка наличия указателя на форму                              |
//+------------------------------------------------------------------+
bool CElement::CheckWindowPointer(ENUM_POINTER_TYPE pointer_type)
  {
//--- Если нет указателя на форму
   if(pointer_type==POINTER_INVALID)
     {
      //--- Сформировать сообщение
      string message=__FUNCTION__+" > Перед созданием элемента нужно передать указатель на форму: "+ClassName()+"::WindowPointer(CWindow &object)";
      //--- Вывести сообщение в журнал терминала
      ::Print(message);
      //--- Прервать построение графического интерфейса приложения
      return(false);
     }
//--- Отправить признак наличия указателя
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Создаёт элемент "Простая кнопка"                                 |
//+------------------------------------------------------------------+
bool CSimpleButton::CreateSimpleButton(const long chart_id,const int subwin,const string button_text,const int x,const int y)
  {
//--- Выйти, если нет указателя на форму
   if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd)))
      return(false);
//--- Инициализация переменных
   m_id          =m_wnd.LastId()+1;
   m_chart_id    =chart_id;
   m_subwin      =subwin;
   m_x           =x;
   m_y           =y;
   m_button_text =button_text;
//--- Отступы от крайней точки
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Создание кнопки
   if(!CreateButton())
      return(false);
//--- Скрыть элемент, если окно диалоговое или оно минимизировано
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

 

6. В базовый класс CElement добавлен режим для автоматического изменения ширины элементов, если изменилась ширина формы, к которой элемент присоединён. Для установки режима используется метод CElement::AutoXResizeMode(). Теперь правый край элемента в этом режиме будет привязан к правому краю формы. Также добавлен метод CElement::AutoXResizeRightOffset() для установки и получения отступа в пикселях от правого края формы. 

//+------------------------------------------------------------------+
//| Базовый класс элемента управления                                |
//+------------------------------------------------------------------+
class CElement
  {
protected:
   //--- Режим автоматического изменения ширины элемента
   bool              m_auto_xresize_mode;
   //--- Отступ от правого края формы в режиме авто-изменения ширины элемента
   int               m_auto_xresize_right_offset;
   //---
public:
   //--- (1) Режим авто-изменения ширины элемента, (2) получение/установка отступа от правого края формы
   bool              AutoXResizeMode(void)                    const { return(m_auto_xresize_mode);         }
   void              AutoXResizeMode(const bool flag)               { m_auto_xresize_mode=flag;            }
   int               AutoXResizeRightOffset(void)             const { return(m_auto_xresize_right_offset); }
   void              AutoXResizeRightOffset(const int offset)       { m_auto_xresize_right_offset=offset;  }
  };

По умолчанию режим привязки правого края элемента к правому краю формы отключен (false), а отступ равен 0. Инициализация в конструкторе класса CElement (см. листинг кода ниже): 

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_auto_xresize_mode(false),
                           m_auto_xresize_right_offset(0)
  {
  }

Ранее в классе CWindow уже был создан метод CWindow::ChangeWindowWidth() для изменения ширины формы. Теперь, если включен режим, ширина формы автоматически изменяется, только если графический интерфейс реализован в подокне индикатора. То есть, при изменении ширины окна графика в обработчике событий формы по событию CHARTEVENT_CHART_CHANGE корректируется ширина формы. 

//--- Событие изменения свойств графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Если кнопка отжата
      if(m_clamping_area_mouse==NOT_PRESSED)
        {
         //--- Получим размеры окна графика
         SetWindowProperties();
         //--- Корректировка координат
         UpdateWindowXY(m_x,m_y);
        }
      //--- Изменить размер, если включен режим
      if(CElement::AutoXResizeMode())
         ChangeWindowWidth(m_chart.WidthInPixels()-2);
      //---
      return;
     }

К списку идентификаторов событий библиотеки добавился ещё один — ON_WINDOW_CHANGE_SIZE. Этот идентификатор будет использоваться для генерации сообщения об изменении размеров формы. 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Идентификаторы событий
#define ON_WINDOW_CHANGE_SIZE     (3)  // Изменение размеров окна
...

Соответственно в метод CWindow::ChangeWindowWidth() добавлена генерация такого сообщения. Ниже показана сокращённая версия этого метода: 

//+------------------------------------------------------------------+
//| Изменяет ширину окна                                             |
//+------------------------------------------------------------------+
void CWindow::ChangeWindowWidth(const int width)
  {
//--- Если ширина не изменилась, выйдем
//--- Обновим ширину для фона и заголовка
//--- Обновим координаты и отступы для всех кнопок:
//    Кнопка закрытия
//--- Кнопка разворачивания
//--- Кнопка сворачивания
//--- Кнопка всплывающих подсказок (если включена)

//--- Сообщение о том, что размеры окна были изменены
   ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_SIZE,(long)CElement::Id(),0,””);
  }

Обработка этого сообщения осуществляется в движке библиотеки (класс CWndEvents). В базовом классе элементов управления CElement добавлен виртуальный метод ChangeWidthByRightWindowSide(), а во многих производных (там, где нужно изменение ширины элемента) — уникальная его реализация.  

//+------------------------------------------------------------------+
//| Базовый класс элемента управления                                |
//+------------------------------------------------------------------+
class CElement
  {
public:
   //--- Изменить ширину по правому краю окна
   virtual void      ChangeWidthByRightWindowSide(void) {}
  };

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

//+------------------------------------------------------------------+
//| Класс для обработки событий                                      |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
private:
   //--- Обработка изменения размеров окна
   bool              OnWindowChangeSize(void);
  };
//+------------------------------------------------------------------+
//| Событие ON_WINDOW_CHANGE_SIZE                                    |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowChangeSize(void)
  {
//--- Если сигнал "Изменить размер элементов"
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_SIZE)
      return(false);
//--- Индекс активного окна
   int awi=m_active_window_index;
//--- Если идентификаторы окна совпадают
   if(m_lparam!=m_windows[awi].Id())
      return(true);
//--- Изменить ширину всех элементов кроме формы
   int elements_total=CWndContainer::ElementsTotal(awi);
   for(int e=0; e<elements_total; e++)
     {
      if(m_wnd[awi].m_elements[e].ClassName()=="CWindow")
         continue;
      //---
      if(m_wnd[awi].m_elements[e].AutoXResizeMode())
         m_wnd[awi].m_elements[e].ChangeWidthByRightWindowSide();
     }
//---
   return(true);
  }

Вызов этого метода осуществляется в главном методе обработки событий библиотеки CWndEvents::ChartEventCustom(): 

//+------------------------------------------------------------------+
//| Событие CHARTEVENT_CUSTOM                                        |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Если сигнал свернуть форму
//--- Если сигнал развернуть форму

//--- Если сигнал изменить размер элементов
   if(OnWindowChangeSize())
      return;

//--- Если сигнал на скрытие контекстных меню от пункта инициатора
//--- Если сигнал на скрытие всех контекстных меню
//--- Если сигнал на открытие диалогового окна
//--- Если сигнал на закрытие диалогового окна
//--- Если сигнал на сброс цвета элементов на указанной форме
//--- Если сигнал на сброс приоритетов на нажатие левой кнопкой мыши
//--- Если сигнал на восстановление приоритетов на нажатие левой кнопкой мыши
  }

На текущий момент уникальные методы ChangeWidthByRightWindowSide() для изменения ширины элемента относительно ширины формы, к которой они присоединены, есть в следующих классах:

  • CTabs – простые вкладки.
  • CIconTabs – вкладки с картинками.
  • CStatusBar – статусная строка.
  • CMenuBar – главное меню.
  • CLabelsTable – таблица из текстовых меток.
  • CTable – таблица из полей ввода.
  • CCanvasTable – нарисованная таблица.
  • CProgressBar – индикатор выполнения.
  • CTreeView – древовидный список.
  • CFileNavigator – файловый навигатор.
  • CLineGraph – линейный график.

Для примера в листинге ниже приведён код этого метода для элемента типа CLabelsTable

//+------------------------------------------------------------------+
//| Изменить ширину по правому краю формы                            |
//+------------------------------------------------------------------+
void CLabelsTable::ChangeWidthByRightWindowSide(void)
  {
//--- Координаты
   int x=0;
//--- Размеры
   int x_size=0;
//--- Рассчитать и установить новый размер фону таблицы
   x_size=m_wnd.X2()-m_area.X()-m_auto_xresize_right_offset;
   CElement::XSize(x_size);
   m_area.XSize(x_size);
   m_area.X_Size(x_size);
//--- Рассчитать и установить новую координату для вертикальной полосы прокрутки
   x=m_area.X2()-m_scrollv.ScrollWidth();
   m_scrollv.XDistance(x);
//--- Рассчитать и изменить ширину горизонтальной полосы прокрутки
   x_size=CElement::XSize()-m_scrollh.ScrollWidth()+1;
   m_scrollh.ChangeXSize(x_size);
//--- Обновить положение объектов
   Moving(m_wnd.X(),m_wnd.Y());
  }


7. Добавлена возможность программного переключения вкладок уже после их создания. Для этого в классах CTabs и CIconTabs реализован метод SelectTab(). В листинге кода ниже в качестве примера приведён метод из класса CTabs:

//+------------------------------------------------------------------+
//| Класс для создания вкладок                                       |
//+------------------------------------------------------------------+
class CTabs : public CElement
  {
public:
   //--- Выделяет указанную вкладку
   void              SelectTab(const int index);
  };
//+------------------------------------------------------------------+
//| Выделяет вкладку                                                 |
//+------------------------------------------------------------------+
void CTabs::SelectTab(const int index)
  {
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Если выбрана эта вкладка
      if(index==i)
        {
         //--- Координаты
         int x=0;
         int y=0;
         //--- Размеры
         int x_size=0;
         int y_size=0;
         //--- Сохранить индекс выделенной вкладки
         SelectedTab(index);
         //--- Установить цвета
         m_tabs[i].Color(m_tab_text_color_selected);
         m_tabs[i].BackColor(m_tab_color_selected);
         //--- Расчёт относительно позиционирования вкладок
         CalculatingPatch(x,y,x_size,y_size);
         //--- Обновить значения
         m_patch.X_Size(x_size);
         m_patch.Y_Size(y_size);
         m_patch.XGap(x-m_wnd.X());
         m_patch.YGap(y-m_wnd.Y());
         //--- Обновить положение объектов
         Moving(m_wnd.X(),m_wnd.Y());
        }
      else
        {
         //--- Установить цвета для неактивных вкладок
         m_tabs[i].Color(m_tab_text_color);
         m_tabs[i].BackColor(m_tab_color);
        }
     }
//--- Показать элементы только выделенной вкладки
   ShowTabElements();
  }

 

8. В таблице из полей ввода (CTable) теперь можно снять выделение ряда повторным нажатием на нём.

9. При нажатии на ячейке таблицы (CTable), с отключенным режимом редактирования ячеек, в сгенерированном событии в string-параметре (sparam) отправляется строка формата "столбец_ряд_текст".

10. Исправлены перечисление и методы для определения области зажатия кнопки мыши. Теперь используется одно перечисление ENUM_MOUSE_STATE для отслеживания области зажатия левой кнопки мыши во всех элементах.

11. Переименованы некоторые файлы-картинки, используемые как ресурсы в библиотеке, по причине ограничения длины имени файлов в программах типа "Индикатор".

12. Также внесены мелкие визуальные улучшения и исправлены некоторые ошибки. Некоторые ошибки воспроизводились только в MetaTrader 4


Приложение для теста обновлений

Напишем приложение, чтобы Вы сразу могли протестировать все перечисленные в предыдущем разделе обновления и убедиться, что всё работает правильно. Сделаем две версии этого приложения — «Эксперт» и «Индикатор». Графический интерфейс индикатора расположим в подокне графика, чтобы можно было протестировать режим автоматического изменения ширины окна по ширине окна графика, а также элементов, которые будут подстраивать свою ширину по окну, к которому они присоединены. 

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

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\Controls\WndEvents.mqh>
//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {

//...

  };
//+------------------------------------------------------------------+
//| Создание элементов управления                                    |
//+------------------------------------------------------------------+
#include "MainWindow.mqh"

Чтобы связка между файлами работала полноценно, файл Program.mqh нужно подключить к файлу MainWindow.mqh

//+------------------------------------------------------------------+
//|                                                   MainWindow.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Program.mqh"

В файле Program.mqh имеет смысл оставлять только главный файл создания графического интерфейса программы, в котором вызываются все методы создания элементов управления. 

Итак, приступим. Всего в графических интерфейсах тестовых приложений будет восемь вкладок. На скриншотах ниже показано, как разместились в них элементы управления. На первой вкладке представлены все типы кнопок (включая группы кнопок) и список с вертикальной полосой прокрутки. Кнопка «Simple Button 3» двухрежимная, и если привести её во включенное состояние, это сделает видимым элемент «Индикатор выполнения», в котором для демонстрации будет имитироваться некий процесс.  

 Рис. 3. Группа элементов графического интерфейса первой вкладки.

Рис. 3. Группа элементов графического интерфейса первой вкладки.


На скриншоте ниже показано, что, когда кнопка «Simple Button 3» находится во включенном состоянии, в области статусной строки отображается индикатор выполнения:

 Рис. 4. Демонстрация элемента «Индикатор выполнения».

Рис. 4. Демонстрация элемента «Индикатор выполнения».


На второй, третьей и четвёртой вкладках представлены таблицы разных типов:

 Рис. 5. Группа элементов графического интерфейса второй вкладки.

Рис. 5. Группа элементов графического интерфейса второй вкладки.

 

 Рис. 6. Группа элементов графического интерфейса третьей вкладки.

Рис. 6. Группа элементов графического интерфейса третьей вкладки.

 

Рис. 7. Группа элементов графического интерфейса четвёртой вкладки. 

Рис. 7. Группа элементов графического интерфейса четвёртой вкладки.


На пятой вкладке расположен элемент «Линейный график». В классе CProgram объявлены и имплементированы методы для работы с этим элементом. В таймере CProgram::OnTimerEvent() каждые 300 миллисекунд генерируются случайные серии:

 Рис. 8. Группа элементов графического интерфейса пятой вкладки.

Рис. 8. Группа элементов графического интерфейса пятой вкладки.


На шестой и седьмой вкладках расположены древовидный список и файловый навигатор, а также различные типы комбо-боксов, полей ввода и чек-боксов:

 Рис. 9. Группа элементов графического интерфейса шестой вкладки.

Рис. 9. Группа элементов графического интерфейса шестой вкладки.

 

Рис. 10. Группа элементов графического интерфейса седьмой вкладки. 

Рис. 10. Группа элементов графического интерфейса седьмой вкладки.


За седьмой вкладкой закреплены следующие элементы: «Календарь», «Выпадающий календарь», «Слайдер», «Двухсторонний слайдер», «Разделительная полоса» и «Кнопка для вызова цветовой палитры»:

 Рис. 11. Группа элементов графического интерфейса восьмой вкладки.

Рис. 11. Группа элементов графического интерфейса восьмой вкладки.


Если нужно, чтобы окно с цветовой палитрой было создано в главном окне графика, то в методе создания нужно указать нулевой индекс и для формы, и для элементов, которые будут присоединены к этой форме. В данном случае к форме присоединен только один элемент — «Цветовая палитра». На скриншоте ниже показан итоговый результат:

 Рис. 12. Демонстрация элемента «Цветовая палитра» в главном окне графика.

Рис. 12. Демонстрация элемента «Цветовая палитра» в главном окне графика.


Как мы уже сказали выше, для теста было создано еще одно приложение — Эксперт, с таким же графическим интерфейсом:

 Рис. 13. Тест приложения типа «Эксперт».

Рис. 13. Тест приложения типа «Эксперт».


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

 

 


Заключение

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

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

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


Многие исправления и обновления реализованы по заявкам от заинтересованных пользователей. Если Вам чего-то не хватает или при создании своего приложения Вы обнаружили баг, относящийся к этой библиотеке, то сообщите мне об этом, пожалуйста, в комментариях к статье или в личных сообщениях. Я обязательно отвечу. Уже сейчас идёт работа над следующей версией библиотеки (build 3). В нее будет внесено множество дополнительных возможностей, которые поднимут библиотеку на новый уровень. 

Кроме тестовых приложений, которые были рассмотрены в этой статье, в архивах ниже можно скачать обновлённые версии тестовых экспертов и индикаторов из всех предыдущих статей серии.

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (23)
Anatoli Kazharski
Anatoli Kazharski | 9 авг 2016 в 10:45
Alexey Oreshkin:
Что то не так делаю походу или проблема в другом, но с полосами прокрутки какие то проблемы у меня.
При старте они почти не работают, но если радиокнопками их обновить то всё начинает работать норм. При переходе на новую вкладку та же беда, пока не нажму на радиокнопку.
Отправил архив в ЛС.

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

 

//---

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

Сейчас такое событие при переключении вкладок не генерируется. Будет доступно в следующем обновлении библиотеки.

Alexey Oreshkin
Alexey Oreshkin | 9 авг 2016 в 11:18
А когда ждать обновление ?
Anatoli Kazharski
Anatoli Kazharski | 9 авг 2016 в 11:26
Alexey Oreshkin:
А когда ждать обновление ?

Если нужно срочно, то можете сами добавить следующие изменения:

1. В файл Defines.mqh добавьте новый идентификатор:

#define ON_CLICK_TAB               (26) // Переключение вкладки

//--- 

2. В классы CTabs и CIconTabs (файлы Tabs.mqh и IconTabs.mqh) в метод ShowTabElements() нужно добавить строчку, как показано в листинге ниже:

//+------------------------------------------------------------------+
//| Показывает элементы только выделенной вкладки                    |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- Выйти, если вкладки скрыты
   if(!CElement::IsVisible())
      return;
//--- Проверка индекса выделенной вкладки
   CheckTabIndex();
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Получим количество элементов присоединённых к вкладке
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- Если выделена эта вкладка
      if(i==m_selected_tab)
        {
         //--- Показать элементы вкладки
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Show();
        }
      //--- Скрыть элементы неактивных вкладок
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
//--- Отправить сообщение об этом
   ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElement::Id(),m_selected_tab,"");
  }

//---

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

Пример: 

//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Событие нажатия на вкладке
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_TAB)
     {
      //--- Если нажали на радио-кнопку
      if(m_tabs.SelectedTab()==0)
        {
         switch(m_radio_buttons1.SelectedButtonIndex()){
            case  0:
               m_canvas_table1.Show();
               m_canvas_table11.Hide();
               m_canvas_table111.Hide();
               ModifyCanvasTable1(0);
            break;
            case  1:
               m_canvas_table1.Hide();
               m_canvas_table11.Show();
               m_canvas_table111.Hide();
               ModifyCanvasTable1(1);                
            break;
            case  2:
               m_canvas_table1.Hide();
               m_canvas_table11.Hide();
               m_canvas_table111.Show();
               ModifyCanvasTable1(2);               
            break;                        
         }       
         return;
        }
      return;
     }
  }
Alexey Oreshkin
Alexey Oreshkin | 9 авг 2016 в 17:00
Благодарю, работает.
Нашёл ещё одну ошибку - запускаем индюк, отодвигаем панель и начинаем жать радиокнопки. Когда отрисовывается таблица, вначале она начинает рисоваться с левого края, а потом как бы подтягивается к нужным координатам.
Anatoli Kazharski
Anatoli Kazharski | 9 авг 2016 в 17:43
Alexey Oreshkin:
Благодарю, работает.
Нашёл ещё одну ошибку - запускаем индюк, отодвигаем панель и начинаем жать радиокнопки. Когда отрисовывается таблица, вначале она начинает рисоваться с левого края, а потом как бы подтягивается к нужным координатам.

Да, есть такое. На примере таблиц этого типа, добавьте в метод CCanvasTable::Show() в файле CanvasTable.mqh строку, как показано ниже:

//+------------------------------------------------------------------+
//| Показывает элемент                                               |
//+------------------------------------------------------------------+
void CCanvasTable::Show(void)
  {
//--- Выйти, если элемент уже видим
   if(CElement::IsVisible())
      return;
//--- Сделать видимыми все объекты
   m_area.Timeframes(OBJ_ALL_PERIODS);
   m_canvas.Timeframes(OBJ_ALL_PERIODS);
   m_scrollv.Show();
   m_scrollh.Show();
//--- Состояние видимости
   CElement::IsVisible(true);
//--- Перемещение элемента
   Moving(m_wnd.X(),m_wnd.Y());
  }

 //---

Если такая проблема будет с другими элементами добавляйте в их метод Show() такую же строчку. В следующем обновлении во всех классах элементов будет это исправление.

Тестирование торговых стратегий на реальных тиках Тестирование торговых стратегий на реальных тиках

В данной статье мы покажем результаты тестирования простой торговой стратегии в 3-х режимах: "OHLC на M1", "Все тики" и "Каждый тик на основе реальных тиков" с использованием записанных тиков из истории.

Графические интерфейсы IX: Элементы "Индикатор выполнения" и "Линейный график" (Глава 2) Графические интерфейсы IX: Элементы "Индикатор выполнения" и "Линейный график" (Глава 2)

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

LifeHack для трейдера: один бэк-тест хорошо, а четыре – лучше LifeHack для трейдера: один бэк-тест хорошо, а четыре – лучше

Перед каждым трейдером при первом одиночном тестировании встает один и тот же вопрос — "Какой же из четырех режимов использовать?" Каждый из предлагаемых режимов имеет свои преимущества и особенности, поэтому сделаем проще - запустим сразу все режимы одной кнопкой! В статье показано, как с помощью Win API и небольшой магии увидеть одновременно все четыре графика тестирования.

Кроссплатформенный торговый советник: Введение Кроссплатформенный торговый советник: Введение

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