Элементы управления в подокне индикатора. Кнопки.

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

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

Для создания кнопок в MQL5 можно взять графические объекты различных типов. Это может быть OBJ_BUTTON (кнопка), OBJ_BITMAP (рисунок), OBJ_BITMAP_LABEL (графическая метка) или OBJ_EDIT (поле ввода). 

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

Итак. С помощью Мастера MQL5 создайте индикатор. Код шаблона в итоге нужно сделать таким, как это показано ниже:

//+------------------------------------------------------------------+
//|                                                  TestButtons.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//---
#property indicator_separate_window // Индикатор в подокне
#property indicator_plots 0         // Отсутствие графических серий
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Деинициализация                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int    rates_total,     // размер массива price[]
                const int    prev_calculated, // обработано баров на предыдущем вызове
                const int    begin,           // откуда начинаются значимые данные
                const double &price[])        // массив для расчета
  {
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int     id,     // идентификатор события  
                  const long&   lparam, // параметр события типа long
                  const double& dparam, // параметр события типа double
                  const string& sparam) // параметр события типа string
  {
   
  }
//+------------------------------------------------------------------+

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

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

Ниже приведён их код с комментариями:

//---
#define NUMBER_BUTTONS_WIDTH  4     // Количество кнопок в ширину
#define NUMBER_BUTTONS_HEIGHT 3     // Количество кнопок в высоту
//+------------------------------------------------------------------+
//| Глобальные параметры                                             |
//+------------------------------------------------------------------+
//--- Шрифт
string            font_name="Calibri";
//--- Свойства подокна индикатора
int               number_subwindow      =WRONG_VALUE;             // Номер подокна
int               subwindow_height      =0;                       // Высота подокна
string            shortname_subwindow   ="TestButtons";           // Короткое имя индикатора
string            prefix                =shortname_subwindow+"_"; // Префикс для объектов
int               chart_width           =0;                       // Ширина графика
int               chart_height          =0;                       // Высота графика
int               chart_y_distance      =0;                       // Дистанция от верха графика до подокна
//--- Свойства кнопок
color             button_bg_color       =clrSteelBlue;            // Цвет кнопки
color             button_font_color     =clrWhite;                // Цвет шрифта
color             button_hover_bg_color =C'38,118,166';           // Цвет кнопки при наведении курсора
color             button_on_bg_color    =C'2,72,136';             // Цвет нажатой кнопки
//--- Отображаемый текст в кнопках
string buttons_text[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH]=
  {
     {"BUTTON 01","BUTTON 02","BUTTON 03","BUTTON 04"},
     {"BUTTON 05","BUTTON 06","BUTTON 07","BUTTON 08"},
     {"BUTTON 09","BUTTON 10","BUTTON 11","BUTTON 12"}
  };
//--- Названия объектов
string buttons_object_name[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH]=
  {
     {"button_01","button_02","button_03","button_04"},
     {"button_05","button_06","button_07","button_08"},
     {"button_09","button_10","button_11","button_12"}
  };
//--- Ширина кнопок
int buttons_width[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH];
//--- Высота кнопок
int buttons_height[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH];
//--- Координата X
int buttons_x_distance[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH];
//--- Координата Y
int buttons_y_distance[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH];
//--- Состояние кнопок
bool buttons_state[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH]=
  {
     {true,false,false,false},
     {false,false,false,false},
     {false,false,false,false}
  };
//--- Цвет кнопок
color buttons_color[NUMBER_BUTTONS_HEIGHT][NUMBER_BUTTONS_WIDTH];
//---

Во время загрузки индикатора на график в функции OnInit() нужно инициализировать массивы свойствами объектов рассчитав координаты и размеры. Также нужно включить слежение за перемещением курсора мыши. И наконец нужно установить кнопки в подокно индикатора.

В итоге код в функции OnInit() примет вид, как показано ниже:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Включим таймер с интервалом 1 секунда
   EventSetTimer(1);
//--- Добавим префикс к именам объектов
   AddPrefix();
//--- Включим слежение за событиями мыши
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Установим короткое имя
   IndicatorSetString(INDICATOR_SHORTNAME,shortname_subwindow);
//--- Получим свойства подокна
   SetSubwindowProperties();
//--- Установим свойства кнопок
   InitButtonsColor();       // Определим цвета
   InitButtonsXYdistance();  // Определим координаты
   InitButtonsWidthHeight(); // Определим размеры
//--- Установим панель кнопок
   SetButtonsPanel();
//--- Обновим график
   ChartRedraw();
//--- Всё прошло успешно
   return(INIT_SUCCEEDED);
  }
//---

Рассмотрим функции в выделенных строках в коде выше.

В функции AddPrefix() каждому графическому объекту добавляется префикс, который является коротким именем индикатора. Это нужно для того, чтобы, если на графике используется более одной программы, исключить замену/удаление/смещение объектов при совпадении имён объектов. Код функции AddPrefix() выглядит так:

//+------------------------------------------------------------------+
//| Добавляет ко всем названиям объектов префикс                     |
//+------------------------------------------------------------------+
void AddPrefix()
  {
//--- Установим префикс названиям объектов
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
         buttons_object_name[j][i]=prefix+buttons_object_name[j][i];
  }
//---

Свойства графика необходимые для расчётов будем получать в функции SetSubwindowProperties():

//+------------------------------------------------------------------+
//| Установим свойства подокна                                       |
//+------------------------------------------------------------------+
void SetSubwindowProperties()
  {
//--- Получим номер подокна индикатора
   number_subwindow=ChartWindowFind(0,shortname_subwindow);
//--- Получим ширину и высоту подокна
   chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
   subwindow_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,number_subwindow);
  }
//---

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

//+------------------------------------------------------------------+
//| Определяет цвет для кнопок                                       |
//+------------------------------------------------------------------+
void InitButtonsColor()
  {
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         //--- Если кнопка нажата
         if(buttons_state[j][i])
            buttons_color[j][i]=button_on_bg_color;
         //--- Если кнопка отжата
         else
            buttons_color[j][i]=button_bg_color;
        }
     }
  }
//+------------------------------------------------------------------+
//| Определяет координаты XY для кнопок                              |
//+------------------------------------------------------------------+
void InitButtonsXYdistance()
  {
   int button_width=chart_width/NUMBER_BUTTONS_WIDTH;
   int button_height=subwindow_height/NUMBER_BUTTONS_HEIGHT;
//---
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         if(i==0)
            buttons_x_distance[j][i]=0;
         else
            buttons_x_distance[j][i]=(button_width*i)-i;
         //---
         if(j==0)
            buttons_y_distance[j][i]=0;
         else
            buttons_y_distance[j][i]=(button_height*j)-j;
        }
     }
  }
//+------------------------------------------------------------------+
//| Определяет ширину и высоту для кнопок                            |
//+------------------------------------------------------------------+
void InitButtonsWidthHeight()
  {
   int button_width=chart_width/NUMBER_BUTTONS_WIDTH;
   int button_height=subwindow_height/NUMBER_BUTTONS_HEIGHT;
//---
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         if(i==NUMBER_BUTTONS_WIDTH-1)
            buttons_width[j][i]=chart_width-(button_width*(NUMBER_BUTTONS_WIDTH-1)-i);
         else
            buttons_width[j][i]=button_width;
         //---
         if(j==NUMBER_BUTTONS_HEIGHT-1)
            buttons_height[j][i]=subwindow_height-(button_height*(NUMBER_BUTTONS_HEIGHT-1)-j)-1;
         else
            buttons_height[j][i]=button_height;
        }
     }
  }
//---

И наконец функция SetButtonsPanel() устанавливает кнопки в подокно индикатора. Ниже представлен её код:

//+------------------------------------------------------------------+
//| Установка кнопок                                                 |
//+------------------------------------------------------------------+
void SetButtonsPanel()
  {
//--- Создадим кнопки
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         CreateEdit(0,number_subwindow,buttons_object_name[j][i],buttons_text[j][i],
                    CORNER_LEFT_UPPER,font_name,8,button_font_color,buttons_color[j][i],clrNONE,
                    buttons_width[j][i],buttons_height[j][i],
                    buttons_x_distance[j][i],buttons_y_distance[j][i],2,true,buttons_text[j][i]);
        }
     }
  }
//---

Код функции CreateEdit() выглядит так:

//+------------------------------------------------------------------+
//| СОЗДАНИЕ_ОБЪЕКТА_EDIT                                            |
//+------------------------------------------------------------------+
void CreateEdit(long   chart_id,     // id графика
                int    subwindow,    // номер окна (подокна)
                string name,         // имя объекта
                string text,         // отображаемый текст
                long   corner,       // угол графика
                string font,         // шрифт
                int    font_size,    // размер шрифта
                color  font_color,   // цвет шрифта
                color  bg_color,     // цвет фона
                color  border_color, // цвет рамки
                int    x_size,       // ширина
                int    y_size,       // высота
                int    x_dist,       // координата по шкале X
                int    y_dist,       // координата по шкале Y
                long   zorder,       // приоритет
                bool   read_only,    // флаг "Только для чтения"
                string tooltip)      // всплывающая подсказка
  {
//--- Если объект создался успешно, то...
   if(ObjectCreate(chart_id,name,OBJ_EDIT,subwindow,0,0))
     {
      ObjectSetString(chart_id,name,OBJPROP_TEXT,text);                  // установка имени
      ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner);             // установка угла привязки
      ObjectSetString(chart_id,name,OBJPROP_FONT,font);                  // установка шрифта
      ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size);        // установка размера шрифта
      ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color);          // цвет шрифта
      ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,bg_color);          // цвет фона
      ObjectSetInteger(chart_id,name,OBJPROP_BORDER_COLOR,border_color); // цвет фона
      ObjectSetInteger(chart_id,name,OBJPROP_XSIZE,x_size);              // ширина
      ObjectSetInteger(chart_id,name,OBJPROP_YSIZE,y_size);              // высота
      ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_dist);          // установка координаты X
      ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_dist);          // установка координаты Y
      ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);          // нельзя выделить объект, если FALSE
      ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,zorder);             // Приоритет выше/ниже
      ObjectSetInteger(chart_id,name,OBJPROP_READONLY,read_only);        // Только для чтения
      ObjectSetInteger(chart_id,name,OBJPROP_ALIGN,ALIGN_CENTER);        // Выравнять по центру
      ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,tooltip);            // нет всплывающей подсказки, если "\n"
     }
  }
//---

Обратите внимание на последний параметр функции CreateEdit(). Этот параметр отвечает за всплывающую подсказку при наведении курсора мыши на графический объект. Для примера, в функции SetButtonsPanel() в качестве этого параметра передаются значения из массива buttons_text (текст отображённый в кнопках). Но можно создать отдельный массив с более подробными пояснениями для пользователей.

Теперь, если загрузить индикатор на график можно увидеть такой результат (см. скриншот ниже):

Установка кнопок в подокно при загрузке индикатора на график

Это пока просто объекты, которые упорядоченны в подокне индикатора и не реагирующие на действия пользователя. Теперь займёмся их "оживлением".

Сначала сделаем так, чтобы кнопки подгонялись под размер подокна при изменении его размера. Для этого нужно написать ещё две функции: SetXYdistanceButtons() и ResizeButtons(). Эти функции будут устанавливать координаты и размеры кнопкам. Ниже можно ознакомиться с их кодом:

//+------------------------------------------------------------------+
//| Обновляет координаты кнопок                                      |
//+------------------------------------------------------------------+
void SetXYdistanceButtons()
  {
//--- Установим координаты
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_XDISTANCE,buttons_x_distance[j][i]);
         ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_YDISTANCE,buttons_y_distance[j][i]);
        }
     }
  }
//+------------------------------------------------------------------+
//| Обновляет размеры кнопок                                         |
//+------------------------------------------------------------------+
void ResizeButtons()
  {
//--- Установим размеры
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_XSIZE,buttons_width[j][i]);
         ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_YSIZE,buttons_height[j][i]);
        }
     }
  }
//---

Для обработки события изменения свойств и размеров графика нужно использовать идентификатор CHARTEVENT_CHART_CHANGE. Ниже показано, какой код нужно добавить в тело функции OnChartEvent():

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int     id,     // идентификатор события  
                  const long&   lparam, // параметр события типа long
                  const double& dparam, // параметр события типа double
                  const string& sparam) // параметр события типа string
  {
//--- Отслеживает событие изменение свойств и размеров графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Получим свойства подокна
      SetSubwindowProperties();
      //--- Определим координаты для кнопок
      InitButtonsXYdistance();
      //--- Определим размеры кнопок
      InitButtonsWidthHeight();
      //--- Установим новые координаты кнопкам
      SetXYdistanceButtons();
      //--- Установим новые размеры кнопкам
      ResizeButtons();
      //--- Обновим график
      ChartRedraw(); return;
     }
  }
//---

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

Далее сделаем так, чтобы при наведении курсора на кнопку её цвет изменялся. Перед тем, как приступим к написанию функций, разберёмся как обрабатывается событие с идентификатором CHARTEVENT_MOUSE_MOVE.

В функции OnInit() у нас уже содержится строка, которая говорит программе отслеживать перемещение курсора мыши, а также состояние левой кнопки мыши:

//--- Включим слежение за событиями мыши
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//---

Если этого не сделать или в качестве последнего параметра передать значение false, события с идентификатором CHARTEVENT_MOUSE_MOVE не будут отслеживаться в функции OnChartEvent(). Это удобно, так как не в каждой программе может понадобиться отслеживать эти  события.

Чтобы понять, как это работает можно временно вставить код в функцию OnChartEvent() с выводом комментария на график:

//--- Отслеживание движения мыши и нажатия левой кнопки мыши
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n",
              "lparam (x): ",lparam,"\n",
              "dparam (y): ",dparam,"\n",
              "sparam (mb state): ",sparam
              );
      //---
      return;
     }
//---

Если сейчас перемещать курсор мыши на графике, в левом верхнем углу можно увидеть текущие значения координат курсора. Нажимая кнопку мыши можно увидеть изменение в строке комментария "sparam (mb state): ". То есть, значение в этой строке будет изменяться. Ноль (0) означает, что кнопка мыши отжата, а единица (1) - нажата.

Если нужно узнать, в каком подокне сейчас находится курсор мыши, можно воспользоваться функцией ChartXYToTimePrice(). В неё передаются координаты, а возвращает она (в переданные ей по ссылке переменные) номер окна/подокна, время и цену. Увидеть это можно протестировав следующий код:

//--- Отслеживание движения мыши и нажатия левой кнопки мыши
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      int      x      =(int)lparam; // Координата по оси X
      int      y      =(int)dparam; // Координата по оси Y
      int      window =WRONG_VALUE; // Номер окна, в котором находится курсор
      datetime time   =NULL;        // Время соответствующее координате X
      double   level  =0.0;         // Уровень (цена) соответствующий координате Y
      //--- Получим местоположение курсора
      if(ChartXYToTimePrice(0,x,y,window,time,level))
        {
         Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n",
                 "x: ",x,"\n",
                 "y: ",y,"\n",
                 "sparam (mb state): ",sparam,"\n",
                 "window: ",window,"\n",
                 "time: ",time,"\n",
                 "level: ",DoubleToString(level,_Digits)
                 );
        }
      //---
      return;
     }
//---

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

//--- Получим местоположение курсора
      if(ChartXYToTimePrice(0,x,y,window,time,level))
        {
         //--- Получим расстояние от верха графика до подокна индикатора
         chart_y_distance=(int)ChartGetInteger(0,CHART_WINDOW_YDISTANCE,number_subwindow);
         //--- Преобразуем координату Y в относительную
         y-=chart_y_distance;
         //---
         Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n",
                 "x: ",x,"\n",
                 "y: ",y,"\n",
                 "sparam (mb state): ",sparam,"\n",
                 "window: ",window,"\n",
                 "time: ",time,"\n",
                 "level: ",DoubleToString(level,_Digits)
                 );
        }
//---

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

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

      
         //--- Если курсор в зоне подокна,
         //    отключим скролл графика
         if(window==number_subwindow)
            ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
         //--- Включим скролл графика, если вышли из зоны подокна индикатора
         else
            ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
//---

Далее напишем функцию ChangeButtonsColorHover(), которая изменяет цвет кнопки, когда курсор находится над ней:

//+------------------------------------------------------------------+
//| Изменение цвета кнопок при наведении курсора мыши                |
//+------------------------------------------------------------------+
void ChangeButtonsColorHover(int x,int y)
  {
   int x1,y1,x2,y2;
//--- Инициализируем массив координат XY у кнопок
   InitButtonsXYdistance();
//--- Определим, расположен ли курсор над одной из кнопок
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         //--- Если эта кнопка нажата,
         //    перейдём к следующей
         if(buttons_state[j][i])
            continue;
         //--- Получим границы кнопки
         x1=buttons_x_distance[j][i];
         y1=buttons_y_distance[j][i];
         x2=buttons_x_distance[j][i]+buttons_width[j][i];
         y2=buttons_y_distance[j][i]+buttons_height[j][i];
         //--- Если курсор мыши в зоне кнопки,
         //    установим ей цвет отличия
         if(x>x1 && x<x2 && y>y1 && y<y2)
            ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_BGCOLOR,button_hover_bg_color);
         //--- Иначе установим обычный цвет
         else
            ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_BGCOLOR,button_bg_color);
        }
     }
  }
//---

В итоге в ветке идентификатора CHARTEVENT_MOUSE_MOVE получился вот такой код:

//--- Отслеживание движения мыши и нажатия левой кнопки мыши
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      int      x      =(int)lparam; // по горизонтали
      int      y      =(int)dparam; // по вертикали
      int      window =WRONG_VALUE; // окно, в котором находится курсор
      datetime time   =NULL;        // время бара, соответствующего курсору
      double   level  =0.0;         // уровень (цена), соответствующая курсору
      //--- Получим местоположение курсора
      if(ChartXYToTimePrice(0,x,y,window,time,level))
        {
         //--- Получим расстояние от верха графика до подокна индикатора
         chart_y_distance=(int)ChartGetInteger(0,CHART_WINDOW_YDISTANCE,number_subwindow);
         //--- Преобразуем координату Y в относительную
         y-=chart_y_distance;
         //--- Если курсор в зоне подокна,
         //    отключим скролл графика
         if(window==number_subwindow)
            ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
         //--- Включим скролл графика, если вышли из зоны подокна индикатора
         else
            ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
         //---
         ChangeButtonsColorHover(x,y);
        }
      //--- Обновим график
      ChartRedraw(); return;
     }
//---

Если сейчас провести курсор над кнопками, можно увидеть, как изменяется/восстанавливается их цвет.

На данный момент цвет нажатой кнопки только у BUTTON 01. Если нажимать на другие кнопки они не будут реагировать на нажатие и менять свой цвет. Чтобы это реализовать нужно использовать событие с идентификатором CHARTEVENT_OBJECT_CLICK.

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

//+------------------------------------------------------------------+
//| Инициализирует состояние кнопок, если было нажатие на кнопке     |
//+------------------------------------------------------------------+
bool InitButtonsState(string click_object)
  {
//--- Получим номер подокна индикатора
   number_subwindow=ChartWindowFind(0,shortname_subwindow);
//--- Если нажали на кнопке и она находится в подокне индикатора
   if(ObjectFind(0,click_object)==number_subwindow && 
      StringFind(click_object,prefix+"button_",0)>=0)
     {
      //--- Определим нажатую кнопку
      for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
        {
         for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
           {
            //--- Определим состояние всех кнопок
            if(click_object==buttons_object_name[j][i])
               buttons_state[j][i]=true;
            else
               buttons_state[j][i]=false;
           }
        }
      //---
      return(true);
     }
//---
   return(false);
  }
//+------------------------------------------------------------------+
//| Изменение цвета кнопок при нажатии                               |
//+------------------------------------------------------------------+
void ChangeColorOnButtons()
  {
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
     {
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
        {
         //--- Если эта кнопка нажата,
         //    установим ей цвет отличия
         if(buttons_state[j][i])
            ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_BGCOLOR,button_on_bg_color);
         //--- Не нажатой кнопке установим обычный цвет
         else
            ObjectSetInteger(0,buttons_object_name[j][i],OBJPROP_BGCOLOR,button_bg_color);
        }
     }
  }
//---

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

//+------------------------------------------------------------------+
//| Деинициализация                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(reason==REASON_REMOVE ||  // Если индикатор удалён с графика или
      reason==REASON_RECOMPILE) // программа была перекомпилирована
     {
      //--- Отключим таймер
      EventKillTimer();
      //--- Удалим объекты
      DeleteButtons();
      //--- Включим управление скроллу графика
      ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
      //--- Отключим слежение за событиями мыши
      ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,false);
      //--- Обновим график
      ChartRedraw();
     }
  }
//---

Функции для удаления графических объектов программы:

//+------------------------------------------------------------------+
//| Удалить кнопки                                                   |
//+------------------------------------------------------------------+
void DeleteButtons()
  {
   for(int i=0; i<NUMBER_BUTTONS_WIDTH; i++)
      for(int j=0; j<NUMBER_BUTTONS_HEIGHT; j++)
         DeleteObjectByName(buttons_object_name[j][i]);
  }
//+------------------------------------------------------------------+
//| Удаляет объект по имени                                          |
//+------------------------------------------------------------------+
void DeleteObjectByName(string Name)
  {
//--- Если есть такой объект
   if(ObjectFind(0,Name)>=0)
     {
      //--- Если была ошибка при удалении, сообщим об этом
      if(!ObjectDelete(0,Name))
         Print("Ошибка ("+IntegerToString(GetLastError())+") при удалении объекта!");
     }
  }
//---

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

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Проверим, включено ли отслеживание событий мыши
   CheckEventMouseMove();
  }
//---

Код функции CheckEventMouseMove():

//+------------------------------------------------------------------+
//| Проверяет, включено ли отслеживание событий мыши                 |
//+------------------------------------------------------------------+
void CheckEventMouseMove()
  {
//--- Включим слежение за передвижением курсора, если режим отключен
   if(!ChartGetInteger(0,CHART_EVENT_MOUSE_MOVE))
      ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
  }
//---

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

Ниже можно посмотреть видеролик с демонстрацией того, что получилось:



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




Скачать исходный код индикатора TestButtons.mq5


Комментариев нет :

Отправить комментарий