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

Anatoli Kazharski | 4 августа, 2016

Содержание

Введение

Более подробно о том, для чего предназначена эта библиотека, можно прочитать в первой статье серии: Графические интерфейсы 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();
  }

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

Соответствующие поля и методы для сохранения и получения значений этих параметров реализованы в теле класса 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() для изменения ширины элемента относительно ширины формы, к которой они присоединены, есть в следующих классах:

Для примера в листинге ниже приведён код этого метода для элемента типа 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). В нее будет внесено множество дополнительных возможностей, которые поднимут библиотеку на новый уровень. 

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