Элементы управления в подокне индикатора. Полоса прокрутки (скроллинг).

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

До этого на этом сайте, во всех статьях по программированию на MQL5, при построении списков использовался графический объект OBJ_LABEL (текстовая метка). Но на этот раз будем использовать канву для отображения текста в ней. Удобство этого метода в том, что вместо множества объектов OBJ_LABEL будет использоваться только один - OBJ_BITMAP_LABEL (графическая метка). На канве можно рисовать все элементы интерфейса, но в этот раз мы ограничимся только выводом текста.

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

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

//---
#define SYMBOL_LIST_SIZE 71 // Количество строк в списке показателей символа
//--- Подключим класс для работы с канвой
#include <Canvas\Canvas.mqh>
//--- Загрузка класса
CCanvas canvas;
//+------------------------------------------------------------------+
//| Глобальные переменные                                            |
//+------------------------------------------------------------------+
//--- Свойства подокна индикатора
int               number_subwindow      =WRONG_VALUE;               // Номер подокна
int               subwindow_height      =0;                         // Высота подокна
string            shortname_subwindow   ="TestScroll";              // Короткое имя индикатора
string            prefix                =shortname_subwindow+"_";   // Префикс для объектов
int               chart_width           =0;                         // Ширина графика
int               chart_y_distance      =0;                         // Дистанция от верха графика до подокна
//--- Свойства канвы
string            canvas_name           =prefix+"canvas";           // Название канвы
color             canvas_bg_color       =C'20,20,20';               // Цвет фона канвы
ENUM_COLOR_FORMAT clr_format            =COLOR_FORMAT_XRGB_NOALPHA; // Компонент альфа-канала игнорируется
//--- Свойства списка
int               list_h                =0;                         // Высота списка
int               text_h                =0;                         // Высота текста
int               font_size             =15;                        // Размер шрифта
string            font_name             ="Calibri";                 // Шрифт
double            string_size_percent   =100/SYMBOL_LIST_SIZE;      // Размер одной строки списка в процентах
//--- Свойства скролла
string            scroll_name           =prefix+"scroll";           // Имя каретки скролла
int               scroll_x              =0;                         // Координата скролла x 
int               scroll_y              =0;                         // Координата скролла y 
int               scroll_x2             =0;                         // Координата скролла x2 
int               scroll_y2             =0;                         // Координата скролла y2 
double            scroll_y_percent      =0.0;                       // Координата скролла Y в процентном выражении
int               scroll_w              =9;                         // Ширина скролла
int               scroll_h              =0;                         // Высота скролла
bool              scroll_state          =false;                     // Состояние скролла
string            scroll_bg_name        =prefix+"scroll_bg";        // Имя фона скролла
int               scroll_bg_w           =9;                         // Ширина фона скролла
int               scroll_point_fixing   =0;                         // Точка фиксации при нажатии
int               scroll_size_fixing    =0;                         // Расстояние от Y скролла до точки фиксации
color             scroll_color          =clrSilver;                 // Цвет скролла
color             scroll_color_hover    =clrDimGray;                // Цвет скролла при наведении курсора
color             scroll_color_push     =clrSlateGray;              // Цвет скролла при нажатии
//--- Состояние кнопки мыши (нажата/отжата)
bool              state_mouse_button=false;
//--- Массивы показателей символа
color             array_color[]; // Цвет значений
string            array_value[]; // Значения
//--- Название показателей текущего символа
string symbol_info_name[]=
  {
   "Количество сделок в текущей сессии",
   "Общее число ордеров на покупку в текущий момент",
   "Общее число ордеров на продажу в текущий момент",
   "Volume - объем в последней сделке",
   "Максимальный Volume за день",
   "Минимальный Volume за день",
   "Время последней котировки",
   "Количество знаков после запятой",
   "Размер спреда в пунктах",
   "Признак плавающего спреда",
   "Максимальное количество показываемых заявок в стакане",
   "Способ вычисления стоимости контракта",
   "Тип исполнения ордеров",
   "Дата начала торгов по инструменту (обычно используется для фьючерсов)",
   "Дата окончания торгов по инструменту (обычно используется для фьючерсов)",
   "Минимальный отступ в пунктах от текущей цены закрытия для установки Stop ордеров",
   "Дистанция заморозки торговых операций (в пунктах)",
   "Режим заключения сделок",
   "Модель расчета свопа",
   "День недели для начисления тройного свопа",
   "Флаги разрешенных режимов истечения ордера",
   "Флаги разрешенных режимов заливки ордера",
//---
   "Bid - лучшее предложение на продажу",
   "Максимальный Bid за день",
   "Минимальный Bid за день",
   "Ask - лучшее предложение на покупку",
   "Максимальный Ask за день",
   "Минимальный Ask за день",
   "Цена, по которой совершена последняя сделка",
   "Максимальный Last за день",
   "Минимальный Last за день",
   "Значение одного пункта",
   "Рассчитанная стоимость тика для прибыльной позиции",
   "Рассчитанная стоимость тика для убыточной позиции",
   "Минимальное изменение цены",
   "Размер торгового контракта",
   "Минимальный объем для заключения сделки",
   "Максимальный объем для заключения сделки",
   "Минимальный шаг изменения объема для заключения сделки",
   "Максимально допустимый совокупный объем открытой позиции и отложенных ордеров  в одном направлении",
   "Значение свопа в покупку",
   "Значение свопа в продажу",
   "Начальная маржа - размер необходимых залоговых средств в маржинальной валюте для открытия позиции (1 лот)",
   "Поддерживающая маржа по инструменту",
   "Коэффициент взимания маржи по длинным позициям",
   "Коэффициент взимания маржи по коротким позициям",
   "Коэффициент взимания маржи по Limit ордерам",
   "Коэффициент взимания маржи по Stop ордерам",
   "Коэффициент взимания маржи по Stop Limit ордерам",
   "Суммарный объём сделок в текущую сессию",
   "Суммарный оборот в текущую сессию",
   "Суммарный объём открытых позиций",
   "Общий объём ордеров на покупку в текущий момент",
   "Общий объём ордеров на продажу в текущий момент",
   "Цена открытия сессии",
   "Цена закрытия сессии",
   "Средневзвешенная цена сессии",
   "Цена поставки на текущую сессию",
   "Минимально допустимое значение цены на сессию",
   "Максимально допустимое значение цены на сессию",
//---
   "Базовая валюта инструмента",
   "Валюта прибыли",
   "Валюта в которой вычисляется залоговые средства",
   "Источник текущей котировки",
   "Строковое описание символа",
   "Имя торгового символа в системе международных идентификационных кодов ценных бумаг (ISIN)",
   "Путь в дереве символов",
//---
   "Количество баров по символу-периоду на данный момент",
   "Самая первая дата по символу-периоду на данный момент",
   "Самая первая дата в истории по символу на сервере",
   "Данные по символу синхронизированы"
  };
//---

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

Для создания канвы воспользуемся методом CreateBitmapLabel() класса CCanvas. Напишем функцию SetCanvas():

//+------------------------------------------------------------------+
//| Установить канву                                                 |
//+------------------------------------------------------------------+
void SetCanvas()
  {
//--- Если канвы нет, установим её
   if(ObjectFind(0,canvas_name)<0)
      canvas.CreateBitmapLabel(0,number_subwindow,canvas_name,0,0,chart_width,subwindow_height,clr_format);
  }
//---

Также понадобится метод для изменения размеров канвы, чтобы подгонять её под размеры подокна индикатора. Для этого в классе CCanvas есть метод Resize(). А функция ResizeCanvas() с использованием этого метода будет выглядеть так, как это показано ниже:

//+------------------------------------------------------------------+
//| Изменяет размер канвы                                            |
//+------------------------------------------------------------------+
void ResizeCanvas()
  {
//--- Если канва есть в подокне индикатора, установим новый размер
   if(ObjectFind(0,canvas_name)==number_subwindow)
      canvas.Resize(chart_width,subwindow_height);
//--- Если канвы нет, установим её
   else
      canvas.CreateBitmapLabel(0,number_subwindow,canvas_name,0,0,chart_width,subwindow_height,clr_format);
  }
//---

Чтобы удалить канву нужно использовать метод Destroy():

//+------------------------------------------------------------------+
//| Удаляет канву                                                    |
//+------------------------------------------------------------------+
void DeleteCanvas()
  {
//--- Если канва есть, удалим её
   if(ObjectFind(0,canvas_name)>0)
      canvas.Destroy();
  }
//---

Из класса CCanvas в этой статье ещё будем использовать методы FontSet() для установки шрифта, TextHeight() для определения высоты текста, TextOut() для вывода текста на канву, Erase() для очистки канвы и Update() для перерисовки. Далее в статье будет показано, где в программе используются перечисленные методы.

Во время инициализации в функции OnInit() нужно подготовить программу для работы. Ниже в коде показано, что нужно сделать. Комментарии в каждой строке помогут разобраться. Методы FontSet() и TextHeight() класса CCanvas используются в этой программе только в этой части.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Включим слежение за событиями мыши
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Установим короткое имя
   IndicatorSetString(INDICATOR_SHORTNAME,shortname_subwindow);
//--- Установим размеры массивам показателей символа и их цвета
   ArrayResize(array_color,SYMBOL_LIST_SIZE);
   ArrayResize(array_value,SYMBOL_LIST_SIZE);
//--- Получим свойства подокна
   SetSubwindowProperties();
//--- Установим шрифт для отображения в канве
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Запомним размер (высоту) текста для расчётов
   text_h=canvas.TextHeight("A")-1;
//--- Посчитаем высоту всего списка
   list_h=text_h*SYMBOL_LIST_SIZE;
//--- Создадим канву
   SetCanvas();
//--- Отобразим список показателей символа
   ShowSymbolInfo();
//--- Обновим график
   ChartRedraw();
//--- Всё прошло успешно
   return(INIT_SUCCEEDED);
  }
//---

Функция SetSubwindowProperties() такая же, как и в предыдущей статье, в ней в глобальные переменные присваиваются значения номера подокна индикатора и его размеры. Перейдём сразу к функции ShowSymbolInfo(), ниже представлен её код:

//+------------------------------------------------------------------+
//| Отображает информацию текущего символа                           |
//+------------------------------------------------------------------+
void ShowSymbolInfo(double scroll_position=0.0)
  {
   int    limit_list    =0;   // Счётчик нанесённых на канву строк
   double top_list      =0.0; // Счётчик для определения начала списка
   int    y_distance    =0;   // Для определения координаты следующей строки в списке
   int    string_number =0;   // Для определения строки, с которой отображать список
//--- Определим строку, с которой будет отображаться список
   for(int i=0; i<SYMBOL_LIST_SIZE; i++)
     {
      //--- Считаем строки пока не дойдём до той,
      //    с которой нужно отобразить список
      if(top_list>=scroll_position)
         break;
      //---
      top_list+=string_size_percent;
      string_number++;
     }
//--- Инициализируем массивы списка с указанной строки
   InitArraysSymbolList(string_number);
//--- Очистим канву
   canvas.Erase(canvas_bg_color);
//--- Нанесём на канву список
   for(int i=string_number; i<SYMBOL_LIST_SIZE; i++)
     {
      //--- Название показателя
      canvas.TextOut(655,y_distance,symbol_info_name[i]+" :",ColorToARGB(clrWhite),TA_RIGHT|TA_TOP);
      //--- Значение показателя
      canvas.TextOut(665,y_distance,array_value[i],ColorToARGB(array_color[i]),TA_LEFT|TA_TOP);
      //--- Расcчитаем координату для следующей строки
      y_distance+=text_h;
      //--- Посчитаем кол-во нанесённых строк
      limit_list++;
      //--- Если выходим за пределы подокна, остановим цикл
      if(limit_list*text_h>subwindow_height)
         break;
     }
//--- Обновим канву
   canvas.Update();
  }
//---

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

В самом начале функции  ShowSymbolInfo() в цикле производится определение, с какой строки нужно отобразить список. Затем в функции InitArraysSymbolList() инициализируются массивы значений и их цвета (выделенная строка в коде выше). Инициализация производится с указанной строки, которая была определена в предыдущем цикле. После этого с помощью метода Erase() канва очищается, по сути закрашивается полностью указанным цветом. И в последнем цикле осуществляется нанесение текста на канву используя метод TextOut(). В конце функции ShowSymbolInfo() канва обновляется с помощью метода Update().

Ниже представлен код функции InitArraysSymbolList():

//+------------------------------------------------------------------+
//| Инициализация массивов показателей символа и их цвета            |
//+------------------------------------------------------------------+
void InitArraysSymbolList(int string_number)
  {
   int limit_list=0;
//---
   for(int i=string_number; i<SYMBOL_LIST_SIZE; i++)
     {
      //--- Определим значение и цвет показателя символа
      array_value[i]=GetStringSymbolInfoByIndex(i);
      array_color[i]=GetColorSymbolInfoByIndex(i);
      //--- Увеличим счётчик
      limit_list++;
      //--- Если по количеству строк превысили высоту подокна, выйдем
      if(limit_list*text_h>subwindow_height)
         break;
     }
  }
//---

В коде выше видно, что значения показателей символа и их цвета определяется с помощью функций GetStringSymbolInfoByIndex() и GetColorSymbolInfoByIndex().

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

//+------------------------------------------------------------------+
//| Возвращает строку показателя символа по индексу                  |
//+------------------------------------------------------------------+
string GetStringSymbolInfoByIndex(int index)
  {
   string str           ="-";
   long   l_check_value =0;
   double d_check_value =0.0;
   string s_check_value ="";
//---
   switch(index)
     {
      case 0  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_DEALS);
         str=(l_check_value==0)?"-":IntegerToString(l_check_value);                                      break;
      case 1  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_BUY_ORDERS);
         str=(l_check_value==0)?"-":IntegerToString(l_check_value);                                      break;
      case 2  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_SELL_ORDERS);
         str=(l_check_value==0)?"-":IntegerToString(l_check_value);                                      break;
      case 3  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUME);
         str=(l_check_value==0)?"-":IntegerToString(l_check_value);                                      break;
      case 4  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMEHIGH);
         str=(l_check_value==0)?"-":IntegerToString(l_check_value);                                      break;
      case 5  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMELOW);
         str=(l_check_value==0)?"-":IntegerToString(l_check_value);                                      break;
      case 6  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TIME);
         str=(l_check_value==0)?"-":TimeToString(l_check_value);                                         break;
      case 7  : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_DIGITS));                           break;
      case 8  : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD));                           break;
      case 9  : str=(!SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT))?"false":"true";                    break;
      case 10 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TICKS_BOOKDEPTH);
         str=(l_check_value==0)?"-":DoubleToString(l_check_value,_Digits);                               break;
      case 11 : str=GetStringTradeCalcMode(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_CALC_MODE));           break;
      case 12 : str=GetStringTradeMode(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_MODE));                    break;
      case 13 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_START_TIME);
         str=(l_check_value==0)?"-":TimeToString(l_check_value);                                         break;
      case 14 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME);
         str=(l_check_value==0)?"-":TimeToString(l_check_value);                                         break;
      case 15 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         str=(l_check_value==0)?"false":IntegerToString(l_check_value);                                  break;
      case 16 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL);
         str=(l_check_value==0)?"false":IntegerToString(l_check_value);                                  break;
      case 17 : str=GetStringTradeExeMode(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE));              break;
      case 18 : str=GetStringSwapMode(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_MODE));                      break;
      case 19 : str=GetStringWeekday(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_ROLLOVER3DAYS));              break;
      case 20 : str=GetStringExpirationMode();                                                           break;
      case 21 : str=GetStringFillingMode();                                                              break;
      //---
      case 22 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BID);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 23 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDHIGH);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 24 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDLOW);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 25 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 26 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKHIGH);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 27 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKLOW);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 28 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LAST);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 29 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTHIGH);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 30 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTLOW);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,_Digits);                               break;
      case 31 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_POINT),_Digits);                      break;
      case 32 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT),2);          break;
      case 33 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_LOSS),2);            break;
      case 34 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE),_Digits);            break;
      case 35 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE),2);              break;
      case 36 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),2);                       break;
      case 37 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX),2);                       break;
      case 38 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP),2);                      break;
      case 39 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         str=(d_check_value==0)?"Unlimited":DoubleToString(d_check_value,2);                             break;
      case 40 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SWAP_LONG);
         str=(d_check_value==0)?"false":DoubleToString(d_check_value,2);                                 break;
      case 41 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SWAP_SHORT);
         str=(d_check_value==0)?"false":DoubleToString(d_check_value,2);                                 break;
      case 42 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_INITIAL);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 43 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_MAINTENANCE);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 44 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_LONG);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 45 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_SHORT);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 46 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_LIMIT);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 47 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_STOP);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 48 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_STOPLIMIT);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 49 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_VOLUME);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 50 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_TURNOVER);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 51 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_INTEREST);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 52 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_BUY_ORDERS_VOLUME);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 53 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_SELL_ORDERS_VOLUME);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 54 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_OPEN);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 55 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_CLOSE);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 56 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_AW);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 57 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_SETTLEMENT);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 58 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_LIMIT_MIN);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
      case 59 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_LIMIT_MAX);
         str=(d_check_value==0)?"-":DoubleToString(d_check_value,2);                                     break;
         //---
      case 60 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE);                                      break;
      case 61 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT);                                    break;
      case 62 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN);                                    break;
      case 63 :
         s_check_value=SymbolInfoString(_Symbol,SYMBOL_BANK);
         str=(s_check_value!="")?s_check_value:"-";                                                      break;
      case 64 : str=SymbolInfoString(_Symbol,SYMBOL_DESCRIPTION);                                        break;
      case 65 :
         s_check_value=SymbolInfoString(_Symbol,SYMBOL_ISIN);
         str=(s_check_value!="")?s_check_value:"-";                                                      break;
      case 66 : str=SymbolInfoString(_Symbol,SYMBOL_PATH);                                               break;
      //---
      case 67 : str=IntegerToString(SeriesInfoInteger(_Symbol,_Period,SERIES_BARS_COUNT));               break;
      case 68 : str=TimeToString((datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE));         break;
      case 69 : str=TimeToString((datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE));  break;
      case 70 : str=(!(bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED))?"false":"true";      break;
     }
//---
   return(str);
  }
//---

С кодом в выделенных строках, в коде выше, можно ознакомиться далее. Функции GetStringTradeCalcMode(), GetStringTradeMode(), GetStringTradeExeMode(), GetStringSwapMode() и GetStringWeekday() просто возвращают готовые строки в зависимости от переданного значения.

//+------------------------------------------------------------------+
//| Возвращает строку о способе вычисления                           |
//| величины залоговых средств по инструменту                        |
//+------------------------------------------------------------------+
string GetStringTradeCalcMode(long mode)
  {
   string str="?";
//---
   switch((int)mode)
     {
      case SYMBOL_CALC_MODE_FOREX       :
         str="Forex mode";                 break;
      case SYMBOL_CALC_MODE_FUTURES     :
         str="Futures mode";               break;
      case SYMBOL_CALC_MODE_CFD         :
         str="CFD mode";                   break;
      case SYMBOL_CALC_MODE_CFDINDEX    :
         str="CFD index mode";             break;
      case SYMBOL_CALC_MODE_CFDLEVERAGE :
         str="CFD Leverage mode";          break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Возвращает строку режима торговли                                |
//+------------------------------------------------------------------+
string GetStringTradeMode(long mode)
  {
   string str="-";
//---
   switch((int)mode)
     {
      case SYMBOL_TRADE_MODE_DISABLED  :
         str="Торговля по символу запрещена";              break;
      case SYMBOL_TRADE_MODE_LONGONLY  :
         str="Разрешены только покупки";                   break;
      case SYMBOL_TRADE_MODE_SHORTONLY :
         str="Разрешены только продажи";                   break;
      case SYMBOL_TRADE_MODE_CLOSEONLY :
         str="Разрешены только операции закрытия позиций"; break;
      case SYMBOL_TRADE_MODE_FULL      :
         str="Нет ограничений на торговые операции";       break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Возвращает строку режима заключения сделок                       |
//+------------------------------------------------------------------+
string GetStringTradeExeMode(long mode)
  {
   string str="-";
//---
   switch((int)mode)
     {
      case SYMBOL_TRADE_EXECUTION_REQUEST  :
         str="Торговля по запросу (Request)";         break;
      case SYMBOL_TRADE_EXECUTION_INSTANT  :
         str="Торговля по потоковым ценам (Instant)"; break;
      case SYMBOL_TRADE_EXECUTION_MARKET   :
         str="Исполнение ордеров по рынку (Market)";  break;
      case SYMBOL_TRADE_EXECUTION_EXCHANGE :
         str="Биржевое исполнение (Exchange)";        break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Возвращает строку модель расчёта свопа                           |
//+------------------------------------------------------------------+
string GetStringSwapMode(long mode)
  {
   string str="-";
//---
   switch((int)mode)
     {
      case SYMBOL_SWAP_MODE_DISABLED         :
         str="Нет свопов";                                               break;
      case SYMBOL_SWAP_MODE_POINTS           :
         str="Начисляются в пунктах";                                    break;
      case SYMBOL_SWAP_MODE_CURRENCY_SYMBOL  :
         str="Начисляются в деньгах в базовой валюте символа";           break;
      case SYMBOL_SWAP_MODE_CURRENCY_MARGIN  :
         str="Начисляются в деньгах в маржинальной валюте символа";      break;
      case SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT :
         str="Начисляются в деньгах в валюте депозита клиента";          break;
      case SYMBOL_SWAP_MODE_INTEREST_CURRENT :
         str="Начисляются в годовых процентах от цены инструмента";      break;
      case SYMBOL_SWAP_MODE_INTEREST_OPEN    :
         str="Начисляются в годовых процентах от цены открытия позиции"; break;
      case SYMBOL_SWAP_MODE_REOPEN_CURRENT   :
         str="Начисляются переоткрытием позиции (close price +/-)";      break;
      case SYMBOL_SWAP_MODE_REOPEN_BID       :
         str="Начисляются переоткрытием позиции (bid price +/-)";        break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Возвращает строку дня недели                                     |
//+------------------------------------------------------------------+
string GetStringWeekday(long day)
  {
   string str="-";
//---
   switch((int)day)
     {
      case SUNDAY    :
         str="Воскресенье"; break;
      case MONDAY    :
         str="Понедельник"; break;
      case TUESDAY   :
         str="Вторник";     break;
      case WEDNESDAY :
         str="Среда";       break;
      case THURSDAY  :
         str="Четверг";     break;
      case FRIDAY    :
         str="Пятница";     break;
      case SATURDAY  :
         str="Суббота";     break;
     }
//---
   return(str);
  }
//---

В функциях GetStringExpirationMode() и GetStringFillingMode() строковое значение формируется в зависимости от того, какие режимы истечения ордера и заливки ордера доступны для текущего символа (см. код ниже). Так как наличие каждого режима нужно проверить отдельно, то для удобства используются вспомогательные функции: IsExpirationTypeAllowed() и IsFillingTypeAllowed().

//+------------------------------------------------------------------+
//| Возвращает строку режимов истечения ордера                       |
//+------------------------------------------------------------------+
string GetStringExpirationMode()
  {
   string str           ="";    // Для формирования строки
//--- Переменные для проверки режимов
   bool   gtc           =false; // Ордер действителен неограниченно по времени до явной его отмены
   bool   day           =false; // Ордер действителен до конца дня
   bool   specified     =false; // Срок истечения указывается в ордере
   bool   specified_day =false; // День истечения указывается в ордере
//--- Проверим режимы
   gtc           =IsExpirationTypeAllowed(SYMBOL_EXPIRATION_GTC);
   day           =IsExpirationTypeAllowed(SYMBOL_EXPIRATION_DAY);
   specified     =IsExpirationTypeAllowed(SYMBOL_EXPIRATION_SPECIFIED);
   specified_day =IsExpirationTypeAllowed(SYMBOL_EXPIRATION_SPECIFIED_DAY);
//--- Сформируем строку доступных режимов
   if(gtc)
     {
      StringAdd(str,"GTC");
      if(day || specified || specified_day)
         StringAdd(str," / ");
     }
//---
   if(day)
     {
      StringAdd(str,"DAY");
      if(specified || specified_day)
         StringAdd(str," / ");
     }
//---
   if(specified)
     {
      StringAdd(str,"SPECIFIED");
      if(specified_day)
         StringAdd(str," / ");
     }
//---
   if(specified_day)
      StringAdd(str,"SPECIFIED DAY");
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Возвращает строку режимов заливки ордера                         |
//+------------------------------------------------------------------+
string GetStringFillingMode()
  {
//--- Для формирования строки
   string str            ="";
//--- Переменные для проверки режимов
//    Указано "Все или ничего", 
//    если необходимый объем в ордере по указанной цене не набирается,
//    то ордер отменяется и сделка не проводится
   bool   aon            =false; 
//--- Если по указанной в ордере цене сделку можно заполнить только частично, 
//    то совершается сделка на доступный объем. Остаток по ордеру снимается, 
//    новый ордер не выставляется
   bool   cancel_remaind =false;
//--- Совершается сделка по указанной в заявке цене в пределах доступного объема.
//    На остаток от заполнения выставляется новый ордер по той же цене
   bool   return_remaind =false;
//--- Проверим режимы
   aon            =IsFillingTypeAllowed(SYMBOL_FILLING_ALL_OR_NONE);
   cancel_remaind =IsFillingTypeAllowed(SYMBOL_CANCEL_REMAIND);
   return_remaind =IsFillingTypeAllowed(SYMBOL_RETURN_REMAIND);
//--- Сформируем строку доступных режимов
   if(aon)
     {
      StringAdd(str,"AON");
      if(cancel_remaind || return_remaind)
         StringAdd(str," / ");
     }
//---
   if(cancel_remaind)
     {
      StringAdd(str,"CANCEL");
      if(return_remaind)
         StringAdd(str," / ");
     }
//---
   if(return_remaind)
      StringAdd(str,"RETURN;");
//---
   return(str);
  }
//---

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

//+------------------------------------------------------------------+
//| Проверяет разрешённость указанного режима экспирации             |
//+------------------------------------------------------------------+
bool IsExpirationTypeAllowed(int exp_type)
  {
//--- Получим значение свойства, описывающего допустимые режимы истечения срока действия
   int expiration=(int)SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_MODE);
//--- Вернем true, если режим exp_type разрешен
   return((expiration&exp_type)==exp_type);
  }
//---

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

//+------------------------------------------------------------------+
//| Проверяет разрешённость указанного режима заполнения             |
//+------------------------------------------------------------------+
bool IsFillingTypeAllowed(int fill_type)
  {
//--- Получим значение свойства, описывающего режим заполнения
   int filling=(int)SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE);
//--- Вернём true, если режим fill_type разрешен
   return((filling&fill_type)==fill_type);
  }
//---

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

//+------------------------------------------------------------------+
//| Возвращает цвет показателя символа по индексу                    |
//+------------------------------------------------------------------+
color GetColorSymbolInfoByIndex(int index)
  {
   double check_value =0.0;
   color  clr         =clrWhiteSmoke;
//---
   switch(index)
     {
      case 6  :
         clr=(SymbolInfoInteger(_Symbol,SYMBOL_TIME)>0)?clrCornflowerBlue:clrWhiteSmoke;                     break;
         //---
      case 9  : clr=(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT)>0)?clrGold:clrRed;                       break;
      //---
      case 13 :
         clr=(SymbolInfoInteger(_Symbol,SYMBOL_START_TIME)>0)?clrCornflowerBlue:clrWhiteSmoke;               break;
      case 14 :
         clr=(SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME)>0)?clrCornflowerBlue:clrWhiteSmoke;          break;
         //---
      case 15 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)>0)?clrWhiteSmoke:clrRed;            break;
      case 16 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)>0)?clrWhiteSmoke:clrRed;           break;
      //---
      case 20 : clr=clrGold;                                                                                 break;
      case 21 : clr=clrGold;                                                                                 break;
      //---
      case 39 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT)>0)?clrWhiteSmoke:clrGold;                 break;
      case 40 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_SWAP_LONG)>0)?clrLime:clrRed;                           break;
      case 41 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_SWAP_SHORT)>0)?clrLime:clrRed;                          break;
      //---
      case 60 : clr=clrGold;                                                                                 break;
      case 61 : clr=clrGold;                                                                                 break;
      case 62 : clr=clrGold;                                                                                 break;
      //---
      case 68 :
         clr=(SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE)>0)?clrCornflowerBlue:clrWhiteSmoke;        break;
      case 69 :
         clr=(SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE)>0)?clrCornflowerBlue:clrWhiteSmoke; break;
      case 70 : clr=(!(bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED))?clrRed:clrGold;          break;
     }
//---
   return(clr);
  }
//---

Обратите внимание, что в функциях GetStringSymbolInfoByIndex() и GetColorSymbolInfoByIndex() используется условный оператор ?: (тернарный оператор). В этих случаях это удобнее, чем конструкция вида if else. Подробнее об этом можно прочитать в Справочном руководстве по языку MQL5 в разделе Основы языка / Операторы / Условный оператор ?: .

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

Загрузка индикатора на график без полосы прокрутки

И всё это один объект!

Далее напишем функции для работы с вертикальной полосой прокрутки. Как уже говорилось в начале статьи для создания скроллинга будем использовать два графических объекта типа OBJ_RECTANGLE_LABEL (прямоугольная метка). Один будет фоном, а второй полосой прокрутки (скроллом). Скроллинг будет размещаться в правой части подокна индикатора.

Функция для создания прямоугольной метки CreateRectangleLable():

//+------------------------------------------------------------------+
//| Создание прямоугольника                                          |
//+------------------------------------------------------------------+
void CreateRectangleLable(long              chart_id,         // id графика
                          int               number_window,    // номер окна
                          string            name_rect,        // имя объекта
                          int               x_dist,           // уровень цены
                          int               y_dist,           // цвет линии
                          int               x_size,           // ширина
                          int               y_size,           // высота
                          ENUM_BASE_CORNER  corner,           // угол графика
                          color             border,           // цвет рамки
                          color             background,       // цвет фона
                          bool              selectable,       // нельзя выделить объект, если FALSE
                          bool              back)             // фоновое расположение
  {
// Если объект создался успешно, то...
   if(ObjectCreate(chart_id,name_rect,OBJ_RECTANGLE_LABEL,number_window,0,0))
     {
      // ...установим его свойства
      ObjectSetInteger(chart_id,name_rect,OBJPROP_XDISTANCE,x_dist);        // установка координаты X
      ObjectSetInteger(chart_id,name_rect,OBJPROP_YDISTANCE,y_dist);        // установка координаты Y
      ObjectSetInteger(chart_id,name_rect,OBJPROP_XSIZE,x_size);            // установка ширины
      ObjectSetInteger(chart_id,name_rect,OBJPROP_YSIZE,y_size);            // установка высоты
      ObjectSetInteger(chart_id,name_rect,OBJPROP_BORDER_TYPE,BORDER_FLAT); // установим плоский вид
      ObjectSetInteger(chart_id,name_rect,OBJPROP_COLOR,border);            // установка цвета границы
      ObjectSetInteger(chart_id,name_rect,OBJPROP_CORNER,corner);           // установка угла привязки
      ObjectSetInteger(chart_id,name_rect,OBJPROP_BGCOLOR,background);      // установка цвета фона
      ObjectSetInteger(chart_id,name_rect,OBJPROP_SELECTABLE,selectable);   // нельзя выделить объект, если false
      ObjectSetInteger(chart_id,name_rect,OBJPROP_BACK,back);               // будет фоном, если true
      ObjectSetString(chart_id,name_rect,OBJPROP_TOOLTIP,"\n");             // нет всплывающей подсказки, если "\n"
     }
  }
//---

Создадим функции для создания и изменения размеров скролла и фона скролла: SetResizeScroll() и SetResizeScrollBackground():

//+------------------------------------------------------------------+
//| Устанавливает фон скролла или корректирует его размеры           |
//+------------------------------------------------------------------+
void SetResizeScrollBackground()
  {
//--- Если фон скролла уже есть на графике, скорректируем его настройки
   if(ObjectFind(0,scroll_bg_name)>0)
     {
      //--- Установим значения фона скролла
      ObjectSetInteger(0,scroll_bg_name,OBJPROP_YDISTANCE,0);
      ObjectSetInteger(0,scroll_bg_name,OBJPROP_XDISTANCE,chart_width-scroll_bg_w);
      ObjectSetInteger(0,scroll_bg_name,OBJPROP_YSIZE,subwindow_height);
     }
//--- Если фона нет, создадим его
   else
     {
      CreateRectangleLable(0,number_subwindow,scroll_bg_name,
                           chart_width-scroll_bg_w,0,scroll_bg_w,subwindow_height,
                           CORNER_LEFT_UPPER,C'50,50,50',C'50,50,50',false,false);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает скролл или корректирует его размеры                |
//+------------------------------------------------------------------+
void SetResizeScroll()
  {
//--- Рассчитаем размер скролла относительно высоты подокна
   CalculateScrollHeight();
//--- Если скролл уже есть на графике скорректируем его настройки
   if(ObjectFind(0,scroll_name)>0)
     {
      //--- Установим высоту и координату X
      ObjectSetInteger(0,scroll_name,OBJPROP_YSIZE,scroll_h);
      ObjectSetInteger(0,scroll_name,OBJPROP_XDISTANCE,chart_width-scroll_w);
      //--- Скорректируем положение скролла по оси Y,
      //    если выходим за пределы подокна вниз
      if(scroll_y+scroll_h>subwindow_height)
         ObjectSetInteger(0,scroll_name,OBJPROP_YDISTANCE,subwindow_height-scroll_h);
     }
//--- Создадим скролл, если его нет
   else
     {
      CreateRectangleLable(0,number_subwindow,scroll_name,
                           chart_width-scroll_w,0,scroll_w,scroll_h,
                           CORNER_LEFT_UPPER,clrSilver,clrSilver,false,false);
     }
  }
//---

В функции SetResizeScroll() в самом начале (выделенная строка выше) производится расчёт высоты скролла. Для этого используется функция CalculateScrollHeight():

//+------------------------------------------------------------------+
//| Рассчитывает размер скролла относительно высоты подокна          |
//+------------------------------------------------------------------+
void CalculateScrollHeight()
  {
//--- Если высота подокна больше размера списка
//    запомним размер подокна
   if(subwindow_height>=list_h)
      scroll_h=subwindow_height-1;
//--- Иначе рассчитаем размер скролла
   else
     {
      double scroll_h_temp=0.0;
      //--- Посчитаем размер скролла относительно высоты подокна
      scroll_h_temp=subwindow_height-(((double)subwindow_height/100)*(100-((double)subwindow_height/list_h)*100));
      //--- Если рассчитанное значение меньше 25 процентов
      //    от высоты подокна, установим размер 25 процентов
      if(scroll_h_temp/subwindow_height<0.25)
         scroll_h_temp=subwindow_height/4;
      //--- Запомним в глобальную переменную
      scroll_h=(int)scroll_h_temp;
     }
  }
//---

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

//+------------------------------------------------------------------+
//| Удалить скролл                                                   |
//+------------------------------------------------------------------+
void DeleteScroll()
  {
   DeleteObjectByName(scroll_name);
   DeleteObjectByName(scroll_bg_name);
  }
//+------------------------------------------------------------------+
//| Удаляет объект по имени                                          |
//+------------------------------------------------------------------+
void DeleteObjectByName(string Name)
  {
//--- Если есть такой объект
   if(ObjectFind(0,Name)>=0)
     {
      //--- Если была ошибка при удалении, сообщим об этом
      if(!ObjectDelete(0,Name))
         Print("Ошибка ("+IntegerToString(GetLastError())+") при удалении объекта!");
     }
  }
//---

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

Ширина полосы скролла довольно узкая и при его перемещении вверх/вниз будут моменты, когда курсор смещается. Это создаёт неудобство и нужно сделать так, чтобы если нажатие было произведено на скролле, то даже если произошло смещение, управление остаётся у скролла пока кнопка мыши нажата.

Ниже представлен код функций, о которых шла речь выше:

//+------------------------------------------------------------------+
//| Устанавливает цвет скроллу                                       |
//+------------------------------------------------------------------+
void SetScrollColor(color set_color)
  {
   ObjectSetInteger(0,scroll_name,OBJPROP_COLOR,set_color);
   ObjectSetInteger(0,scroll_name,OBJPROP_BGCOLOR,set_color);
  }
//+------------------------------------------------------------------+
//| Устанавливает границы скролла                                    |
//+------------------------------------------------------------------+
void SetScrollBoundaries()
  {
   scroll_x=(int)ObjectGetInteger(0,scroll_name,OBJPROP_XDISTANCE);
   scroll_y=(int)ObjectGetInteger(0,scroll_name,OBJPROP_YDISTANCE);
   scroll_x2=scroll_x+scroll_w;
   scroll_y2=scroll_y+scroll_h;
  }
//+------------------------------------------------------------------+
//| Изменение цвета скролла при наведении курсора мыши               |
//+------------------------------------------------------------------+
void ChangeScrollColorHover(int x,int y)
  {
//--- Если курсор мыши в зоне скролла,
//    сделаем цвет скролла темнее
   if(x>scroll_x && x<scroll_x2 && y>scroll_y && y<scroll_y2)
      SetScrollColor(scroll_color_hover);
//--- Если курсор вне зоны скролла
   else
     {
      //--- Если кнопка мыши отжата,
      //    установим цвет скролла светлее
      if(!state_mouse_button)
         SetScrollColor(scroll_color);
     }
  }
//+------------------------------------------------------------------+
//| Определяет состояние скролла                                     |
//+------------------------------------------------------------------+
void ScrollState(int x,int y)
  {
//--- Если курсор в зоне скролла
   if(x>scroll_x && x<scroll_x2 && y>scroll_y && y<scroll_y2)
     {
      //--- Если кнопка мыши нажата на скролле, запомним это
      if(state_mouse_button)
         scroll_state=true;
     }
//--- Если курсор вне зоны скролла
   else
     {
      //--- Если кнопка мыши отжата,
      //    отключим управление скролла
      if(!state_mouse_button)
         ZeroScrollVariables();
     }
  }
//+------------------------------------------------------------------+
//| Обнуление переменных связанных с перемещением скролла            |
//+------------------------------------------------------------------+
void ZeroScrollVariables()
  {
   scroll_state        =false;
   scroll_size_fixing  =0;
   scroll_point_fixing =0;
  }
//---

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

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

Ниже представлены функции Scrolling(), UpdateListAndScroll() и YToPercent(), с помощью которых как раз и осуществляют вышеописанные действия.

//+------------------------------------------------------------------+
//| Перемещение скролла                                              |
//+------------------------------------------------------------------+
void Scrolling(int y)
  {
   int  threshold   =1; // Порог в пикселях для перерасчёта
   int  new_y_point =0; // Новая координата Y
//--- Если кнопка мыши нажата
   if(state_mouse_button)
     {
      //--- Установим цвет нажатого скролла
      SetScrollColor(scroll_color_push);
      //--- Запомним текущую координату Y курсора
      if(scroll_point_fixing==0)
         scroll_point_fixing=y;
      //--- Запомним расстояние от верха скролла до курсора
      if(scroll_size_fixing==0)
         scroll_size_fixing=scroll_y-scroll_point_fixing;
     }
//--- Если в нажатом состоянии прошли порог вниз
   if(y-scroll_point_fixing>=threshold)
     {
      //--- Если не выходим за пределы подокна вниз
      if(scroll_y+scroll_h+threshold<subwindow_height)
         new_y_point=y+scroll_size_fixing;
      else
        {
         scroll_size_fixing=0;
         new_y_point=int(subwindow_height-scroll_h)-1;
        }
      //--- Обновим список и скролл
      UpdateListAndScroll(new_y_point);
      return;
     }
//--- Если в нажатом состоянии прошли порог вверх
   if(y-scroll_point_fixing<=-(threshold))
     {
      //--- Если не выходим за пределы подокна вверх 
      if(y-fabs(scroll_size_fixing)>=0)
         new_y_point=y-fabs(scroll_size_fixing);
      else
        {
         new_y_point=0;
         scroll_size_fixing=0;
        }
      //--- Обновим список и скролл
      UpdateListAndScroll(new_y_point);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Обновление списка и положения скролла                            |
//+------------------------------------------------------------------+
void UpdateListAndScroll(int new_point)
  {
//--- Установим новую координату скроллу
   ObjectSetInteger(0,scroll_name,OBJPROP_YDISTANCE,new_point);
//--- Обновим список относительно скролла
   ShowSymbolInfo(YToPercent(new_point));
//--- Обнулим точку фиксации
   scroll_point_fixing=0;
  }
//+------------------------------------------------------------------+
//| Преобразует координату Y в процент                               |
//+------------------------------------------------------------------+
double YToPercent(long y)
  {
   if(subwindow_height<=0)
      subwindow_height=1;
//---
   return(((double)y/subwindow_height)*100);
  }
//---

Теперь все функции нужно расставить по своим местам, чтобы программа заработала так, как было задумано. В функции OnChartEvent() нужно разместить функции, как это показано ниже:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Отслеживание клика мыши на графике
   if(id==CHARTEVENT_CLICK)
     {
      ZeroScrollVariables();
      //--- Обновим график
      ChartRedraw(); return;
     }
//--- Отслеживание движения мыши и состояния левой кнопки мыши
   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

      //--- Получим свойства подокна
      SetSubwindowProperties();
      //--- Проверим и запомним состояние кнопки мыши
      CheckMouseButtonState(sparam);
      //--- Получим местоположение курсора
      if(ChartXYToTimePrice(0,x,y,window,time,level))
        {
         //--- Если курсор в зоне подокна
         if(window==number_subwindow)
           {
            //--- Отключим скролл графика
            ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
            //--- Преобразует координату Y в относительную
            YToRelative(y);
            //--- Определим границы скролла
            SetScrollBoundaries();
            //--- Изменим цвет скролла при наведении
            ChangeScrollColorHover(x,y);
            //--- Определим состояние скролла
            ScrollState(x,y);
            //--- Если управление передано скроллу, определим положение в списке
            if(scroll_state)
               Scrolling(y);
           }
         //--- Если курсор вне зоны подокна
         else
           {
            //--- Включим управление скролла графика
            ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
            //--- Если управление не у скролла, установим цвет
            if(!scroll_state)
               SetScrollColor(scroll_color);
           }
        }
      //--- Если местоположение курсора не определено
      else
        {
         //--- Если управление не у скролла, установим цвет
         if(!scroll_state)
            SetScrollColor(scroll_color);
        }
      //--- Обновим график
      ChartRedraw(); return;
     }
//--- Отслеживает события изменения свойств и размеров графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Получим свойства подокна
      SetSubwindowProperties();
      //--- Получим координату Y скролла
      scroll_y=(int)ObjectGetInteger(0,scroll_name,OBJPROP_YDISTANCE);
      //--- Если размер подокна равен нулю, выйдем
      if(subwindow_height<=0)
         return;
      //--- Установим новый размер канве
      ResizeCanvas();
      //--- Обновить фон скролла
      SetResizeScrollBackground();
      //--- Обновить скролл
      SetResizeScroll();
      //--- Обновим данные в канве
      ShowSymbolInfo(YToPercent(scroll_y));
      //---
      return;
     }
  }
//---

C функциями, которые выделены в коде выше можно ознакомиться ниже. Комментарии в коде подскажут, для чего они предназначены.

//+------------------------------------------------------------------+
//| Проверяет состояние кнопки мыши                                  |
//+------------------------------------------------------------------+
void CheckMouseButtonState(string state)
  {
//--- Определим состояние кнопки мыши
//    Если нажата
   if(state=="1")
      state_mouse_button=true;
//--- Если отжата
   if(state=="0")
     {
      //--- Обнулим переменные
      ZeroScrollVariables();
      state_mouse_button=false;
     }
  }
//+------------------------------------------------------------------+
//| Преобразует координату Y в относительную                         |
//+------------------------------------------------------------------+
void YToRelative(int &y)
  {
//--- Получим расстояние от верха графика до подокна индикатора
   chart_y_distance=(int)ChartGetInteger(0,CHART_WINDOW_YDISTANCE,number_subwindow);
//--- Преобразуем координату Y в относительную
   y-=chart_y_distance;
  }
//---

Полосу прокрутки нужно устанавливать, как и канву, в функции OnInit() во время инициализации (см. выделенные строки в коде ниже).

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Включим слежение за событиями мыши
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Установим короткое имя
   IndicatorSetString(INDICATOR_SHORTNAME,shortname_subwindow);
//--- Установим размеры массивам показателей символа и их цвета
   ArrayResize(array_color,SYMBOL_LIST_SIZE);
   ArrayResize(array_value,SYMBOL_LIST_SIZE);
//--- Получим свойства подокна
   SetSubwindowProperties();
//--- Установим шрифт для отображения в канве
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Запомним размер (высоту) текста для расчётов
   text_h=canvas.TextHeight("A")-1;
//--- Посчитаем высоту всего списка
   list_h=text_h*SYMBOL_LIST_SIZE;
//--- Создадим канву
   SetCanvas();
//--- Установим фон скролла
   SetResizeScrollBackground();
//--- Установим скролл
   SetResizeScroll();
//--- Отобразим список показателей символа
   ShowSymbolInfo();
//--- Обновим график
   ChartRedraw();
//--- Всё прошло успешно
   return(INIT_SUCCEEDED);
  }
//---

Также нужно добавить код в функцию OnDeinit(). В зависимости от причины деинициализации программу можно настроить более точно.

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

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

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int    rates_total,     // размер массива price[]
                const int    prev_calculated, // обработано баров на предыдущем вызове
                const int    begin,           // откуда начинаются значимые данные
                const double &price[])        // массив для расчета
  {
//--- Получим координату Y скролла
   scroll_y=(int)ObjectGetInteger(0,scroll_name,OBJPROP_YDISTANCE);
//--- Обновим канву
   ShowSymbolInfo(YToPercent(scroll_y));
//---
   return(rates_total);
  }
//---

Всё готово. Код для изучения в редакторе MetaEditor 5 можно скачать в конце статьи. Ниже можно ещё посмотреть видео ролик с демонстрацией того, что получилось.






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


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

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