English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Рецепты MQL5 - Элементы управления в подокне индикатора - Кнопки

Рецепты MQL5 - Элементы управления в подокне индикатора - Кнопки

MetaTrader 5Примеры | 21 октября 2013, 10:54
8 119 79
Anatoli Kazharski
Anatoli Kazharski

Введение

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

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

 

Процесс разработки

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

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

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

//+------------------------------------------------------------------+
//|                                                  TestButtons.mq5 |
//|                        Copyright 2013, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, 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()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---

  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---

  }
//+------------------------------------------------------------------+

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

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

//---
#define BUTTON_COLUMNS  4           // Количество кнопок по ширине
#define BUTTON_ROWS 3               // Количество кнопок по высоте
//+------------------------------------------------------------------+
//| Глобальные параметры                                             |
//+------------------------------------------------------------------+
//--- Шрифт
string            font_name="Calibri";
//--- Свойства подокна индикатора
int               subwindow_number           =WRONG_VALUE;             // Номер подокна
int               subwindow_height           =0;                       // Высота подокна
string            subwindow_shortname        ="TestButtons";           // Короткое имя индикатора
string            prefix                     =subwindow_shortname+"_"; // Префикс для имен объектов
int               chart_width                =0;                       // Ширина графика
int               chart_height               =0;                       // Высота графика
int               chart_y_offset             =0;                       // Дистанция от верха графика до подокна
//--- Цвета элементов кнопки
color             background_color           =clrSteelBlue;            // Цвет кнопки
color             font_color                 =clrWhite;                // Цвет шрифта
color             hover_background_color     =C'38,118,166';           // Цвет кнопки при наведении курсора
color             clicked_background_color   =C'2,72,136';             // Цвет нажатой кнопки
//--- Отображаемый текст в кнопках
string button_texts[BUTTON_ROWS][BUTTON_COLUMNS]=
  {
     {"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 button_object_names[BUTTON_ROWS][BUTTON_COLUMNS]=
  {
     {"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 button_widths[BUTTON_ROWS][BUTTON_COLUMNS];
//--- Высоты кнопок
int button_heights[BUTTON_ROWS][BUTTON_COLUMNS];
//--- Координаты X
int button_x_distances[BUTTON_ROWS][BUTTON_COLUMNS];
//--- Координаты Y
int button_y_distances[BUTTON_ROWS][BUTTON_COLUMNS];
//--- Состояния кнопок
bool button_states[BUTTON_ROWS][BUTTON_COLUMNS]=
  {
     {true,false,false,false},
     {false,false,false,false},
     {false,false,false,false}
  };
//--- Цвета кнопок
color button_colors[BUTTON_ROWS][BUTTON_COLUMNS];

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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Включим таймер с интервалом 1 секунда
   EventSetTimer(1);
//--- Добавим префикс к именам объектов
   AddPrefix();
//--- Включим слежение за событиями мыши
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Установим короткое имя
   IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname);
//--- Установим свойства подокна
   SetSubwindowProperties();
//--- Установим свойства кнопок
   SetButtonColors();      // Цвета
   SetButtonCoordinates(); // Координаты
   SetButtonSizes();       // Размеры
//--- Добавим кнопочную панель
   AddButtonsPanel();
//--- Обновим график
   ChartRedraw();
//--- Все прошло успешно
   return(INIT_SUCCEEDED);
  }

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает свойства подокна                                   |
//+------------------------------------------------------------------+
void SetSubwindowProperties()
  {
//--- Номер подокна индикатора
   subwindow_number=ChartWindowFind(0,subwindow_shortname);
//--- Ширина и высота подокна
   chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
   subwindow_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,subwindow_number);
  }

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

//+------------------------------------------------------------------+
//| Задает цвет для кнопок                                           |
//+------------------------------------------------------------------+
void SetButtonColors()
  {
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         //--- Если кнопка нажата
         if(button_states[j][i])
            button_colors[j][i]=clicked_background_color;
         //--- Если кнопка отжата
         else
            button_colors[j][i]=background_color;
        }
     }
  }
//+------------------------------------------------------------------+
//| Задает координаты X и Y для кнопок                               |
//+------------------------------------------------------------------+
void SetButtonCoordinates()
  {
   int button_width=chart_width/BUTTON_COLUMNS;
   int button_height=subwindow_height/BUTTON_ROWS;
//---
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         if(i==0)
            button_x_distances[j][i]=0;
         else
            button_x_distances[j][i]=(button_width*i)-i;
         //---
         if(j==0)
            button_y_distances[j][i]=0;
         else
            button_y_distances[j][i]=(button_height*j)-j;
        }
     }
  }
//+------------------------------------------------------------------+
//| Задает ширину и высоту для кнопок                                |
//+------------------------------------------------------------------+
void SetButtonSizes()
  {
   int button_width=chart_width/BUTTON_COLUMNS;
   int button_height=subwindow_height/BUTTON_ROWS;
//---
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         if(i==BUTTON_COLUMNS-1)
            button_widths[j][i]=chart_width-(button_width*(BUTTON_COLUMNS-1)-i);
         else
            button_widths[j][i]=button_width;
         //---
         if(j==BUTTON_ROWS-1)
            button_heights[j][i]=subwindow_height-(button_height*(BUTTON_ROWS-1)-j)-1;
         else
            button_heights[j][i]=button_height;
        }
     }
  }

И, наконец, функция AddButtonsPanel() добавляет кнопки в подокно индикатора:

//+------------------------------------------------------------------+
//| Добавляет кнопки в подокно индикатора                            |
//+------------------------------------------------------------------+
void AddButtonsPanel()
  {
//--- Создадим кнопки
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         CreateButton(0,subwindow_number,button_object_names[j][i],button_texts[j][i],
                      CORNER_LEFT_UPPER,font_name,8,font_color,button_colors[j][i],clrNONE,
                      button_widths[j][i],button_heights[j][i],
                      button_x_distances[j][i],button_y_distances[j][i],2,true,button_texts[j][i]);
        }
     }
  }

Код вспомогательной функции CreateButton() выглядит так:

//+------------------------------------------------------------------+
//| Создает кнопку (графический объект типа "Поле ввода")            |
//+------------------------------------------------------------------+
void CreateButton(long   chart_id,     // id графика
                  int    sub_window,   // номер окна (подокна)
                  string object_name,  // имя объекта
                  string text,         // отображаемый текст
                  long   corner,       // угол графика
                  string font,         // шрифт
                  int    font_size,    // размер шрифта
                  color  c_font,       // цвет шрифта
                  color  c_background, // цвет фона
                  color  c_border,     // цвет рамки
                  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,object_name,OBJ_EDIT,subwindow_number,0,0))
     {
      ObjectSetString(chart_id,object_name,OBJPROP_TEXT,text);              // имя
      ObjectSetInteger(chart_id,object_name,OBJPROP_CORNER,corner);         // угол привязки
      ObjectSetString(chart_id,object_name,OBJPROP_FONT,font);              // шрифт
      ObjectSetInteger(chart_id,object_name,OBJPROP_FONTSIZE,font_size);    // размер шрифта
      ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,c_font);          // цвет шрифта
      ObjectSetInteger(chart_id,object_name,OBJPROP_BGCOLOR,c_background);  // цвет фона
      ObjectSetInteger(chart_id,object_name,OBJPROP_BORDER_COLOR,c_border); // цвет рамки
      ObjectSetInteger(chart_id,object_name,OBJPROP_XSIZE,x_size);          // ширина
      ObjectSetInteger(chart_id,object_name,OBJPROP_YSIZE,y_size);          // высота
      ObjectSetInteger(chart_id,object_name,OBJPROP_XDISTANCE,x_dist);      // координата X
      ObjectSetInteger(chart_id,object_name,OBJPROP_YDISTANCE,y_dist);      // координата Y
      ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,false);      // объект нельзя выделить
      ObjectSetInteger(chart_id,object_name,OBJPROP_ZORDER,zorder);         // приоритет
      ObjectSetInteger(chart_id,object_name,OBJPROP_READONLY,read_only);    // текст не редактируется
      ObjectSetInteger(chart_id,object_name,OBJPROP_ALIGN,ALIGN_CENTER);    // выровнять по центру
      ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,tooltip);        // без всплывающей подсказки, если "\n"
     }
  }

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

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

Рис. 1. - Добавление кнопок в подокно индикатора

Рис. 1. - Добавление кнопок в подокно индикатора

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

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

//+------------------------------------------------------------------+
//| Обновляет координаты кнопок                                      |
//+------------------------------------------------------------------+
void UpdateButtonCoordinates()
  {
//--- Установим координаты
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         ObjectSetInteger(0,button_object_names[j][i],OBJPROP_XDISTANCE,button_x_distances[j][i]);
         ObjectSetInteger(0,button_object_names[j][i],OBJPROP_YDISTANCE,button_y_distances[j][i]);
        }
     }
  }
//+------------------------------------------------------------------+
//| Обновляет размеры кнопок                                         |
//+------------------------------------------------------------------+
void ResizeButtons()
  {
//--- Установим размеры
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         ObjectSetInteger(0,button_object_names[j][i],OBJPROP_XSIZE,button_widths[j][i]);
         ObjectSetInteger(0,button_object_names[j][i],OBJPROP_YSIZE,button_heights[j][i]);
        }
     }
  }

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

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,           // идентификатор события
                  const long &lparam,     // параметр события типа long
                  const double &dparam,   // параметр события типа double
                  const string &sparam)   // параметр события типа string
  {
//--- Отслеживание события изменения свойств и размеров графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Установим свойства подокна
      SetSubwindowProperties();
      //--- Установим координаты для кнопок
      SetButtonCoordinates();
      //--- Установим размеры кнопок
      SetButtonSizes();
      //--- Установим новые координаты кнопкам
      UpdateButtonCoordinates();
      //--- Установим новые размеры кнопкам
      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 (статус кнопок мыши): ",sparam
              );

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

Если нужно узнать, в каком подокне сейчас находится курсор мыши, можно воспользоваться функцией 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   price  =0.0;         // Цена, соответствующая координате Y
      //--- Получим местоположение курсора
      if(ChartXYToTimePrice(0,x,y,window,time,price))
        {
         Comment("id: ",CHARTEVENT_MOUSE_MOVE,"\n",
                 "x: ",x,"\n",
                 "y: ",y,"\n",
                 "sparam (статус кнопок мыши): ",sparam,"\n",
                 "окно: ",window,"\n",
                 "время: ",time,"\n",
                 "цена: ",DoubleToString(price,_Digits)
                 );
        }
      //---
      return;
     }

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Изменение цвета кнопки при наведении курсора мыши                |
//+------------------------------------------------------------------+
void ChangeButtonColorOnHover(int x,int y)
  {
   int x1,y1,x2,y2;
//--- Инициализируем массив координат XY у кнопок
   SetButtonCoordinates();
//--- Определим, расположен ли курсор над одной из кнопок
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         //--- Если эта кнопка нажата, перейдём к следующей
         if(button_states[j][i])
            continue;
         //--- Получим границы кнопки
         x1=button_x_distances[j][i];
         y1=button_y_distances[j][i];
         x2=button_x_distances[j][i]+button_widths[j][i];
         y2=button_y_distances[j][i]+button_heights[j][i];
         //--- Если курсор мыши в зоне кнопки, установим ей новый цвет
         if(x>x1 && x<x2 && y>y1 && y<y2)
            ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,hover_background_color);
         //--- Иначе установим обычный цвет
         else
            ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,background_color);
        }
     }
  }

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

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

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

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

Напишем две функции: InitializeButtonStates() и ChangeButtonColorOnClick(). В функции InitializeButtonStates() будет производиться проверка, было ли нажатие на кнопке, учитывая префикс в имени. Если нажатие было на кнопке, то в цикле инициализируется массив состояний кнопок (button_states) и функция возвращает true.

//+------------------------------------------------------------------+
//| Инициализирует состояния кнопок при нажатии                      |
//+------------------------------------------------------------------+
bool InitializeButtonStates(string clicked_object)
  {
//--- Получим номер подокна индикатора
   subwindow_number=ChartWindowFind(0,subwindow_shortname);
//--- Если кликнули на кнопке и она находится в подокне индикатора
   if(ObjectFind(0,clicked_object)==subwindow_number && StringFind(clicked_object,prefix+"button_",0)>=0)
     {
      //--- Определим нажатую кнопку
      for(int i=0; i<BUTTON_COLUMNS; i++)
        {
         for(int j=0; j<BUTTON_ROWS; j++)
           {
            //--- Определим состояние всех кнопок
            if(clicked_object==button_object_names[j][i])
               button_states[j][i]=true;
            else
               button_states[j][i]=false;
           }
        }
      //---
      return(true);
     }
//---
   return(false);
  }

После этого в функции ChangeButtonColorOnClick() цвета кнопкам устанавливаются согласно значениям массива button_states.

//+------------------------------------------------------------------+
//| Изменяет цвет кнопки при нажатии                                 |
//+------------------------------------------------------------------+
void ChangeButtonColorOnClick()
  {
   for(int i=0; i<BUTTON_COLUMNS; i++)
     {
      for(int j=0; j<BUTTON_ROWS; j++)
        {
         //--- Если эта кнопка нажата, установим ей цвет отличия
         if(button_states[j][i])
            ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,clicked_background_color);
         //--- Для ненажатой кнопки установим обычный цвет
         else
            ObjectSetInteger(0,button_object_names[j][i],OBJPROP_BGCOLOR,background_color);
        }
     }
  }

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

//--- Отслеживание нажатий на графическом объекте левой кнопкой мыши
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Если кликнули по кнопке
      if(InitializeButtonStates(sparam))
        {
         //--- Установим цвета для кнопок
         ChangeButtonColorOnClick();
        }
      //--- Обновим график
      ChartRedraw();
      return;
     }

Теперь при нажатии на кнопку ее цвет будет изменяться.

Осталось учесть еще некоторые нюансы. В функции 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<BUTTON_COLUMNS; i++)
      for(int j=0; j<BUTTON_ROWS; j++)
         DeleteObjectByName(button_object_names[j][i]);
  }
//+------------------------------------------------------------------+
//| Удаляет объект по имени                                          |
//+------------------------------------------------------------------+
void DeleteObjectByName(string object_name)
  {
//--- Если есть такой объект
   if(ObjectFind(0,object_name)>=0)
     {
      //--- Если была ошибка при удалении, сообщим об этом
      if(!ObjectDelete(0,object_name))
         Print("Ошибка ("+IntegerToString(GetLastError())+") при удалении объекта!");
     }
  }

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

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

  }

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

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

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

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

 

Заключение

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

Прикрепленные файлы |
testbuttons.mq5 (20.4 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (79)
Rashid Umarov
Rashid Umarov | 24 окт. 2013 в 12:25
C-4:
Разумеется, потому что этой информации нигде нет (оказывается есть, см. выше:))). Сам мучился с этой проблемой, пока не обратился в сервис деск. Там-то мне и подсказали, что изменения свойств объектов на чарте происходит асинхронно. А судя по Вашему примеру это касается свойств и самого чарта.

Еще в разделе Графические объекты :

Группа функций, предназначенных для работы с графическими объектами, относящимися к любому указанному графику.

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

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

Andrey Khatimlianskii
Andrey Khatimlianskii | 24 окт. 2013 в 14:49
Rosh:

Еще в разделе Графические объекты :

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

Мне мое предложение больше не повторять? Или в сервис-деск оформить?

VictorD
VictorD | 12 янв. 2015 в 04:53
Автору - глубокая благодарность за прекрасную статью. 
Oleksandr Skrynditsa
Oleksandr Skrynditsa | 20 апр. 2019 в 18:36
Доброго времени суток. Вижу что последнее сообщение давно было но все же спрошу.

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

Вот собственно сам вопрос: как сделать так чтоб график не затрагивался?
Artyom Trishkin
Artyom Trishkin | 20 апр. 2019 в 19:19
Oleksandr Skrynditsa:
Доброго времени суток. Вижу что последнее сообщение давно было но все же спрошу.

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

Вот собственно сам вопрос: как сделать так чтоб график не затрагивался?

CHART_MOUSE_SCROLL

Удивите ваших MQL5-клиентов эффективным коктейлем технологий! Удивите ваших MQL5-клиентов эффективным коктейлем технологий!
MQL5 предоставляет программистам полный набор функций и объектно-ориентированный API, благодаря которым они могут делать в среде MetaTrader все что угодно. Тем не менее, веб-технологии – это очень универсальный инструмент, который может помочь в ситуациях, когда вам нужно создать нечто совершенно особое, вы хотите удивить ваших клиентов или у вас просто нет времени на изучение определенной части стандартной библиотеки MQL5. В данной статье вы узнаете, как можно управлять временем разработки при создании вашего уникального коктейля технологий.
Рецепты MQL5 - Наблюдение за несколькими таймфреймами в одном окне Рецепты MQL5 - Наблюдение за несколькими таймфреймами в одном окне
MetaTrader 5 предлагает на выбор 21 таймфрейм для анализа. На график можно также поместить специальный объект-график и уже в нем задать символ, таймфрейм и еще некоторые свойства. В этой статье рассмотрим такие графические объекты более подробно: создадим индикатор с элементами управления (кнопками), с помощью которых можно будет устанавливать в подокно сразу несколько объектов-графиков. При этом объекты-графики будут точно вписываться и автоматически подстраиваться под размер подокна при изменении размеров главного окна графика или терминала.
Рецепты MQL5 - Элементы управления в подокне индикатора - Полоса прокрутки Рецепты MQL5 - Элементы управления в подокне индикатора - Полоса прокрутки
Продолжим изучение элементов управления и на этот раз рассмотрим полосу прокрутки (scrollbar). Так же, как и в предыдущей статье "Рецепты MQL5 - Элементы управления в подокне индикатора - Кнопки", будем работать в подокне индикатора. Упомянутую статью необходимо изучить, так как в ней подробно изложена работа с событиями в функции OnChartEvent(), а здесь о них будет только поверхностное упоминание. В этой статье в качестве примера мы создадим вертикальную полосу прокрутки для большого списка всех показателей финансового инструмента, которые возможно получить средствами MQL5.
Технические индикаторы как цифровые фильтры Технические индикаторы как цифровые фильтры
В данной статье технические индикаторы рассматриваются как цифровые фильтры. Объясняется принцип работы и основные характеристики цифровых фильтров. Рассматриваются практические способы получения ядра фильтра в терминале MetaTrader 5 и интеграция с готовым анализатором спектра, предложенным в статье "Строим анализатор спектра". В качестве примеров приведены импульсные и спектральные характеристики типичных цифровых фильтров.