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

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

MetaTrader 5Примеры | 30 октября 2013, 14:00
5 065 6
Anatoli Kazharski
Anatoli Kazharski

Введение

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

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

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

 

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

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

#define LIST_SIZE 71                // Количество строк в списке показателей финансового инструмента
//--- Подключим класс для работы с холстом
#include <Canvas\Canvas.mqh>
CCanvas canvas;
//+------------------------------------------------------------------+
//|   Глобальные переменные                                          |
//+------------------------------------------------------------------+
//--- Свойства подокна индикатора
int               subwindow_number              =WRONG_VALUE;                    // Номер подокна
int               subwindow_height              =0;                              // Высота подокна
string            subwindow_shortname           ="TestScrollbar";                // Короткое имя индикатора
string            prefix                        =subwindow_shortname+"_";        // Префикс для объектов
int               chart_width                   =0;                              // Ширина графика
int               chart_height                  =0;                              // Высота графика
int               chart_y_offset                =0;                              // Дистанция от верха графика до подокна
//--- Свойства холста
string            canvas_name                   =prefix+"canvas";                // Название холста
color             canvas_background_color       =C'20,20,20';                    // Цвет фона холста
ENUM_COLOR_FORMAT color_format                  =COLOR_FORMAT_XRGB_NOALPHA;      // Компонент альфа-канала игнорируется
//--- Свойства списка
int               list_height                   =0;                              // Высота списка
int               text_height                   =0;                              // Высота текста
int               font_size                     =15;                             // Размер шрифта
string            font_name                     ="Calibri";                      // Шрифт
double            line_size                     =100/LIST_SIZE;                  // Размер одной строки списка в процентах
//--- Свойства полосы прокрутки: ползунок
string            scrollbar_thumb_name          =prefix+"scrollbar_thumb";       // Имя объекта-ползунка
int               scrollbar_thumb_x1            =0;                              // Координата x1
int               scrollbar_thumb_y1            =0;                              // Координата y1
int               scrollbar_thumb_x2            =0;                              // Координата x2
int               scrollbar_thumb_y2            =0;                              // Координата y2
double            scrollbar_thumb_y_percent     =0.0;                            // Координата по оси Y в процентном выражении
int               scrollbar_thumb_width         =9;                              // Ширина
int               scrollbar_thumb_height        =0;                              // Высота
bool              scrollbar_thumb_clicked       =false;                          // Состояние (кликнули мышью по ползунку или нет)
color             scrollbar_thumb_color         =clrSilver;                      // Цвет ползунка
color             scrollbar_thumb_color_on_hover=clrDimGray;                     // Цвет ползунка при наведении курсора
color             scrollbar_thumb_color_on_click=clrSlateGray;                   // Цвет ползунка при нажатии
//--- Свойства полосы прокрутки: фон
string            scrollbar_background_name     =prefix+"scrollbar_background";  // Имя объекта-фона
int               scrollbar_background_width    =9;                              // Ширина фона
color             scrollbar_background_color    =C'50,50,50';                    // Цвет фона
//--- Свойства полосы прокрутки: прочее
int               scrollbar_fix_point           =0;                              // Y-координата точки фиксации при нажатии
int               scrollbar_fix_point_y_offest  =0;                              // Расстояние по оси Y от верха скроллбара до точки фиксации
//--- Состояние кнопки мыши (нажата/отжата)
bool              mouse_button_state=false;
//--- Массивы для показателей финансового инструмента
color             symbol_property_colors[];                                      // Цвета значений
string            symbol_property_values[];                                      // Значения
//--- Названия показателей финансового инструмента
string symbol_propety_names[LIST_SIZE]=
  {
   "Количество сделок в текущей сессии",
   "Общее число ордеров на покупку в текущий момент",
   "Общее число ордеров на продажу в текущий момент",
   "Объем в последней сделке",
   "Максимальный объем за день",
   "Минимальный объем за день",
   "Время последней котировки",
   "Количество знаков после запятой",
   "Размер спреда в пунктах",
   "Признак плавающего спреда",
   "Максимальное количество показываемых заявок в стакане",
   "Способ вычисления стоимости контракта",
   "Тип исполнения ордеров",
   "Дата начала торгов по инструменту (обычно используется для фьючерсов)",
   "Дата окончания торгов по инструменту (обычно используется для фьючерсов)",
   "Минимальный отступ в пунктах от текущей цены закрытия для установки Stop ордеров",
   "Дистанция заморозки торговых операций (в пунктах)",
   "Режим заключения сделок",
   "Модель расчета свопа",
   "День недели для начисления тройного свопа",
   "Флаги разрешенных режимов истечения ордера",
   "Флаги разрешенных режимов заливки ордера",
//---
   "Bid - лучшее предложение на продажу",
   "Максимальный Bid за день",
   "Минимальный Bid за день",
   "Ask - лучшее предложение на покупку",
   "Максимальный Ask за день",
   "Минимальный Ask за день",
   "Last - цена последней сделки",
   "Максимальный Last за день",
   "Минимальный Last за день",
   "Значение одного пункта",
   "Рассчитанная стоимость тика для прибыльной позиции",
   "Рассчитанная стоимость тика для убыточной позиции",
   "Минимальное изменение цены",
   "Размер торгового контракта",
   "Минимальный объем для заключения сделки",
   "Максимальный объем для заключения сделки",
   "Минимальный шаг изменения объема для заключения сделки",
   "Максимально допустимый совокупный объем открытой позиции и отложенных ордеров  в одном направлении",
   "Значение свопа в покупку",
   "Значение свопа в продажу",
   "Начальная маржа - размер необходимых залоговых средств в маржинальной валюте для открытия позиции (1 лот)",
   "Поддерживающая маржа по инструменту",
   "Коэффициент взимания маржи по длинным позициям",
   "Коэффициент взимания маржи по коротким позициям",
   "Коэффициент взимания маржи по Limit ордерам",
   "Коэффициент взимания маржи по Stop ордерам",
   "Коэффициент взимания маржи по Stop Limit ордерам",
   "Суммарный объем сделок в текущую сессию",
   "Суммарный оборот в текущую сессию",
   "Суммарный объем открытых позиций",
   "Общий объем ордеров на покупку в текущий момент",
   "Общий объем ордеров на продажу в текущий момент",
   "Цена открытия сессии",
   "Цена закрытия сессии",
   "Средневзвешенная цена сессии",
   "Цена поставки на текущую сессию",
   "Минимально допустимое значение цены на сессию",
   "Максимально допустимое значение цены на сессию",
//---
   "Базовая валюта инструмента",
   "Валюта прибыли",
   "Валюта, в которой вычисляется залоговые средства",
   "Источник текущей котировки",
   "Строковое описание символа",
   "Имя торгового символа в системе международных идентификационных кодов ценных бумаг (ISIN)",
   "Путь в дереве символов",
//---
   "Текущее количество баров для символа на выбранном таймфрейме",
   "Самая первая дата для символа на выбранном таймфрейме",
   "Самая первая дата в истории для символа на выбранном таймфрейме",
   "Данные по символу синхронизированы"
  };

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

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

//+------------------------------------------------------------------+
//| Добавляет холст                                                  |
//+------------------------------------------------------------------+
void AddCanvas()
  {
//--- Если холста нет, добавим его
   if(ObjectFind(0,canvas_name)<0)
      canvas.CreateBitmapLabel(0,subwindow_number,canvas_name,0,0,chart_width,subwindow_height,color_format);
  }

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

//+------------------------------------------------------------------+
//| Изменяет размеры холста                                          |
//+------------------------------------------------------------------+
void ResizeCanvas()
  {
//--- Если холст уже добавлен в подокно индикатора, установим новый размер
   if(ObjectFind(0,canvas_name)==subwindow_number)
      canvas.Resize(chart_width,subwindow_height);
//--- Если холста нет, добавим его
   else
      canvas.CreateBitmapLabel(0,subwindow_number,canvas_name,0,0,chart_width,subwindow_height,color_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,subwindow_shortname);
//--- Установим размеры массивов показателей символа и их цвета
   ArrayResize(symbol_property_colors,LIST_SIZE);
   ArrayResize(symbol_property_values,LIST_SIZE);
//--- Установим свойства подокна
   SetSubwindowProperties();
//--- Установим шрифт для отображения на холсте
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Запомним размер (высоту) текста для расчетов
   text_height=canvas.TextHeight("A")-1;
//--- Посчитаем высоту всего списка
   list_height=text_height*LIST_SIZE;
//--- Добавим холст на график
   AddCanvas();
//--- Отобразим список показателей символа
   ShowSymbolInfo();
//--- Обновим график
   ChartRedraw();
//--- Все прошло успешно
   return(INIT_SUCCEEDED);
  }

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

//+------------------------------------------------------------------+
//| Отображает информацию текущего символа                           |
//+------------------------------------------------------------------+
void ShowSymbolInfo(double current_thumb_position=0.0)
  {
   int    list_lines       =0;   // Счетчик нанесенных на холст строк
   double thumb_position   =0.0; // Положение ползунка в процентах для определения первой отображаемой строки
   int    y_distance       =0;   // Для определения координаты следующей строки в списке
   int    line_number      =0;   // Номер строки, с которой будет отображаться список
//--- Определим строку, с которой будет отображаться список
   for(int i=0; i<LIST_SIZE; i++)
     {
      //--- Считаем строки пока не дойдем до той, с которой нужно отобразить список
      if(thumb_position>=current_thumb_position)
         break;
      //---
      thumb_position+=line_size;
      line_number++;
     }
//--- Инициализируем массивы списка с указанной строки
   InitializePropertyArrays(line_number);
//--- Очистим холст
   canvas.Erase(canvas_background_color);
//--- Нанесем на холст список
   for(int i=line_number; i<LIST_SIZE; i++)
     {
      //--- Название показателя
      canvas.TextOut(655,y_distance,symbol_propety_names[i]+" :",ColorToARGB(clrWhite),TA_RIGHT|TA_TOP);
      //--- Значение показателя
      canvas.TextOut(665,y_distance,symbol_property_values[i],ColorToARGB(symbol_property_colors[i]),TA_LEFT|TA_TOP);
      //--- Расcчитаем координату для следующей строки
      y_distance+=text_height;
      //--- Посчитаем кол-во нанесенных строк
      list_lines++;
      //--- Если выходим за пределы подокна, остановим цикл
      if(list_lines*text_height>subwindow_height)
         break;
     }
//--- Обновим холст
   canvas.Update();
  }

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

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

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

//+------------------------------------------------------------------+
//| Инициализирует массивы значений и их цветов для текущего символа |
//+------------------------------------------------------------------+
void InitializePropertyArrays(int line_number)
  {
   int lines_count=0;
//---
   for(int i=line_number; i<LIST_SIZE; i++)
     {
      //--- Определим значение и цвет показателя символа
      symbol_property_values[i]=GetStringSymbolInfoByIndex(i);
      symbol_property_colors[i]=GetColorSymbolInfoByIndex(i);
      //--- Увеличим счетчик
      lines_count++;
      //--- Если по количеству строк превысили высоту подокна, выйдем
      if(lines_count*text_height>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=TradeCalcModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_CALC_MODE));            break;
      case 12 : str=TradeModeToString(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=TradeExeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE));               break;
      case 18 : str=SwapModeToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_MODE));                       break;
      case 19 : str=WeekdayToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_ROLLOVER3DAYS));               break;
      case 20 : str=ExpirationModeToString();                                                            break;
      case 21 : str=FillingModeToString();                                                               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);
  }

Выделенные выше функции TradeCalcModeToString(), TradeModeToString(), TradeExeModeToString(), SwapModeToString() и WeekdayToString() просто возвращают строковое представление показателей в зависимости от переданного значения.

//+------------------------------------------------------------------+
//| Возвращает строковое представление способа вычисления            |
//| величины залоговых средств по инструменту                        |
//+------------------------------------------------------------------+
string TradeCalcModeToString(long mode)
  {
   string str="?";
//---
   switch((ENUM_SYMBOL_CALC_MODE)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;
      case SYMBOL_CALC_MODE_EXCH_STOCKS         :
         str="Exchange mode";       break;
      case SYMBOL_CALC_MODE_EXCH_FUTURES        :
         str="Futures mode";        break;
      case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS  :
         str="FORTS Futures mode";  break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Возвращает строковое представление режима торговли               |
//+------------------------------------------------------------------+
string TradeModeToString(long mode)
  {
   string str="-";
//---
   switch((ENUM_SYMBOL_TRADE_MODE)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 TradeExeModeToString(long mode)
  {
   string str="-";
//---
   switch((ENUM_SYMBOL_TRADE_EXECUTION)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 SwapModeToString(long mode)
  {
   string str="-";
//---
   switch((ENUM_SYMBOL_SWAP_MODE)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 WeekdayToString(long day)
  {
   string str="-";
//---
   switch((ENUM_DAY_OF_WEEK)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() строковое значение формируется в зависимости от того, какие режимы истечения ордера и заполнения объема доступны для текущего символа.

//+------------------------------------------------------------------+
//| Возвращает строковое представление режимов истечения ордера      |
//+------------------------------------------------------------------+
string ExpirationModeToString()
  {
   string str="";    // Для формирования строки
//--- Переменные для проверки режимов
   bool   gtc           =false; // Ордер действителен неограниченно по времени до явной его отмены
   bool   day           =false; // Ордер действителен до конца дня
   bool   specified     =false; // Срок истечения указывается в ордере
   bool   specified_day =false; // День истечения указывается в ордере
//--- Проверим режимы
   gtc           =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_GTC);
   day           =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_DAY);
   specified     =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED);
   specified_day =IsExpirationTypeAllowed(_Symbol,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 FillingModeToString()
  {
//--- Переменная для формирования строки
   string str="";

//--- Переменные для проверки режимов:

//    "Все/Ничего"   -  если необходимый объем в ордере по указанной цене не набирается,
//                      то ордер отменяется и сделка не проводится
   bool   fok=false;

//    "Все/Частично" -  если по указанной в ордере цене сделку можно заполнить только частично, 
//                      то совершается сделка на доступный объем. Остаток по ордеру снимается, 
//                      новый ордер не выставляется
   bool   ioc=false;

//    "Вернуть"      -  Совершается сделка по указанной в заявке цене в пределах доступного объема.
//                      На остаток от заполнения выставляется новый ордер по той же цене
   bool   return_remainder=false;

//--- Проверим режимы
   fok   =IsFillingTypeAllowed(_Symbol,SYMBOL_FILLING_FOK);
   ioc   =IsFillingTypeAllowed(_Symbol,SYMBOL_FILLING_IOC);
//--- Для режимов "Исполнение по рынку" и "Биржевое исполнение"
   ENUM_SYMBOL_TRADE_EXECUTION symbol_trade_exemode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);
   return_remainder=(symbol_trade_exemode==SYMBOL_TRADE_EXECUTION_MARKET || 
                     symbol_trade_exemode==SYMBOL_TRADE_EXECUTION_EXCHANGE) ? true : false;
//--- Сформируем строку доступных режимов
   if(fok)
     {
      StringAdd(str,"Fill or Kill");
      if(ioc || return_remainder)
         StringAdd(str," / ");
     }
//---
   if(ioc)
     {
      StringAdd(str,"Immediate or Cancel");
      if(return_remainder)
         StringAdd(str," / ");
     }
//---
   if(return_remainder)
      StringAdd(str,"Return");
//---
   return(str);
  }

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

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

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

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

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

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

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

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

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

Создадим функции для создания и изменения размеров ползунка и фона полосы прокрутки: AdjustScrollbarThumb() и AdjustScrollbarBackground():

//+------------------------------------------------------------------+
//| Добавляет ползунок полосы прокрутки или корректирует его размеры |
//+------------------------------------------------------------------+
void AdjustScrollbarThumb()
  {
//--- Рассчитаем размер ползунка относительно высоты подокна
   CalculateScrollbarThumbHeight();
//--- Если ползунок уже есть на графике, скорректируем его свойства
   if(ObjectFind(0,scrollbar_thumb_name)>0)
     {
      //--- Установим высоту и координату X
      ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_YSIZE,scrollbar_thumb_height);
      ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_XDISTANCE,chart_width-scrollbar_thumb_width);
      //--- Скорректируем положение ползунка по оси Y, если выходим за пределы подокна вниз
      if(scrollbar_thumb_y1+scrollbar_thumb_height>subwindow_height)
         ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE,subwindow_height-scrollbar_thumb_height);
     }
//--- Создадим ползунок, если его нет
   else
     {
      CreateRectangleLable(0,subwindow_number,scrollbar_thumb_name,
                           chart_width-scrollbar_thumb_width,0,scrollbar_thumb_width,scrollbar_thumb_height,
                           CORNER_LEFT_UPPER,clrSilver,clrSilver,false,false);
     }
  }
//+------------------------------------------------------------------+
//| Добавляет фон полосы прокрутки или корректирует его размеры      |
//+------------------------------------------------------------------+
void AdjustScrollbarBackground()
  {
//--- Если фон полосы прокрутки уже есть на графике, скорректируем его свойства
   if(ObjectFind(0,scrollbar_background_name)>0)
     {
      //--- Установим размеры фона
      ObjectSetInteger(0,scrollbar_background_name,OBJPROP_YDISTANCE,0);
      ObjectSetInteger(0,scrollbar_background_name,OBJPROP_XDISTANCE,chart_width-scrollbar_background_width);
      ObjectSetInteger(0,scrollbar_background_name,OBJPROP_YSIZE,subwindow_height);
     }
//--- Если фона нет, создадим его
   else
     {
      CreateRectangleLable(0,subwindow_number,scrollbar_background_name,
                           chart_width-scrollbar_background_width,0,scrollbar_background_width,subwindow_height,
                           CORNER_LEFT_UPPER,scrollbar_background_color,scrollbar_background_color,false,false);
     }
  }

В самом начале функции AdjustScrollbarThumb() в выделенной строке производится расчет высоты ползунка:

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает цвет ползунка полосы прокрутки                     |
//+------------------------------------------------------------------+
void SetScrollbarThumbColor(color thumb_color)
  {
   ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_COLOR,thumb_color);
   ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_BGCOLOR,thumb_color);
  }
//+------------------------------------------------------------------+
//| Устанавливает границы ползунка полосы прокрутки                  |
//+------------------------------------------------------------------+
void SetScrollbarThumbBoundaries()
  {
   scrollbar_thumb_x1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_XDISTANCE);
   scrollbar_thumb_y1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE);
   scrollbar_thumb_x2=scrollbar_thumb_x1+scrollbar_thumb_width;
   scrollbar_thumb_y2=scrollbar_thumb_y1+scrollbar_thumb_height;
  }
//+------------------------------------------------------------------+
//| Изменяет цвет ползунка при наведении на него курсора мыши        |
//+------------------------------------------------------------------+
void ChangeScrollbarThumbColorOnHover(int x,int y)
  {
//--- Если курсор мыши располагается в пределах границ ползунка, сделаем цвет темнее
   if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2)
      SetScrollbarThumbColor(scrollbar_thumb_color_on_hover);
//--- Если курсор за пределами границ ползунка
   else
     {
      //--- Если кнопка мыши отжата, вернем обычный цвет ползунка
      if(!mouse_button_state)
         SetScrollbarThumbColor(scrollbar_thumb_color);
     }
  }
//+------------------------------------------------------------------+
//| Определяет состояние ползунка полосы прокрутки                   |
//+------------------------------------------------------------------+
void SetScrollbarThumbState(int x,int y)
  {
//--- Если курсор мыши располагается в пределах границ ползунка
   if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2)
     {
      //--- Если кнопка мыши зажата, запомним это
      if(mouse_button_state)
         scrollbar_thumb_clicked=true;
     }
//--- Если курсор за пределами границ ползунка
   else
     {
      //--- Если кнопка мыши отжата, отключим управление ползунком
      if(!mouse_button_state)
         ZeroScrollbarThumbVariables();
     }
  }
//+------------------------------------------------------------------+
//| Обнуляет переменные, связанных с перемещением ползунка           |
//+------------------------------------------------------------------+
void ZeroScrollbarThumbVariables()
  {
   scrollbar_thumb_clicked       =false;
   scrollbar_fix_point           =0;
   scrollbar_fix_point_y_offest  =0;
  }

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

Ниже представлены функции MoveThumb(), UpdateListAndScrollbarThumb() и ThumbYCoordinateToPercent(), с помощью которых осуществляются вышеописанные действия:

//+------------------------------------------------------------------+
//| Перемещает ползунок по вертикали на указанную координату         |
//+------------------------------------------------------------------+
void MoveThumb(int y)
  {
   int  threshold   =1; // Порог в пикселях для перерасчета
   int  new_y_point =0; // Новая координата Y
//--- Если кнопка мыши нажата
   if(mouse_button_state)
     {
      //--- Установим цвет нажатого ползунка
      SetScrollbarThumbColor(scrollbar_thumb_color_on_click);
      //--- Запомним текущую координату Y курсора
      if(scrollbar_fix_point==0)
         scrollbar_fix_point=y;
      //--- Запомним расстояние от верха ползунка до курсора
      if(scrollbar_fix_point_y_offest==0)
         scrollbar_fix_point_y_offest=scrollbar_thumb_y1-scrollbar_fix_point;
     }
//--- Если в нажатом состоянии прокрутили вниз больше порогового значения
   if(y-scrollbar_fix_point>=threshold)
     {
      //--- Если не выходим за нижнюю границу подокна
      if(scrollbar_thumb_y1+scrollbar_thumb_height+threshold<subwindow_height)
         new_y_point=y+scrollbar_fix_point_y_offest;
      else
        {
         scrollbar_fix_point_y_offest=0;
         new_y_point=int(subwindow_height-scrollbar_thumb_height)-1;
        }
      //--- Обновим список и ползунок полосы прокрутки
      UpdateListAndScrollbarThumb(new_y_point);
      return;
     }
//--- Если в нажатом состоянии прокрутили вверх больше порогового значения
   if(y-scrollbar_fix_point<=-(threshold))
     {
      //--- Если не выходим за верхнюю границу подокна
      if(y-fabs(scrollbar_fix_point_y_offest)>=0)
         new_y_point=y-fabs(scrollbar_fix_point_y_offest);
      else
        {
         new_y_point=0;
         scrollbar_fix_point_y_offest=0;
        }
      //--- Обновим список и ползунок полосы прокрутки
      UpdateListAndScrollbarThumb(new_y_point);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Обновляет список и положение ползунка                            |
//+------------------------------------------------------------------+
void UpdateListAndScrollbarThumb(int new_point)
  {
//--- Установим новую Y координату для ползунка
   ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE,new_point);
//--- Обновим список со свойствами символа относительно текущего положения ползунка
   ShowSymbolInfo(ThumbYCoordinateToPercent(new_point));
//--- Обнулим точку фиксации
   scrollbar_fix_point=0;
  }
//+------------------------------------------------------------------+
//| Преобразует координату Y ползунка в процент                      |
//+------------------------------------------------------------------+
double ThumbYCoordinateToPercent(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)
     {
      //--- Обнулим переменные, связанные с перемещением ползунка
      ZeroScrollbarThumbVariables();
      //--- Обновим график
      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   price  =0.0;         // Цена, соответствующая координате Y
 
      //--- Установим свойства подокна
      SetSubwindowProperties();
      //--- Проверим и запомним состояние кнопки мыши
      CheckMouseButtonState(sparam);
      //--- Получим местоположение курсора
      if(ChartXYToTimePrice(0,x,y,window,time,price))
        {
         //--- Если курсор в пределах границ подокна
         if(window==subwindow_number)
           {
            //--- Отключим прокрутку ценового графика
            ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
            //--- Пересчитаем Y координату относительно подокна индикатора
            ChartYToSubwindowY(y);
            //--- Определим границы ползунка полосы прокрутки
            SetScrollbarThumbBoundaries();
            //--- Изменим цвет ползунка при наведении курсора
            ChangeScrollbarThumbColorOnHover(x,y);
            //--- Определим состояние ползунка
            SetScrollbarThumbState(x,y);
            //--- Если управление передано ползунку, переместим его и обновим список
            if(scrollbar_thumb_clicked)
               MoveThumb(y);
           }
         //--- Если курсор за пределами границ подокна
         else
           {
            //--- Включим обратно прокрутку ценового графика
            ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
            //--- Если отжали кнопку мыши, вернем обычный цвет ползунка
            if(!scrollbar_thumb_clicked)
               SetScrollbarThumbColor(scrollbar_thumb_color);
           }
        }
      //--- Если местоположение курсора не определено
      else
        {
         //--- Если управление не у ползунка, установим его обычный цвет
         if(!scrollbar_thumb_clicked)
            SetScrollbarThumbColor(scrollbar_thumb_color);
        }
      //--- Обновим график
      ChartRedraw();
      return;
     }
//--- Отслеживаем изменение свойств и размеров графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Установим свойства подокна
      SetSubwindowProperties();
      //--- Получим Y координату ползунка
      scrollbar_thumb_y1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE);
      //--- Если размер подокна равен нулю, выйдем
      if(subwindow_height<=0)
         return;
      //--- Установим новый размер холста
      ResizeCanvas();
      //--- Обновим фон полосы прокрутки
      AdjustScrollbarBackground();
      //--- Обновим ползунок
      AdjustScrollbarThumb();
      //--- Обновим данные на холсте
      ShowSymbolInfo(ThumbYCoordinateToPercent(scrollbar_thumb_y1));
      //---
      return;
     }
  }

Функциями, выделенные в коде выше, являются вспомогательными. Их предназначение можно легко понять по комментариям.

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

Полосу прокрутки, как и холст, необходимо добавить в подокно индикатора во время инициализации.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Включим слежение за событиями мыши
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Установим короткое имя
   IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname);
//--- Установим размеры массивов показателей символа и их цвета
   ArrayResize(symbol_property_colors,LIST_SIZE);
   ArrayResize(symbol_property_values,LIST_SIZE);
//--- Установим свойства подокна
   SetSubwindowProperties();
//--- Установим шрифт для отображения на холсте
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Запомним размер (высоту) текста для расчетов
   text_height=canvas.TextHeight("A")-1;
//--- Посчитаем высоту всего списка
   list_height=text_height*LIST_SIZE;
//--- Добавим холст на график
   AddCanvas();
//--- Добавим полосу прокрутки: фон и ползунок
   AdjustScrollbarBackground();
   AdjustScrollbarThumb();
//--- Отобразим список показателей символа
   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)         // терминал был закрыт
     {
      //--- Удалим полосу прокрутки
      DeleteScrollbar();
      //--- Удалим холст
      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,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- Получим Y координату ползунка
   scrollbar_thumb_y1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE);
//--- Обновим данные на холсте
   ShowSymbolInfo(ThumbYCoordinateToPercent(scrollbar_thumb_y1));
//--- return value of prev_calculated for next call
   return(rates_total);
  }

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

 

Заключение

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

Прикрепленные файлы |
testscrollbar.mq5 (56.51 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Anatoli Kazharski
Anatoli Kazharski | 16 февр. 2015 в 11:43
thejobber:

Анатолий, спасибо за Вашу работу и труд!

такой вопрос, есть ли какой-нибудь пример по использованию класса CScroll? из стандартной библиотеки Scrolls.mqh

Спасибо.

Я не использую стандартную библиотеку для разработки интерфейсов с элементами управления.

Rashid Umarov
Rashid Umarov | 16 февр. 2015 в 11:52
thejobber:

Анатолий, спасибо за Вашу работу и труд!

такой вопрос, есть ли какой-нибудь пример по использованию класса CScroll? из стандартной библиотеки Scrolls.mqh

Есть пример в стандартной поставке


Serhiy Dotsenko
Serhiy Dotsenko | 16 февр. 2015 в 11:56
спасибо, ещё крутил этот пример... не заметил, тупанул одним словом ))
Serhiy Dotsenko
Serhiy Dotsenko | 16 февр. 2015 в 12:15
немного прийдётся переделать логику визуализации... я создавал label, и их количество за ранее не было извесно, думал к форме прикрутить скрол, теперь будет ещё проще, будет один объект CListView. натолкнули меня на хорошую мысль ))
Serhiy Dotsenko
Serhiy Dotsenko | 16 февр. 2015 в 13:38
в CListView у отдельного item'a можно цвет менять?
Индикатор для построения графика "Каги" Индикатор для построения графика "Каги"
В статье предложен вариант индикатора графика "Каги" с различными способами построения и дополнительными функциями, рассмотрен принцип построения индикатора и особенности его реализации на MQL5. Представлены наиболее популярные примеры его практического использования в торговле - стратегии торговли по смене Yin/Yang, отталкивание от линии тренда, торговля по каналам и последовательно возрастающие "плечи"/убывающие "талии".
Удивите ваших MQL5-клиентов эффективным коктейлем технологий! Удивите ваших MQL5-клиентов эффективным коктейлем технологий!
MQL5 предоставляет программистам полный набор функций и объектно-ориентированный API, благодаря которым они могут делать в среде MetaTrader все что угодно. Тем не менее, веб-технологии – это очень универсальный инструмент, который может помочь в ситуациях, когда вам нужно создать нечто совершенно особое, вы хотите удивить ваших клиентов или у вас просто нет времени на изучение определенной части стандартной библиотеки MQL5. В данной статье вы узнаете, как можно управлять временем разработки при создании вашего уникального коктейля технологий.
Повышаем эффективность линейных торговых систем Повышаем эффективность линейных торговых систем
В сегодняшней статье речь пойдет о том, как MQL5-программисты со средним уровнем подготовки могут повысить прибыльность своих линейных торговых систем, используя так называемую технику возведения в степень (technique of exponentiation). Данная техника названа именно так, потому что при ее использовании кривая средств приобретает геометрическую, или экспоненциальную, форму, становясь похожей на параболу. В частности, мы реализуем на языке MQL5 фиксированно-фракционный (Fixed Fractional) метод Ральфа Винса.
Рецепты MQL5 - Элементы управления в подокне индикатора - Кнопки Рецепты MQL5 - Элементы управления в подокне индикатора - Кнопки
В этой статье мы рассмотрим пример разработки пользовательского интерфейса с такими элементами управления, как кнопки. В качестве подсказки пользователю о том, что с элементом можно взаимодействовать, сделаем так, чтобы кнопка при наведении на нее курсора мыши меняла цвет. При наведении курсора мыши на кнопку цвет будет немного затеняться, а при нажатии будет становиться заметно темнее. Добавим еще всплывающие подсказки для каждой кнопки. Таким образом, интерфейс станет интуитивно понятным.