English 中文 Español Deutsch 日本語 Português
Графические интерфейсы II: Элемент "Пункт меню" (Глава 1)

Графические интерфейсы II: Элемент "Пункт меню" (Глава 1)

MetaTrader 5Примеры | 11 февраля 2016, 16:11
5 633 6
Anatoli Kazharski
Anatoli Kazharski

Содержание

 

Введение

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

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

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

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

 

Главное меню программы

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

Рис. 1. Главное меню в терминале MetaTrader 5

Рис. 1. Главное меню в терминале MetaTrader 5

Этот выпадающий список называется «Контекстное меню» и может содержать в себе несколько типов пунктов. Рассмотрим каждый из них подробнее:

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

Главное меню есть и в редакторе кода MetaEditor:

Рис. 2. Главное меню в редакторе кода MetaEditor.

Рис. 2. Главное меню в редакторе кода MetaEditor

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

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

  1. Пункт меню. Для создания этого элемента будет разработан класс CMenuItem.
  2. Для разделительной линии создадим класс CSeparateLine.
  3. Контекстное меню. Класс CContextMenu. Этот элемент интерфейса будет собираться из объектов класса CMenuItem.
  4. Главное меню. Класс CMenuBar. Так же, как и в контекстном меню, составными частями будут являться пункты меню (CMenuItem).

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

 

Разработка класса для создания пункта меню

В папке Controls, где сейчас располагаются все остальные файлы библиотеки, создайте файл MenuItem.mqh с производным классом CMenuItem, в котором можно сразу же объявить стандартные для всех элементов управления виртуальные методы. Базовым классом для него выступает CElement, который подробно рассматривался в предыдущей статье. Оставим пока эти методы без реализации, так как далее в статье хотелось бы отметить одну интересную особенность компилятора.

//+------------------------------------------------------------------+
//|                                                     MenuItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//+------------------------------------------------------------------+
//| Класс создания пункта меню                                       |
//+------------------------------------------------------------------+
class CMenuItem : public CElement
  {
   //---
public:
                     CMenuItem(void);
                    ~CMenuItem(void);  
   //---
public:
   //--- Обработчик событий графика
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Таймер
   virtual void      OnEventTimer(void);
   //--- Перемещение элемента
   virtual void      Moving(const int x,const int y);
   //--- Показ, скрытие, сброс, удаление
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- Установка, сброс приоритетов на нажатие левой кнопки мыши
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);  
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMenuItem::CMenuItem(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CMenuItem::~CMenuItem(void)
  {
  }  
//+------------------------------------------------------------------+

Из каких графических объектов будет собираться элемент «Пункт меню»? В главном меню обычно это просто надписи, у которых, если навести курсор мыши, изменяется цвет фона и/или цвет текста. У пунктов в контекстных меню можно увидеть ярлык, а если пункт содержит в себе свое контекстное меню, то в правой части области пункта отображается стрелка вправо, что для пользователя служит своего рода подсказкой о наличии вложенного меню. Из всего этого следует, что пункт меню может иметь несколько разных типов в зависимости от того, к чему он имеет принадлежность и какую задачу он должен выполнять. Перечислим все его составные части в максимальном комплекте:

  1. Фон.
  2. Ярлык.
  3. Надпись.
  4. Признак наличия контекстного меню.

Рис. 3. Составные части элемента управления «Пункт меню».

Рис. 3. Составные части элемента управления «Пункт меню».

Добавим в класс CMenuItem экземпляры классов графических объектов (которые доступны из файла Element.mqh) и объявления методов для создания этих графических объектов. При установке пункта меню (на график) в метод нужно передать номер индекса пункта меню. Этот номер затем будет использоваться в формировании имени графических объектов, из которых собирается пункт меню, а также понадобится для идентификации в списке пунктов в момент нажатия на одном из них.

class CMenuItem : public CElement
  {
private:
   //--- Объекты для создания пункта меню
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CBmpLabel         m_arrow;
   //---
public:
   //--- Методы для создания пункта меню
   bool              CreateMenuItem(const long chart_id,const int window,const int index_number,const string label_text,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateArrow(void);
   //---
  };

Так как пункт меню может иметь несколько разных типов, нужна возможность (перед его созданием) указать, к какому типу он относится. Нам понадобится перечисление типов пункта меню (ENUM_TYPE_MENU_ITEM).

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

  • MI_SIMPLE — простой пункт.
  • MI_HAS_CONTEXT_MENU — пункт, который содержит в себе контекстное меню.
  • MI_CHECKBOX — пункт-чекбокс.
  • MI_RADIOBUTTON — пункт, имеющий принадлежность к группе радио-пунктов.
//+------------------------------------------------------------------+
//| Перечисление типов пункта меню                                   |
//+------------------------------------------------------------------+
enum ENUM_TYPE_MENU_ITEM
  {
   MI_SIMPLE           =0,
   MI_HAS_CONTEXT_MENU =1,
   MI_CHECKBOX         =2,
   MI_RADIOBUTTON      =3
  };

В классе CMenuItem нужно добавить соответствующее поле и методы для установки и получения типа пункта меню:

class CMenuItem : public CElement
  {
private:
   //--- Свойства пункта меню
   ENUM_TYPE_MENU_ITEM m_type_menu_item;
   //---
public:
   //--- Установка и получение типа
   void              TypeMenuItem(const ENUM_TYPE_MENU_ITEM type)   { m_type_menu_item=type;                 }
   ENUM_TYPE_MENU_ITEM TypeMenuItem(void)                     const { return(m_type_menu_item);              }
   //---
  };

По умолчанию будет установлен тип MI_SIMPLE, то есть простой пункт меню:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMenuItem::CMenuItem(void) : m_type_menu_item(MI_SIMPLE)
  {
  }

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

  1. Изменение цвета фона.
  2. Цвет фона, когда пункт меню недоступен (заблокирован в силу невозможности использовать функцию пункта в текущий момент).
  3. Изменение цвета рамки фона.
  4. Приоритет на нажатие левой кнопкой мыши для всех объектов должен быть общим и его значение равно нулю, кроме фона, так как именно на нем будет отслеживаться нажатие.
  5. Переопределение картинок для ярлыка и признака наличия контекстного меню.
  6. Методы для управления свойствами текстовой метки, такими как:
    • отступы от нулевой координаты фона пункта меню;
    • цвет текста;
    • цвет текста при наведении курсора;
    • цвет текста при заблокированном пункте.
  7. Переменные для отслеживания состояния пункта меню:
    • общее состояние (доступен/заблокирован);
    • состояние чекбокса;
    • состояние радио-пункта;
    • состояние контекстного меню, если оно закреплено за этим пунктом.
  8. Идентификатор для группы радио-пунктов. Он необходим, так как в одном контекстном меню может быть несколько групп радио-пунктов. Идентификатор позволит понять, к какой именно группе принадлежит тот или иной радио-пункт.
  9. Номер индекса пункта меню.

Добавим все перечисленное выше в класс CMenuItem:

class CMenuItem : public CElement
  {
private:
   //--- Свойства фона
   int               m_area_zorder;
   color             m_area_border_color;
   color             m_area_color;
   color             m_area_color_off;
   color             m_area_color_hover;
   //--- Свойства ярлыка
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Свойства текстовой метки
   string            m_label_text;
   int               m_label_x_gap;
   int               m_label_y_gap;
   color             m_label_color;
   color             m_label_color_off;
   color             m_label_color_hover;
   //--- Свойства признака контекстного меню
   string            m_right_arrow_file_on;
   string            m_right_arrow_file_off;
   //--- Общий приоритет на нажатие
   int               m_zorder;
   //--- Доступен/заблокирован
   bool              m_item_state;
   //--- Состояние чекбокса
   bool              m_checkbox_state;
   //--- Состояние радио-кнопки и ее идентификатор
   bool              m_radiobutton_state;
   int               m_radiobutton_id;
   //--- Состояние контекстного меню
   bool              m_context_menu_state;
   //---
public:
   //--- Методы фона
   void              AreaBackColor(const color clr)                 { m_area_color=clr;                      }
   void              AreaBackColorOff(const color clr)              { m_area_color_off=clr;                  }
   void              AreaBorderColor(const color clr)               { m_area_border_color=clr;               }
   //--- Методы ярлыка
   void              IconFileOn(const string file_path)             { m_icon_file_on=file_path;              }
   void              IconFileOff(const string file_path)            { m_icon_file_off=file_path;             }
   //--- Методы текстовой метки
   string            LabelText(void)                          const { return(m_label.Description());         }
   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              LabelColor(const color clr)                    { m_label_color=clr;                     }
   void              LabelColorOff(const color clr)                 { m_label_color_off=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;               }
   //--- Методы признака наличия контекстного меню
   void              RightArrowFileOn(const string file_path)       { m_right_arrow_file_on=file_path;       }
   void              RightArrowFileOff(const string file_path)      { m_right_arrow_file_off=file_path;      }
   //--- Общее (1) состояние пункта и (2) пункта-чекбокса
   void              ItemState(const bool state);
   bool              ItemState(void)                          const { return(m_item_state);                  }
   void              CheckBoxState(const bool flag)                 { m_checkbox_state=flag;                 }
   bool              CheckBoxState(void)                      const { return(m_checkbox_state);              }
   //--- Идентификатор радио-пункта
   void              RadioButtonID(const int id)                    { m_radiobutton_id=id;                   }
   int               RadioButtonID(void)                      const { return(m_radiobutton_id);              }
   //--- Состояние радио-пункта
   void              RadioButtonState(const bool flag)              { m_radiobutton_state=flag;              }
   bool              RadioButtonState(void)                   const { return(m_radiobutton_state);           }
   //--- Состояние контекстного меню, закрепленного за этим пунктом
   bool              ContextMenuState(void)                   const { return(m_context_menu_state);          }
   void              ContextMenuState(const bool flag)              { m_context_menu_state=flag;             }
   //---
  };

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

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

//+------------------------------------------------------------------+
//|                                                     MenuItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Класс создания пункта меню                                       |
//+------------------------------------------------------------------+
class CMenuItem : public CElement
  {
private:
   //--- Указатель на форму, к которой элемент присоединен
   CWindow          *m_wnd;
   //---
public:
   //--- Сохраняет указатель переданной формы
   void              WindowPointer(CWindow &object)                 { m_wnd=::GetPointer(object);            }
   //---
  };

Теперь рассмотрим метод создания пункта меню CMenuItem::CreateMenuItem(). В самом начале метода с помощью функции CheckPointer() будет производиться проверка на корректность указателя на форму, к которой должен быть присоединен элемент. Если указатель невалиден, то программа выведет в журнал сообщение с подсказкой и выйдет из метода, вернув false. Если указатель есть, то далее инициализируются переменные элемента.

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

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

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

//+------------------------------------------------------------------+
//| Создает элемент "Пункт меню"                                     |
//+------------------------------------------------------------------+
bool CMenuItem::CreateMenuItem(const long chart_id,const int subwin,const int index_number,const string label_text,const int x,const int y)
  {
//--- Выйти, если нет указателя на форму
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Перед созданием пункта меню классу нужно передать "
            "указатель на окно: CMenuItem::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Инициализация переменных
   m_id           =m_wnd.LastId()+1;
   m_index        =index_number;
   m_chart_id     =chart_id;
   m_subwin       =subwin;
   m_label_text   =label_text;
   m_x            =x;
   m_y            =y;
//--- Отступы от крайней точки
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Создание пункта меню
   if(!CreateArea())
      return(false);
   if(!CreateIcon())
      return(false);
   if(!CreateLabel())
      return(false);
   if(!CreateArrow())
      return(false);
//--- Если окно свернуто, то спрятать элемент после создания
   if(m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

В методе скрытия элемента CMenuItem::Hide() нужно скрыть все объекты, обнулить некоторые переменные и сбросить цвета:

//+------------------------------------------------------------------+
//| Скрывает пункт меню                                              |
//+------------------------------------------------------------------+
void CMenuItem::Hide(void)
  {
//--- Выйти, если элемент скрыт
   if(!CElement::m_is_visible)
      return;
//--- Скрыть все объекты
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_NO_PERIODS);
//--- Обнуление переменных
   m_context_menu_state=false;
   CElement::m_is_visible=false;
   CElement::MouseFocus(false);
//--- Сброс цвета
   m_area.BackColor(m_area_color);
   m_arrow.State(false);
  }

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

При создании пункта меню в метод CMenuItem::CreateMenuItem() передается номер индекса для этого пункта и затем он сохраняется в поле класса m_index_number, которое затем будет использоваться в формировании имени каждого графического объекта элемента. Перечислим составные части для имени объектов пункта меню:

  • Имя программы.
  • Принадлежность к элементу.
  • Принадлежность к части элемента.
  • Номер индекса элемента.
  • Идентификатор элемента.

Методы создания фона, текстовой метки и признака наличия контекстного меню не имеют каких-то кардинальных отличий от тех, которые были представлены в классе CWindow, поэтому с их кодом можете самостоятельно ознакомиться в приложенном к статье файле MenuItem.mqh. Здесь можно только для примера показать метод для создания ярлыка CMenuItem::CreateIcon(), в котором производится проверка типа пункта меню и в зависимости от того, какой тип установлен, определяется, какая будет использоваться картинка.

У простых пунктов меню (MI_SIMPLE) и пунктов, которые содержат в себе контекстное меню (MI_HAS_CONTEXT_MENU) может вообще не быть ярлыков. Если они не определены пользователем библиотеки, то программа сразу выходит из метода, вернув true, так как это не является ошибкой (просто не нужен ярлык). Если для пунктов с типом «чекбокс» или «радио-пункт» не определены свои картинки, то определятся те, которые предусмотрены по умолчанию. К статье приложены картинки, которые используются в коде библиотеки.

Код метода CMenuItem::CreateIcon():

//+------------------------------------------------------------------+
//| Создает ярлык пункта                                             |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\CheckBoxOn_min_gray.bmp"
#resource "\\Images\\Controls\\CheckBoxOn_min_white.bmp"
//---
bool CMenuItem::CreateIcon(void)
  {
//--- Если это простой пункт или пункт, содержащий в себе контекстное меню
   if(m_type_menu_item==MI_SIMPLE || m_type_menu_item==MI_HAS_CONTEXT_MENU)
     {
      //--- Если ярлык не нужен (картинка не определена), выйдем
      if(m_icon_file_on=="" || m_icon_file_off=="")
         return(true);
     }
//--- Если это чекбокс
   else if(m_type_menu_item==MI_CHECKBOX)
     {
      //--- Если картинка не определена, установка по умолчанию
      if(m_icon_file_on=="")
         m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp";
      if(m_icon_file_off=="")
         m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp";
     }
//--- Если это радио-пункт     
   else if(m_type_menu_item==MI_RADIOBUTTON)
     {
      //--- Если картинка не определена, установка по умолчанию
      if(m_icon_file_on=="")
         m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp";
      if(m_icon_file_off=="")
         m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp";
     }
//--- Формирование имени объекта
   string name=CElement::ProgramName()+"_menuitem_icon_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Координаты объекта
   int x =m_x+7;
   int y =m_y+4;
//--- Установим ярлык
   if(!m_icon.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Установим свойства
   m_icon.BmpFileOn("::"+m_icon_file_on);
   m_icon.BmpFileOff("::"+m_icon_file_off);
   m_icon.State(m_item_state);
   m_icon.Corner(m_corner);
   m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_icon.Selectable(false);
   m_icon.Z_Order(m_zorder);
   m_icon.Tooltip("\n");
//--- Отступы от крайней точки
   m_icon.XGap(x-m_wnd.X());
   m_icon.YGap(y-m_wnd.Y());
//--- Сохраним указатель объекта
   CElement::AddToArray(m_icon);
   return(true);
  }

Метод для скрытия элемента CMenuItem::Hide() уже был показан выше, а теперь реализуем метод CMenuItem::Show(), который будет делать элемент видимым. Для пунктов меню, которые являются чекбоксами и радио-пунктами, в этом методе должно учитываться их текущее состояние (включен/выключен):

//+------------------------------------------------------------------+
//| Делает видимым пункт меню                                        |
//+------------------------------------------------------------------+
void CMenuItem::Show(void)
  {
//--- Выйти, если элемент уже видим
   if(CElement::m_is_visible)
      return;
//--- Сделать видимыми все объекты
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_ALL_PERIODS);
//--- Если это чекбокс, то с учетом его состояния
   if(m_type_menu_item==MI_CHECKBOX)
      m_icon.Timeframes((m_checkbox_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
//--- Если это радио-пункт, то с учетом его состояния
   else if(m_type_menu_item==MI_RADIOBUTTON)
      m_icon.Timeframes((m_radiobutton_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
//--- Обнуление переменных
   CElement::m_is_visible=true;
   CElement::MouseFocus(false);
  }

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

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

Эксперта, которого тестировали в предыдущей статье, можно использовать для тестов и сейчас. В классе CProgram создадим экземпляр класса CMenuItem и метод для создания пункта меню CProgram::CreateMenuItem1(). Отступы от крайней точки формы для каждого создаваемого элемента будем держать рядом в макросах. Когда элементов много, так будет удобнее и быстрее корректировать их положение относительно друг друга, чем переходить от одной реализации метода к другой.

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Окно
   CWindow           m_window;
   //--- Пункт меню
   CMenuItem         m_menu_item1;
public:
                     CProgram();
                    ~CProgram();
   //--- Инициализация/деинициализация
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Таймер
   void              OnTimerEvent(void);
   //---
protected:
   //--- Обработчик событий графика
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //---
public:
   //--- Создает торговую панель
   bool              CreateTradePanel(void);
   //---
private:
//--- Создание формы
   bool              CreateWindow(const string text);
//--- Создание пункта меню
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (25)
   bool              CreateMenuItem1(const string item_text);
  };

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

Ниже показан код метода CProgram::CreateMenuItem1(). Подключенные ресурсы (картинки) доступны для скачивания в конце статьи. В самом начале метода указатель на форму, к которой должен быть прикреплен элемент, сохраняется в классе элемента. Затем рассчитываются координаты, устанавливаются свойства, с которыми должен быть создан элемент, и затем на график устанавливается элемент «пункт меню».

//+------------------------------------------------------------------+
//| Создает пункт меню                                               |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\bar_chart.bmp"
#resource "\\Images\\Controls\\bar_chart_colorless.bmp"
//---
bool CProgram::CreateMenuItem1(string item_text)
  {
//--- Сохраним указатель на окно
   m_menu_item1.WindowPointer(m_window);
//--- Координаты  
   int x=m_window.X()+MENU_ITEM1_GAP_X;
   int y=m_window.Y()+MENU_ITEM1_GAP_Y;
//--- Установим свойства перед созданием
   m_menu_item1.XSize(193);
   m_menu_item1.YSize(24);
   m_menu_item1.TypeMenuItem(MI_HAS_CONTEXT_MENU);
   m_menu_item1.IconFileOn("Images\\Controls\\bar_chart.bmp");
   m_menu_item1.IconFileOff("Images\\Controls\\bar_chart_colorless.bmp");
   m_menu_item1.LabelColor(clrWhite);
   m_menu_item1.LabelColorHover(clrWhite);
//--- Создание пункта меню
   if(!m_menu_item1.CreateMenuItem(m_chart_id,m_subwin,0,item_text,x,y))
      return(false);
//---
   return(true);
  }

Теперь в методе CProgram::CreateTradePanel() можно добавить вызов метода для создания пункта меню:

//+------------------------------------------------------------------+
//| Создает торговую панель                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Создание формы для элементов управления
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
//--- Создание элементов управления:
//    Пункт меню
   if(!CreateMenuItem1("Menu item"))
      return(false);
//--- Перерисовка графика
   m_chart.Redraw();
   return(true);
  }

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

Рис. 4. Сообщения об отсутствии реализации методов

Рис. 4. Сообщения об отсутствии реализации методов

При компиляции файла с классом CMenuItem ошибок не возникло, потому что нигде в классе на текущий стадии его разработки не производится вызов методов без реализации. В базе элементов сейчас есть только указатель на форму. Логически выглядит так, что вызов методов, которые вы видите на скриншоте выше, производится только у формы (CWindow) в классе CWndEvents в циклах методов CheckElementsEvents(), MovingWindow(), CheckElementsEventsTimer() и Destroy(). Но указатели элементов хранятся в массиве типа CElement, то есть вызов производится через базовый класс CElement, где эти методы объявлены как виртуальные.

Так как к базе (к файлу WndContainer.mqh) подключены сейчас два файла с элементами (Window.mqh и MenuItem.mqh), классы которых являются производными от класса CElement, компилятор определяет все производные от него классы и требует реализации вызываемых методов, даже если нет прямого вызова у одного из них. Поэтому далее реализуем необходимые методы в классе CMenuItem, а в классе CWndContainer создадим метод, который позволит добавлять в базу указатели элементов управления.

На этом этапе также можно уже добавить метод CMenuItem::ChangeObjectsColor() для изменения цвета объектов элемента при наведении курсора мыши. В этом методе для элемента «пункт меню» учитывается тип пункта и его состояние (доступен/заблокирован). А в самом начале должна быть проверка, содержит ли этот пункт контекстное меню, и если содержит, то включено ли оно сейчас. Дело в том, что когда контекстное меню пункта включено, то управление передается этому включенному контекстному меню и нужно зафиксировать цвет пункта, из которого оно было вызвано.

Также нам понадобится в дальнейшем метод сброса цвета фокуса на пункте меню, поэтому создадим сразу метод CMenuItem::ResetColors().

class CMenuItem : public CElement
  {
public:
   //--- Изменение цвета объектов элемента
   void              ChangeObjectsColor(void);
   //--- Сбросить цвет
   void              ResetColors(void);
   //---
  };
//+------------------------------------------------------------------+
//| Изменение цвета объекта при наведении курсора                    |
//+------------------------------------------------------------------+
void CMenuItem::ChangeObjectsColor(void)
  {
//--- Выйти, если у этого пункта есть контекстное меню и оно включено
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU && m_context_menu_state)
      return;
//--- Блок кода для простых пунктов и пунктов, содержащих в себе контекстное меню
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU || m_type_menu_item==MI_SIMPLE)
     {
      //--- Если есть фокус
      if(CElement::MouseFocus())
        {
         m_icon.State(m_item_state);
         m_area.BackColor((m_item_state)? m_area_color_hover : m_area_color_off);
         m_label.Color((m_item_state)? m_label_color_hover : m_label_color_off);
         if(m_item_state)
            m_arrow.State(true);
        }
      //--- Если нет фокуса
      else
        {
         m_arrow.State(false);
         m_area.BackColor(m_area_color);
         m_label.Color((m_item_state)? m_label_color : m_label_color_off);
        }
     }
//--- Блок кода для пунктов-чекбоксов и радио-пунктов
   else if(m_type_menu_item==MI_CHECKBOX || m_type_menu_item==MI_RADIOBUTTON)
     {
      m_icon.State(CElement::MouseFocus());
      m_area.BackColor((CElement::MouseFocus())? m_area_color_hover : m_area_color);
      m_label.Color((CElement::MouseFocus())? m_label_color_hover : m_label_color);
     }
  }
//+------------------------------------------------------------------+
//| Сброс цвета пункта                                               |
//+------------------------------------------------------------------+
void CMenuItem::ResetColors(void)
  {
   CElement::MouseFocus(false);
   m_area.BackColor(m_area_color);
   m_label.Color(m_label_color);
  }

Методы для перемещения и удаления объектов реализованы подобно тому, как это сделано в классе CWindow, поэтому нет смысла повторять их здесь. Их код доступен в приложенных к статье файлах, и вы уже можете воспользоваться им. Поэтому добавьте минимальный необходимый код (см. следующий листинг ниже) в методах CMenuItem::OnEvent() и CMenuItem::OnEventTimer(), скомпилируйте файлы библиотеки и эксперта. Теперь ошибок, сообщающих о необходимой реализации методов, не возникнет.

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Если элемент не скрыт
      if(!CElement::m_is_visible)
         return;
      //--- Определим фокус
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      return;
     }  
  }
//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CMenuItem::OnEventTimer(void)
  {
//--- Изменение цвета объектов формы
   ChangeObjectsColor();
  }

 

Тест установки пункта меню

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

Рис. 5. Тест установки элемента «пункт меню» на график.

Рис. 5. Тест установки элемента «пункт меню» на график

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

Назовем этот метод CWndContainer::AddToElementsArray(). У него будет два параметра: (1) номер индекса формы, к которой нужно прикрепить элемент и (2) объект элемента, указатель которого нужно сохранить в базе. В самом начале будет производиться проверка, есть ли в базе форма (формы). Если нет ни одной, то в журнал выведется сообщение о том, что перед попыткой прикрепить элемент к форме, нужно сначала добавить ее в базу. Далее будет проверка на выход за пределы массива.

Если проблем не возникло, то (1) указатель добавляется в массив формы, к которой он присоединяется, (2) объекты элемента добавляются в общий массив объектов, (3) во всех формах сохраняется последний идентификатор этого элемента и (4) счетчик элементов увеличивается на один. Это не окончательная версия этого метода, и мы еще вернемся к нему. Код текущей версии представлен в листинге ниже:

//+------------------------------------------------------------------+
//| Добавляет указатель в массив элементов                           |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Если в базе нет форм для элементов управления
   if(ArraySize(m_windows)<1)
     {
      Print(__FUNCTION__," > Перед созданием элемента управления нужно создать форму "
            "и добавить ее в базу с помощью метода CWndContainer::AddWindow(CWindow &object).");
      return;
     }
//--- Если запрос на несуществующую форму
   if(window_index>=ArraySize(m_windows))
     {
      Print(PREVENTING_OUT_OF_RANGE," window_index: ",window_index,"; ArraySize(m_windows): ",ArraySize(m_windows));
      return;
     }
//--- Добавим в общий массив элементов
   int size=ArraySize(m_wnd[window_index].m_elements);
   ArrayResize(m_wnd[window_index].m_elements,size+1);
   m_wnd[window_index].m_elements[size]=GetPointer(object);
//--- Добавим объекты элемента в общий массив объектов
   AddToObjectsArray(window_index,object);
//--- Запомним во всех формах id последнего элемента
   int windows_total=ArraySize(m_windows);
   for(int w=0; w<windows_total; w++)
      m_windows[w].LastId(m_counter_element_id);
//--- Увеличим счетчик идентификаторов элементов
   m_counter_element_id++;
  }

Текущей версии метода CWndContainer::AddToElementsArray() уже достаточно для того, чтобы протестировать эксперта, над которым начали работать выше. В самом конце метода CProgram::CreateMenuItem() после создания элемента нужно добавить строчку кода, как это показано в сокращенной версии метода ниже.

//+------------------------------------------------------------------+
//| Создает пункт меню                                               |
//+------------------------------------------------------------------+
bool CProgram::CreateMenuItem(string item_text)
  {
//--- Сохраним указатель на окно
//--- Координаты  
//--- Установим свойства перед созданием
//--- Создание пункта меню
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_menu_item);
   return(true);
  }

Если теперь скомпилировать файлы, в которых производили изменения, и загрузить эксперта на график, при перемещении формы элемент «пункт меню» будет перемещаться вместе с ней, а его объекты будут изменять свой внешний вид при наведении курсора мыши:

Рис. 6. Тест элемента «пункт меню» как части графического интерфейса

Рис. 6. Тест элемента «пункт меню» как части графического интерфейса

 

Доработка главных классов библиотеки

Но если попробовать сейчас свернуть форму, то элемент «пункт меню» не будет скрыт, как это ожидается. Каким образом можно реализовать скрытие элементов, которые присоединены к форме? Управление событиями графика сейчас организовано в классе CWndEvents, у которого есть доступ к базе всех элементов своего базового класса CWndContainer. Но как можно понять, что была нажата кнопка сворачивания или разворачивания формы? Для таких ситуаций язык MQL предлагает функцию EventChartCustom(), с помощью которой можно генерировать пользовательские события.

Сейчас при нажатии кнопки сворачивания формы программа в обработчике OnEvent() класса CWindow отследит событие CHARTEVENT_OBJECT_CLICK и, сверив значение строкового параметра (sparam) этого события с именем графического объекта, вызовет метод CWindow::RollUp(). Вот в этот момент можно отправить свое сообщение в очередь потока событий, принять его в обработчике класса CWndEvents и, так как там есть доступ ко всем элементам, в цикле вызвать методы CElement::Hide() каждого из них. Аналогично нужно сделать и для события разворачивания формы, делая все ее элементы видимыми с помощью их методов CElement::Show().

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

//--- События
#define ON_WINDOW_UNROLL          (1) // Разворачивание формы
#define ON_WINDOW_ROLLUP          (2) // Сворачивание формы

В методах CWindow::RollUp() и CWindow::Unroll() в самом конце нужно вызвать функцию EventChartCustom(), передав в нее:

  1. Идентификатор графика.
  2. Идентификатор пользовательского события.
  3. В качестве третьего параметра (lparam) отправим идентификатор элемента.
  4. А четвертым параметром (dparam) будет номер подокна графика, в котором находится программа.

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

Ниже представлены сокращенные версии методов CWindow::RollUp() и CWindow::Unroll() только с тем, что нужно добавить в них (комментарии оставлены):

//+------------------------------------------------------------------+
//| Сворачивает окно                                                 |
//+------------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Заменить кнопку
//--- Установить и запомнить размер
//--- Отключить кнопку
//--- Состояние формы "Свернуто"
//--- Если это индикатор в подокне с фиксированной высотой и с режимом сворачивания подокна,
//    установим размер для подокна индикатора
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_WINDOW_ROLLUP,CElement::Id(),m_subwin,"");
  }
//+------------------------------------------------------------------+
//| Разворачивает окно                                               |
//+------------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Заменить кнопку
//--- Установить и запомнить размер
//--- Отключить кнопку
//--- Состояние формы "Развернуто"
//--- Если это индикатор в подокне с фиксированной высотой и с режимом сворачивания подокна,
//    установим размер для подокна индикатора
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_WINDOW_UNROLL,CElement::Id(),m_subwin,"");
  }

Теперь в классе CWndEvents нужно создать методы, которые будут обрабатывать эти пользовательские события. Назовем их так же, как и макросы для идентификаторов. Ниже показан код объявления и реализации методов в классе CWndEvents:

class CWndEvents : public CWndContainer
  {
private:
   //--- Сворачивание/разворачивание формы
   bool              OnWindowRollUp(void);
   bool              OnWindowUnroll(void);
   //---
  };
//+------------------------------------------------------------------+
//| Событие ON_WINDOW_ROLLUP                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowRollUp(void)
  {
//--- Если сигнал свернуть форму
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_ROLLUP)
      return(false);
//--- Если идентификатор окна и номер подокна совпадают
   if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(0);
      for(int e=0; e<elements_total; e++)
        {
         //--- Скрыть все элементы кроме формы
         if(m_wnd[0].m_elements[e].ClassName()!="CWindow")
            m_wnd[0].m_elements[e].Hide();
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Событие ON_WINDOW_UNROLL                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowUnroll(void)
  {
//--- Если сигнал развернуть форму
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL)
      return(false);
//--- Если идентификатор окна и номер подокна совпадают
   if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(0);
      for(int e=0; e<elements_total; e++)
        {
         //--- Сделать видимыми все элементы кроме формы и тех,
         //    которые являются выпадающими
         if(m_wnd[0].m_elements[e].ClassName()!="CWindow")
            if(!m_wnd[0].m_elements[e].IsDropdown())
               m_wnd[0].m_elements[e].Show();
        }
     }
//---
   return(true);
  }

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

Код метода CWindow::Show():

//+------------------------------------------------------------------+
//| Показывает окно                                                  |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Сделать видимыми все объекты
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_ALL_PERIODS);
//--- Состояние видимости
   CElement::m_is_visible=true;
//--- Обнуление фокуса
   CElement::MouseFocus(false);
   m_button_close.MouseFocus(false);
   m_button_close.State(false);
  }

Вызывать эти методы будем в методе CWndEvents::ChartEventCustom():

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

В дальнейшем все методы, которые будут обрабатывать пользовательские события, будем размещать в методе CWndEvents::ChartEventCustom().

В свою очередь, вызов CWndEvents::ChartEventCustom() нужно разместить в методе CWndEvents::ChartEvent() перед методами, где обрабатываются события графика:

//+------------------------------------------------------------------+
//| Обработка событий программы                                      |
//+------------------------------------------------------------------+
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);
//--- Пользовательские события
   ChartEventCustom();
//--- Проверка событий элементов интерфейса
   CheckElementsEvents();
//--- Событие перемещения мыши
   ChartEventMouseMove();
//--- Событие изменения свойств графика
   ChartEventChartChange();
  }

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

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

 

Заключение

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

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

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

  • Графические интерфейсы II: Элемент "Пункт меню" (Глава 1)
  • Графические интерфейсы II: Элементы "Разделительная линия" и "Контекстное меню" (Глава 2)
  • Графические интерфейсы II: Настройка обработчиков событий библиотеки (Глава 3)
  • Графические интерфейсы II: Элемент "Главное меню" (Глава 4)
Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Anatoli Kazharski
Anatoli Kazharski | 11 февр. 2016 в 17:48
zaskok3:
Для фрилансера и продавцов (Маркет) создание графического интерфейса имеет большое значение, т.к. позволяет привлечь потенциальных заказчиков/покупателей.

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

Поскольку читать и разбираться в столь обширном материале, совершенно не понимая, зачем это нужно - вряд ли кто-то будет. А на видео-демонстрации каждый сам для себя решит, нужно это ему или нет. И даже если сейчас не нужно, то будет представление о возможностях, на которые в будущем можно будет сделать упор. А если, действительно, заинтересует, будут читать статьи внимательно и задавать вопросы по делу.

Просьба от имени НЕ фрилансеров и продавцов.

Тем, кто изучает MQL и объектно-ориентированное программирование, думаю будет интересно почитать. Намного проще изучать язык программирования на подобных проектах, а не на каких-то абстрактных примерах, которые действительно очень часто не отвечают на вопрос: "А где же это можно применить?".

Мне вот, например, очень была нужна такая библиотека, но из того, что было предложено MQL-сообществу, мне многого не хватало для комфортного использования. 

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

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

[Удален] | 11 февр. 2016 в 18:18
Спасибо, из Вашего ответа я понял, где мне может это пригодиться:
Во время торговли на реале я хочу видеть, как бы шла торговля в тестере - реал-тайм. Т.е. вижу боевое окружение штатными средствами. И вижу тестерное реал-тайм окружение уже Вашими средствами визуализации. При этом могу посмотреть и историю в этом виртуальном тестере и график сделок.

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

2D-чарты рисовать будет библиотека?
Anatoli Kazharski
Anatoli Kazharski | 11 февр. 2016 в 19:08

zaskok3:

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

Да, но если Вы сами напишите такой тестер. А с помощью этой библиотеки можно создать для него графический интерфейс.

zaskok3:

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

Да, можно. Но придётся подождать пока будет опубликована вся библиотека. Всего будет около 20 статей, возможно уже даже больше. На текущий момент опубликовано пока только 6 статей серии.

zaskok3:

2D-чарты рисовать будет библиотека?  

У меня уже есть один подобный класс-элемент и вполне возможно Вам этого уже будет достаточно. Но это пока только слегка модифицированный класс из стандартной библиотеки. Мне этого конечно тоже далеко недостаточно и я планирую создать свою библиотеку для построения графиков. Если конечно её не напишет, кто-нибудь раньше меня. ) Хочется что-нибудь наподобие такого качества, как это реализовано, например, в Excel.

[Удален] | 12 февр. 2016 в 09:58
Anatoli Kazharski:
Спасибо за развернутые ответы! Появилось представление, где это может пригодиться именно трейдеру-разработчику для своих реальных нужд, а не ради изысков. Статьи очень содержательные, но буду смотреть подробно и пробовать, когда появятся уже соответствующие возможности. 2D-графики - это особенно хорошо...
[Удален] | 5 апр. 2019 в 12:16
Anatoli Kazharski:

Добавьте, пожалуйста, ссылки на список статей второй части:

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

Графические интерфейсы II: Элемент "Пункт меню" (Глава 1)
Графические интерфейсы II: Элементы "Разделительная линия" и "Контекстное меню" (Глава 2)
Графические интерфейсы II: Настройка обработчиков событий библиотеки (Глава 3)
Графические интерфейсы II: Элемент "Главное меню" (Глава 4)

Сейчас кликабельна только первая (Глава 1). Аналогично, хотелось бы переходы по всем ссылкам в конце глав 2 и 3.

Создание ручных торговых стратегий с использованием нечеткой логики Создание ручных торговых стратегий с использованием нечеткой логики
В статье рассматривается возможность улучшения ручных торговых стратегий с помощью теории нечетких множеств. В качестве примера пошагово описан поиск стратегии и подбор ее параметров, а затем — применение нечеткой логики для размытия слишком формальных критериев входа в рынок. Таким образом, после модификации стратегии мы получаем гибкие условия открытия позиции, более оптимально реагирующие на рыночную ситуацию.
Универсальный торговый эксперт: Торговля в группе и управление портфелем стратегий (Часть 4) Универсальный торговый эксперт: Торговля в группе и управление портфелем стратегий (Часть 4)
В заключительной части серии статей о торговом движке CStrategy мы рассмотрим одновременную работу нескольких торговых алгоритмов, научимся загружать стратегии из XML-файлов, а также представим простую панель для выбора экспертов, находящихся внутри одного исполняемого модуля, и управления их торговыми режимами.
Как быстро добавить панель управления к индикатору и советнику Как быстро добавить панель управления к индикатору и советнику
Вы хотите добавить к своему индикатору или советнику графическую панельку для удобного и быстрого управления, но не знаете, как это сделать? В этой статье шаг за шагом я покажу как "прикрутить" панель диалога со входными параметрами к вашей MQL4/MQL5-программе.
Применение контейнеров для компоновки графического интерфейса: класс CGrid Применение контейнеров для компоновки графического интерфейса: класс CGrid
В данной статье описан альтернативный метод создания графического интерфейса на основе компоновки и контейнеров при помощи менеджера компоновки — класса CGrid. Класс CGrid представляет собой вспомогательный элемент управления, который действует как контейнер для других контейнеров и элементов управления с применением табличной компоновки.