Скачать MetaTrader 5

Графические интерфейсы IX: Элементы "Индикатор выполнения" и "Линейный график" (Глава 2)

26 июля 2016, 10:11
Anatoli Kazharski
26
2 130

Содержание

 

Введение

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

В предыдущей статье мы рассмотрели два взаимосвязанных элемента управления: «Палитру для выбора цвета» и «Кнопку для вызова цветовой палитры». Вторая глава будет посвящена двум элементам интерфейса: рассмотрим «Индикатор выполнения» и «Линейный график». Как всегда, будут показаны подробные примеры того, как использовать эти элементы в своих MQL-приложениях.

 


Элемент "Индикатор выполнения"

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

Перечислим все составные части, из которых будет собираться элемент «Индикатор выполнения» в разрабатываемой библиотеке.

  1. Фон
  2. Описание
  3. Полоса индикатора
  4. Фон индикатора
  5. Процентный показатель


Рис. 1. Составные части элемента «Индикатор выполнения».

Рис. 1. Составные части элемента «Индикатор выполнения».


Далее рассмотрим, как устроен класс для создания этого элемента.

 


Разработка класса CProgressBar

Создаём файл ProgressBar.mqh с классом CProgressBar со стандартными для всех элементов методами и подключаем его к движку библиотеки (файл WndContainer.mqh). Ниже представлен список свойств элемента доступных для настройки пользователем.

  • Цвет общего фона элемента
  • Текст описания
  • Цвет текста описания
  • Смещение описания по двум осям (x, y)
  • Цвет фона и рамки общей области индикатора
  • Размеры фона области индикатора
  • Толщина рамки области индикатора
  • Цвет полосы индикатора
  • Смещение текстовой метки процентного показателя выполняемого процесса
  • Количество знаков после запятой для процентного показателя
class CProgressBar : public CElement
  {
private:
   //--- Цвет фона элемента
   color             m_area_color;
   //--- Описание отображаемого процесса
   string            m_label_text;
   //--- Цвет текста
   color             m_label_color;
   //--- Смещение текстовой метки по двум осям
   int               m_label_x_offset;
   int               m_label_y_offset;
   //--- Цвета фона прогресс-бара и рамки фона
   color             m_bar_area_color;
   color             m_bar_border_color;
   //--- Размеры прогресс-бара
   int               m_bar_x_size;
   int               m_bar_y_size;
   //--- Смещение прогресс-бара по двум осям
   int               m_bar_x_offset;
   int               m_bar_y_offset;
   //--- Толщина рамки прогресс-бара
   int               m_bar_border_width;
   //--- Цвет индикатора
   color             m_indicator_color;
   //--- Смещение метки показателя процентов
   int               m_percent_x_offset;
   int               m_percent_y_offset;
   //--- Количество знаков после запятой
   int               m_digits;
   //---
public:
   //--- Количество знаков после запятой
   void              SetDigits(const int digits)        { m_digits=::fabs(digits);         }
   //--- (1) Цвет фона, (2) название процесса и (3) цвет текста
   void              AreaColor(const color clr)         { m_area_color=clr;                }
   void              LabelText(const string text)       { m_label_text=text;               }
   void              LabelColor(const color clr)        { m_label_color=clr;               }
   //--- Смещение текстовой метки (название процесса)
   void              LabelXOffset(const int x_offset)   { m_label_x_offset=x_offset;       }
   void              LabelYOffset(const int y_offset)   { m_label_y_offset=y_offset;       }
   //--- Цвет (1) фона и (2) рамки прогресс-бара, (3) цвет индикатора
   void              BarAreaColor(const color clr)      { m_bar_area_color=clr;            }
   void              BarBorderColor(const color clr)    { m_bar_border_color=clr;          }
   void              IndicatorColor(const color clr)    { m_indicator_color=clr;           }
   //--- (1) Толщина рамки, (2) размеры области индикатора
   void              BarBorderWidth(const int width)    { m_bar_border_width=width;        }
   void              BarXSize(const int x_size)         { m_bar_x_size=x_size;             }
   void              BarYSize(const int y_size)         { m_bar_y_size=y_size;             }
   //--- (1) Смещение прогресс бара по двум осям, (2) смещение метки показателя процентов
   void              BarXOffset(const int x_offset)     { m_bar_x_offset=x_offset;         }
   void              BarYOffset(const int y_offset)     { m_bar_y_offset=y_offset;         }
   //--- Смещение текстовой метки (процента процесса)
   void              PercentXOffset(const int x_offset) { m_percent_x_offset=x_offset;     }
   void              PercentYOffset(const int y_offset) { m_percent_y_offset=y_offset;     }
  };

Для создания элемента «Индикатор выполнения» понадобятся пять приватных (private) методов и один публичный (public):

class CProgressBar : public CElement
  {
private:
   //--- Объекты для создания элемента
   CRectLabel        m_area;
   CLabel            m_label;
   CRectLabel        m_bar_bg;
   CRectLabel        m_indicator;
   CLabel            m_percent;
   //---
public:
   //--- Методы для создания элемента
   bool              CreateProgressBar(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabel(void);
   bool              CreateBarArea(void);
   bool              CreateIndicator(void);
   bool              CreatePercent(void);
  };

Чтобы индикатор выполнения работал так, как от него ожидается, нужно указать общее количество шагов (итераций) процесса, к которому индикатор «привязывается» и текущий индекс итерации. Для этого понадобятся два вспомогательных метода CProgressBar::StepsTotal() и CProgressBar::CurrentIndex(). Эти методы предназначены для внутреннего (private) использования. В оба метода передаётся только один аргумент, значение которого при необходимости корректируется, чтобы предотвратить выход из допустимого диапазона. 

class CProgressBar : public CElement
  {
private:
   //--- Количество шагов диапазона
   double            m_steps_total;
   //--- Текущая позиция индикатора
   double            m_current_index;
   //---
private:
   //--- Установка новых значений для индикатора
   void              CurrentIndex(const int index);
   void              StepsTotal(const int total);
  };
//+------------------------------------------------------------------+
//| Количество шагов прогресс бара                                   |
//+------------------------------------------------------------------+
void CProgressBar::StepsTotal(const int total)
  {
//--- Скорректировать, если меньше 0
   m_steps_total=(total<1)? 1 : total;
//--- Скорректировать индекс, если выход из диапазона
   if(m_current_index>m_steps_total)
      m_current_index=m_steps_total;
  }
//+------------------------------------------------------------------+
//| Текущая позиция индикатора                                       |
//+------------------------------------------------------------------+
void CProgressBar::CurrentIndex(const int index)
  {
//--- Скорректировать, если меньше 0
   if(index<0)
      m_current_index=1;
//--- Скорректировать индекс, если выход из диапазона
   else
      m_current_index=(index>m_steps_total)? m_steps_total : index;
  }

Методы CProgressBar::StepsTotal() и CProgressBar::CurrentIndex() вызываются в главном методе для взаимодействия с элементом извне — CProgressBar::Update(). Именно в этот метод передаются параметры для индикатора выполнения, относительно которых осуществляются необходимые расчёты и его перерисовка. Первым аргументом здесь послужит индекс итерации процесса (index), а вторым — общее количество итераций (total). После того, как эти значения прошли проверку, рассчитывается новая ширина для полосы индикатора. Затем при необходимости это значение корректируется. Далее, (1) полосе индикатора устанавливается новая ширина, (2) рассчитывается значение для процентного показателя и формируется его строка, и в конце метода (3) текстовой метке процентного показателя устанавливается новое значение. 

class CProgressBar : public CElement
  {
public:
   //--- Обновление индикатора по указанным значениям
   void              Update(const int index,const int total);
  };
//+------------------------------------------------------------------+
//| Обновляет прогресс бар                                           |
//+------------------------------------------------------------------+
void CProgressBar::Update(const int index,const int total)
  {
//--- Установить новый индекс
   CurrentIndex(index);
//--- Установить новый диапазон
   StepsTotal(total);
//--- Рассчитаем ширину индикатора
   double new_width=(m_current_index/m_steps_total)*m_bar_bg.XSize();
//--- Скорректировать, если меньше 1
   if((int)new_width<1)
      new_width=1;
   else
     {
      //--- Скорректировать с учётом ширины рамки
      int x_size=m_bar_bg.XSize()-(m_bar_border_width*2);
      //--- Скорректировать, если выход за границу
      if((int)new_width>=x_size)
         new_width=x_size;
     }
//--- Установим индикатору новую ширину
   m_indicator.X_Size((int)new_width);
//--- Рассчитаем процент и сформируем строку
   double percent =m_current_index/m_steps_total*100;
   string desc    =::DoubleToString((percent>100)? 100 : percent,m_digits)+"%";
//--- Установим новое значение
   m_percent.Description(desc);
  }

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


Тест индикатора выполнения

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

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

class CProgram : public CWndEvents
  {
private:
   //--- Слайдеры
   CSlider           m_slider1;
   //--- Индикаторы выполнения
   CProgressBar      m_progress_bar1;
   CProgressBar      m_progress_bar2;
   CProgressBar      m_progress_bar3;
   CProgressBar      m_progress_bar4;
   CProgressBar      m_progress_bar5;
   CProgressBar      m_progress_bar6;
   CProgressBar      m_progress_bar7;
   CProgressBar      m_progress_bar8;
   //---
private:
   //--- Слайдеры
#define SLIDER1_GAP_X         (7)
#define SLIDER1_GAP_Y         (50)
   bool              CreateSlider1(const string text);
//---
#define PROGRESSBAR1_GAP_X    (7)
#define PROGRESSBAR1_GAP_Y    (100)
   bool              CreateProgressBar1(void);
//---
#define PROGRESSBAR2_GAP_X    (7)
#define PROGRESSBAR2_GAP_Y    (125)
   bool              CreateProgressBar2(void);
//---
#define PROGRESSBAR3_GAP_X    (7)
#define PROGRESSBAR3_GAP_Y    (150)
   bool              CreateProgressBar3(void);
//---
#define PROGRESSBAR4_GAP_X    (7)
#define PROGRESSBAR4_GAP_Y    (175)
   bool              CreateProgressBar4(void);
//---
#define PROGRESSBAR5_GAP_X    (7)
#define PROGRESSBAR5_GAP_Y    (200)
   bool              CreateProgressBar5(void);
//---
#define PROGRESSBAR6_GAP_X    (7)
#define PROGRESSBAR6_GAP_Y    (225)
   bool              CreateProgressBar6(void);
//---
#define PROGRESSBAR7_GAP_X    (7)
#define PROGRESSBAR7_GAP_Y    (250)
   bool              CreateProgressBar7(void);
//---
#define PROGRESSBAR8_GAP_X    (7)
#define PROGRESSBAR8_GAP_Y    (275)
   bool              CreateProgressBar8(void);
  };

Создание всех элементов уже рассматривалось в предыдущих статьях, поэтому здесь в качестве примера можно привести код метода только одного из индикаторов выполнения (см. листинг кода ниже). Как видно, в нём нет ничего такого, что вызвало бы какие-то сложности в понимании. Всё довольно прозрачно и просто. 

//+------------------------------------------------------------------+
//| Создаёт прогресс бар 1                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateProgressBar1(void)
  {
//--- Сохраним указатель на форму
   m_progress_bar1.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+PROGRESSBAR1_GAP_X;
   int y=m_window1.Y()+PROGRESSBAR1_GAP_Y;
//--- Установим свойства перед созданием
   m_progress_bar1.XSize(220);
   m_progress_bar1.YSize(15);
   m_progress_bar1.BarXSize(123);
   m_progress_bar1.BarYSize(11);
   m_progress_bar1.BarXOffset(65);
   m_progress_bar1.BarYOffset(2);
   m_progress_bar1.LabelText("Progress 01:");
//--- Создание элемента
   if(!m_progress_bar1.CreateProgressBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_progress_bar1);
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Создаёт экспертную панель                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Создание формы для элементов управления
//--- Создание элементов управления:
//    Главное меню
//--- Контекстные меню

//--- Слайдеры
   if(!CreateSlider1("Iterations total:"))
      return(false);
//--- Индикаторы выполнения
   if(!CreateProgressBar1())
      return(false);
   if(!CreateProgressBar2())
      return(false);
   if(!CreateProgressBar3())
      return(false);
   if(!CreateProgressBar4())
      return(false);
   if(!CreateProgressBar5())
      return(false);
   if(!CreateProgressBar6())
      return(false);
   if(!CreateProgressBar7())
      return(false);
   if(!CreateProgressBar8())
      return(false);
      
//--- Перерисовка графика
   m_chart.Redraw();
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
//--- Количество итераций
   int total=(int)m_slider1.GetValue();
//--- Восемь прогресс баров
   static int count1=0;
   count1=(count1>=total) ? 0 : count1+=8;
   m_progress_bar1.Update(count1,total);
//---
   static int count2=0;
   count2=(count2>=total) ? 0 : count2+=3;
   m_progress_bar2.Update(count2,total);
//---
   static int count3=0;
   count3=(count3>=total) ? 0 : count3+=12;
   m_progress_bar3.Update(count3,total);
//---
   static int count4=0;
   count4=(count4>=total) ? 0 : count4+=6;
   m_progress_bar4.Update(count4,total);
//---
   static int count5=0;
   count5=(count5>=total) ? 0 : count5+=18;
   m_progress_bar5.Update(count5,total);
//---
   static int count6=0;
   count6=(count6>=total) ? 0 : count6+=10;
   m_progress_bar6.Update(count6,total);
//---
   static int count7=0;
   count7=(count7>=total) ? 0 : count7+=1;
   m_progress_bar7.Update(count7,total);
//---
   static int count8=0;
   count8=(count8>=total) ? 0 : count8+=15;
   m_progress_bar8.Update(count8,total);
   
//--- Таймер для статусной строки
   static int count9=0;
   if(count9<TIMER_STEP_MSC*10)
     {
      count9+=TIMER_STEP_MSC;
      return;
     }
   count9=0;
   m_status_bar.ValueToItem(1,::TimeToString(::TimeLocal(),TIME_DATE|TIME_SECONDS));
  }

Скомпилируем программу и загрузим её на график. Результат окажется таким:

 Рис. 2. Тест элемента «Индикатор выполнения».

Рис. 2. Тест элемента «Индикатор выполнения».

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

 


Элемент "Линейный график"

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

У графиков терминала можно установить масштаб только от 0 (самая сильная степень сжатия) до 5. В самой сильной степени сжатия подразумевается, что для одного элемента массива выделяется один пиксель, и если весь массив данных не помещается в выделенной области, то для этого есть возможность воспользоваться прокруткой графика. Второй способ вместить больше данных — перейти на таймфрейм выше, и если установлен тип графика «Свечи» или «Бары», то, по крайней мере, можно хотя бы увидеть полный диапазон значений за указанный период. 

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

В качестве примера, на скриншоте ниже представлен пример линейного графика из Excel, на котором без потерь (минимумов и максимумов) уместился массив из 50000 элементов.

 Рис. 3. Линейный график в Excel. Размер массива данных - 50000 элементов.

Рис. 3. Линейный график в Excel. Размер массива данных - 50000 элементов.


Разработчики терминала предоставили в стандартной библиотеке классы для создания нескольких типов графиков. Их расположение в директории: <каталог терминала>\MQLX\Include\Canvas\Charts. Перечислим эти классы в списке ниже:

  • CChartCanvas – базовый класс для создания одного из трёх представленных типов графиков.
  • CLineChart – производный класс от CChartCanvas для создания линейного графика.
  • CHistogramChart – производный класс от CChartCanvas для создания гистограммы.
  • CPieChart – производный класс от CChartCanvas для создания круговой диаграммы.

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



Доработка классов из стандартной библиотеки

Как уже упоминалось выше, файлы с нужными классами находятся в этой директории: <каталог терминала>\MQLX\Include\Canvas\Charts. Создадим в директории разрабатываемой библиотеки (<каталог терминала>\MQLX\Include\EasyAndFastGUI\Canvas ) папку Charts и скопируем туда файлы ChartCanvas.mqh и LineChart.mqh из стандартной библиотеки. 

Сначала внесём изменения в базовый класс CChartCanvas. Сейчас его базовым классом является CCanvas. Ранее копия класса CCanvas из стандартной библиотеки была адаптирована под разрабатываемую библиотеку и переименована в CCustomCanvas. Теперь его нужно сделать базовым для CChartCanvas. В листинге кода ниже показано, в каких строках внесены изменения в файле ChartCanvas.mqh

//+------------------------------------------------------------------+
//|                                                  ChartCanvas.mqh |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\CustomCanvas.mqh"
...
//+------------------------------------------------------------------+
//| Class CChartCanvas                                               |
//| Usage: base class for graphical charts                           |
//+------------------------------------------------------------------+
class CChartCanvas : public CCustomCanvas
  {
...

Хотелось бы, чтобы у графиков можно было сделать градиентный фон. Добавим такую возможность. Чтобы это можно было реализовать, подключим файл с классом для работы цветом (CColors) к файлу CustomCanvas.mqh

//+------------------------------------------------------------------+
//|                                                 CustomCanvas.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\Colors.mqh"
...

Теперь в классе CChartCanvas можно объявить экземпляр этого класса и использовать его в своём проекте.

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

Итак, приступим к перечислению. Чтобы сделать градиентный фон, нужны минимум два цвета. В классе CChartCanvas уже есть метод для указания цвета фона — CChartCanvas::ColorBackground(). Добавим подобный метод CChartCanvas::ColorBackground2() для установки второго цвета. Нужен специальный метод для инициализации массива цветами градиента, такой же, как в классе CElements – метод InitColorArray(). Для отрисовки градиента нужно изменить метод CChartCanvas::DrawBackground(). Если раньше достаточно было залить фон одним цветом, просто вызвав CCustomCanvas::Erase() и передав в него в качестве аргумента нужный цвет, то теперь нужно сначала (1) объявить массив и установить ему размер, равный высоте полотна, (2) инициализировать его цветами градиента, (3) в цикле нарисовать каждую линию, использовав цвета из этого массива. Для обнуления массивов и удаления объектов нужно добавить в класс ещё один метод — CChartCanvas::DeleteAll(). 

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

Далее рассмотрим, какие изменения коснулись класса CLineChart. Базовый для него класс — CChartCanvas. Здесь тоже нужно создать метод для уничтожения объектов — CLineChart::DeleteAll(). Основные изменения здесь коснулись отрисовки данных на графике с помощью метода CLineChart::DrawData(). Во-первых, для него добавлены три вспомогательных метода: CLineChart::CheckLimitWhile(), CLineChart::CalculateVariables() и CLineChart::CalculateArray(), в которых осуществляются расчёты. И, самое главное, исправлен алгоритм сжатия отображаемых на графике данных. В предлагаемом варианте сжатие осуществляется так, что в случае превышения ширины выделенной области графика шириной массива отображаемых пикселей, этот массив делится на количество частей, равное количеству пикселей, выступающих за правый край графика. Затем каждая часть (кроме первой) наслаивается на предыдущую. Таким образом, выступ за правый край нивелируется, правый элемент массива всегда примагничен к правому краю, а также не происходит визуальной потери данных относительно минимумов и максимумов. 

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



Разработка класса CLineGraph

Прежде всего, нужно создать новый тип объекта в файле Objects.mqh. Это будет класс CLineChartObject, подобный всем классам в этом же файле. Базовым классом для него будет CLineChart, файл которого нужно подключить к файлу Objects.mqh:

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\Canvas\Charts\LineChart.mqh"
...

Теперь создаём файл LineGraph.mqh с классом CLineGraph и подключаем его к движку библиотеки (WndContainer.mqh). В классе CLineGraph должны быть стандартные виртуальные методы, как и у всех остальных элементов библиотеки:

//+------------------------------------------------------------------+
//| Класс для создания линейного графика                             |
//+------------------------------------------------------------------+
class CLineGraph : public CElement
  {
private:
   //--- Указатель на форму, к которой элемент присоединён
   CWindow          *m_wnd;
public:
   //--- Сохраняет указатель формы
   void              WindowPointer(CWindow &object)    { m_wnd=::GetPointer(object);  }
   //---
public:
   //--- Обработчик событий графика
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
   //--- Таймер
   virtual void      OnEventTimer(void) {}
   //--- Перемещение элемента
   virtual void      Moving(const int x,const int y);
   //--- (1) Показ, (2) скрытие, (3) сброс, (4) удаление
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
  };

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

  • Цвета градиента фона
  • Цвет рамки
  • Цвет сетки
  • Цвет текста
  • Количество знаков после запятой (для значений на вертикальной шкале)

class CLineGraph : public CElement
  {
private:
   //--- Цвета градиента
   color             m_bg_color;
   color             m_bg_color2;
   //--- Цвет рамки
   color             m_border_color;
   //--- Цвет сетки
   color             m_grid_color;
   //--- Цвет текста
   color             m_text_color;
   //--- Количество знаков после запятой
   int               m_digits;
   //---
public:
   //--- Количество знаков после запятой
   void              SetDigits(const int digits)       { m_digits=::fabs(digits);     }
   //--- Два цвета для градиента
   void              BackgroundColor(const color clr)  { m_bg_color=clr;              }
   void              BackgroundColor2(const color clr) { m_bg_color2=clr;             }
   //--- Цвета (1) рамки, (2) сетки и (3) текста
   void              BorderColor(const color clr)      { m_border_color=clr;          }
   void              GridColor(const color clr)        { m_grid_color=clr;            }
   void              TextColor(const color clr)        { m_text_color=clr;            }
  };

Для создания линейного графика понадобятся один приватный (private) и один публичный (public) метод:

class CLineGraph : public CElement
  {
public:
   //--- Методы для создания элемента
   bool              CreateLineGraph(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateGraph(void);
  };

И, наконец, для работы с элементом «Линейный график» уже после того, как он создан, будем использовать методы, которые позволят:

  • установить максимальное количество серий на графике;
  • установить максимум/минимум вертикальной шкалы и количество линий сетки;
  • добавить ряд данных;
  • обновить ряд данных;
  • удалить ряд данных. 

class CLineGraph : public CElement
  {
public:
   //--- Максимальное количество рядов данных
   void              MaxData(const int total)          { m_line_chart.MaxData(total); }
   //--- Установка параметров вертикальной шкалы
   void              VScaleParams(const double max,const double min,const int num_grid);
   //--- Добавление ряда на график
   void              SeriesAdd(double &data[],const string descr,const color clr);
   //--- Обновление ряда на графике
   void              SeriesUpdate(const uint pos,const double &data[],const string descr,const color clr);
   //--- Удаление ряда с графика
   void              SeriesDelete(const uint pos);
  };

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

 

 

Пишем приложение для теста линейного графика

Для теста можно сделать копию эксперта, который уже использовался ранее в этой статье для теста элемента «Индикатор выполнения». Удалим из него все элементы, кроме статусной строки. Определимся, какие элементы для управления линейным графиком нужно создать в графическом интерфейсе. 

Реализуем режим, в котором будет происходить автоматическое добавление и сокращение данных в массивах серий. Этот процесс будет контролироваться в таймере пользовательского класса CProgram. Сделаем так, чтобы скорость этого процесса тоже можно было контролировать, указывая в поле ввода задержку в миллисекундах (параметр Delay). Для этого режима будут введены ещё два дополнительных элемента управления, в полях ввода которых можно будет указать диапазон, то есть, минимальное (параметр Min. limit size) и максимальное (параметр Max. limit size) количество элементов в массивах. Если включить этот режим, массивы на каждом событии таймера будут увеличиваться на один элемент до указанного максимального размера, а после этого начнут сокращаться, пока не дойдут до минимального указанного размера, после чего всё повторяется сначала. При этом в поле ввода отдельного элемента будет показываться текущий размер массивов (параметр Size of series), которым можно будет управлять также и вручную.

Количество серий (рядов данных) можно будет настраивать, выбирая из выпадающего списка от 1-ой серии до 24-х (параметр Number of series). Данные будут рассчитываться с помощью тригонометрических формул по возвращаемым значениям математических функций синуса и косинуса. Добавим также элементы, которые позволят управлять параметрами, участвующими в расчётах. Здесь это будут (1) коэффициент приращения (параметр Increment ratio) и (2) смещение каждой серии относительно предыдущего ряда данных (параметр Offset series). Изменяя коэффициент приращения, можно получить разнообразные визуализации серий. У параметра Increment ratio будет чек-бокс, с помощью которого можно будет запустить автоматическое переключение на следующее значение, как только закончится цикл прироста/сокращения размера серий, если этот режим включен. Здесь прирост коэффициента тоже будет осуществляться до тех пор, пока мы не дойдём до максимального ограничения в самом элементе управления. Как только ограничение будет достигнуто, счётчик будет повёрнут на сокращение значений параметра Increment ratio. Другими словами, счётчик будет переворачиваться при достижении минимального либо максимального ограничений.

Чтобы сделать эксперимент еще интереснее, реализуем режим, который позволит сделать анимацию «бегущих» серий, а также добавим дополнительный параметр, контролирующий шаг смещения серий (Run speed). Кроме этого, добавим к графическому интерфейсу индикатор выполнения, который, если включен режим автоматического добавления и сокращения данных в массивы серий, будет показывать, сколько осталось до окончания этого процесса.

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

class CProgram : public CWndEvents
  {
private:
   //--- Элементы управления
   CSpinEdit         m_delay_ms;
   CComboBox         m_series_total;
   CCheckBoxEdit     m_increment_ratio;
   CSpinEdit         m_offset_series;
   CSpinEdit         m_min_limit_size;
   CCheckBoxEdit     m_max_limit_size;
   CCheckBoxEdit     m_run_speed;
   CSpinEdit         m_series_size;
   CLineGraph        m_line_chart;
   CProgressBar      m_progress_bar;
   //---
private:
   //--- Элементы для управления линейным графиком
#define SPINEDIT1_GAP_X       (7)
#define SPINEDIT1_GAP_Y       (25)
   bool              CreateSpinEditDelay(const string text);
#define COMBOBOX1_GAP_X       (7)
#define COMBOBOX1_GAP_Y       (50)
   bool              CreateComboBoxSeriesTotal(const string text);
#define CHECKBOX_EDIT1_GAP_X  (161)
#define CHECKBOX_EDIT1_GAP_Y  (25)
   bool              CreateCheckBoxEditIncrementRatio(const string text);
#define SPINEDIT2_GAP_X       (161)
#define SPINEDIT2_GAP_Y       (50)
   bool              CreateSpinEditOffsetSeries(const string text);
#define SPINEDIT3_GAP_X       (330)
#define SPINEDIT3_GAP_Y       (25)
   bool              CreateSpinEditMinLimitSize(const string text);
#define CHECKBOX_EDIT2_GAP_X  (330)
#define CHECKBOX_EDIT2_GAP_Y  (50)
   bool              CreateCheckBoxEditMaxLimitSize(const string text);
#define CHECKBOX_EDIT3_GAP_X  (501)
#define CHECKBOX_EDIT3_GAP_Y  (25)
   bool              CreateCheckBoxEditRunSpeed(const string text);
#define SPINEDIT4_GAP_X       (501)
#define SPINEDIT4_GAP_Y       (50)
   bool              CreateSpinEditSeriesSize(const string text);
   //--- Линейный график
#define LINECHART1_GAP_X      (5)
#define LINECHART1_GAP_Y      (75)
   bool              CreateLineChart(void);
   //--- Индикатор выполнения
#define PROGRESSBAR1_GAP_X    (5)
#define PROGRESSBAR1_GAP_Y    (364)
   bool              CreateProgressBar(void);
  };

Все методы для создания элементов в пользовательском классе уже рассматривались ранее в предыдущих статьях. Поэтому здесь будет показан код метода только для создания линейного графика. Но перед этим нужно создать методы для работы с массивами и для расчёта данных для них. Объявляем структуру Series с массивом для отображаемых данных data[] и со вспомогательным массивом data_temp[] для предварительных расчётов. Также нужны массивы, в которых будут храниться цвета и описания серий. 

class CProgram : public CWndEvents
  {
private:
   //--- Структура серий на графике
   struct Series
     {
      double            data[];      // массив отображаемых данных
      double            data_temp[]; // вспомогательный массив для расчётов
     };
   Series            m_series[];

   //--- (1) Названия и (2) цвета серий
   string            m_series_name[];
   color             m_series_color[];
  };

Установить новый размер массивам можно будет с помощью метода CProgram::ResizeDataArrays(). Текущее количество серий и их размер получаем из элементов управления: 

class CProgram : public CWndEvents
  {
private:
   //--- Установить новый размер сериям
   void              ResizeDataArrays(void);
  };
//+------------------------------------------------------------------+
//| Установим новый размер массивам                                  |
//+------------------------------------------------------------------+
void CProgram::ResizeDataArrays(void)
  {
   int total          =(int)m_series_total.ButtonText();
   int size_of_series =(int)m_series_size.GetValue();
//---
   for(int s=0; s<total; s++)
     {
      //--- Установим новый размер массивам
      ::ArrayResize(m_series[s].data,size_of_series);
      ::ArrayResize(m_series[s].data_temp,size_of_series);
     }
  }

После установки размера нужно инициализировать новыми данными массив, предназначенный для расчётов. Для этой цели применим метод CProgram::InitArrays(). В предварительных расчётах будут использоваться параметры из полей ввода элементов управления Offset series и Increment ratio. Для режима «бегущих» серий понадобится счётчик m_run_speed_counter, который при вызове в таймере метода CProgram::ShiftLineChartSeries() будет увеличиваться на значение из поля ввода элемента Run Speed, если чек-бокс этого элемента включен. 

class CProgram : public CWndEvents
  {
private:
   //--- Счётчик скорости "бегущей" серии
   double            m_run_speed_counter;

   //--- Инициализация вспомогательных массивов для расчётов
   void              InitArrays(void);

   //--- Смещение серий линейного графика ("бегущий" график)
   void              ShiftLineChartSeries(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_run_speed_counter(0.0)
  {
//--- ...
  }
//+------------------------------------------------------------------+
//| Смещение серий линейного графика                                 |
//+------------------------------------------------------------------+
void CProgram::ShiftLineChartSeries(void)
  {
   if(m_run_speed.CheckButtonState())
      m_run_speed_counter+=m_run_speed.GetValue();
  }
//+------------------------------------------------------------------+
//| Инициализация вспомогательных массивов для расчётов              |
//+------------------------------------------------------------------+
void CProgram::InitArrays(void)
  {
   int total=(int)m_series_total.ButtonText();
//---
   for(int s=0; s<total; s++)
     {
      int size_of_series=::ArraySize(m_series[s].data_temp);
      //---
      for(int i=0; i<size_of_series; i++)
        {
         if(i==0)
           {
            if(s>0)
               m_series[s].data_temp[i]=m_series[s-1].data_temp[i]+m_offset_series.GetValue();
            else
               m_series[s].data_temp[i]=m_run_speed_counter;
           }
         else
            m_series[s].data_temp[i]=m_series[s].data_temp[i-1]+(int)m_increment_ratio.GetValue();
        }
     }
  }

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

//--- Перечисление функций
enum ENUM_FORMULA
  {
   FORMULA_1=0, // Formula 1
   FORMULA_2=1, // Formula 2
   FORMULA_3=2  // Formula 3
  };
//--- Внешние параметры
input ENUM_FORMULA Formula        =FORMULA_1;       // Formula
input color        ColorSeries_01 =clrRed;          // Color series 01
input color        ColorSeries_02 =clrDodgerBlue;   // Color series 02
input color        ColorSeries_03 =clrWhite;        // Color series 03
input color        ColorSeries_04 =clrYellow;       // Color series 04
input color        ColorSeries_05 =clrMediumPurple; // Color series 05
input color        ColorSeries_06 =clrMagenta;      // Color series 06

Установка первоначального размера массивов серий, а также инициализация массивов описаний и цвета серий осуществляется в конструкторе класса CProgram

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_run_speed_counter(0.0)
  {
//--- Установка размера массивам серий
   int number_of_series=24;
   ::ArrayResize(m_series,number_of_series);
   ::ArrayResize(m_series_name,number_of_series);
   ::ArrayResize(m_series_color,number_of_series);
//--- Инициализация массива названий серий
   for(int i=0; i<number_of_series; i++)
      m_series_name[i]="Series "+string(i+1);
//--- Инициализация массивов цвета серий
   m_series_color[0] =m_series_color[6]  =m_series_color[12] =m_series_color[18] =ColorSeries_01;
   m_series_color[1] =m_series_color[7]  =m_series_color[13] =m_series_color[19] =ColorSeries_02;
   m_series_color[2] =m_series_color[8]  =m_series_color[14] =m_series_color[20] =ColorSeries_03;
   m_series_color[3] =m_series_color[9]  =m_series_color[15] =m_series_color[21] =ColorSeries_04;
   m_series_color[4] =m_series_color[10] =m_series_color[16] =m_series_color[22] =ColorSeries_05;
   m_series_color[5] =m_series_color[11] =m_series_color[17] =m_series_color[23] =ColorSeries_06;
  }

Для расчёта серий по указанной во внешних параметрах формуле применим метод CProgram::CalculateSeries(): 

class CProgram : public CWndEvents
  {
private:
   //--- Рассчитать серии
   void              CalculateSeries(void);
  };
//+------------------------------------------------------------------+
//| Рассчитывает серии                                               |
//+------------------------------------------------------------------+
void CProgram::CalculateSeries(void)
  {
   int total=(int)m_series_total.ButtonText();
//---
   for(int s=0; s<total; s++)
     {
      int size_of_series=::ArraySize(m_series[s].data_temp);
      //---
      for(int i=0; i<size_of_series; i++)
        {
         m_series[s].data_temp[i]+=m_offset_series.GetValue();
         //---
         switch(Formula)
           {
            case FORMULA_1 :
               m_series[s].data[i]=::sin(m_series[s].data_temp[i])-::cos(m_series[s].data_temp[i]);
               break;
            case FORMULA_2 :
               m_series[s].data[i]=::sin(m_series[s].data_temp[i]-::cos(m_series[s].data_temp[i]));
               break;
            case FORMULA_3 :
               m_series[s].data[i]=::sin(m_series[s].data_temp[i]*10)-::cos(m_series[s].data_temp[i]);
               break;
           }

        }
     }
  }

После того, как рассчитанные данные серий занесены в массивы, их можно (1) добавить на график с помощью метода CProgram::AddSeries() или, если они уже были ранее добавлены, (2) обновить с помощью метода CProgram::UpdateSeries()

class CProgram : public CWndEvents
  {
private:
   //--- Добавить серии на график
   void              AddSeries(void);
   //--- Обновить серии на графике
   void              UpdateSeries(void);
  };
//+------------------------------------------------------------------+
//| Рассчитывает и устанавливает серии на диаграмму                  |
//+------------------------------------------------------------------+
void CProgram::AddSeries(void)
  {
   int total=(int)m_series_total.ButtonText();
   for(int s=0; s<total; s++)
      m_line_chart.SeriesAdd(m_series[s].data,m_series_name[s],m_series_color[s]);
  }
//+------------------------------------------------------------------+
//| Рассчитывает и обновляет серии на диаграмме                      |
//+------------------------------------------------------------------+
void CProgram::UpdateSeries(void)
  {
   int total=(int)m_series_total.ButtonText();
   for(int s=0; s<total; s++)
      m_line_chart.SeriesUpdate(s,m_series[s].data,m_series_name[s],m_series_color[s]);
  }

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

//+------------------------------------------------------------------+
//| Создаёт линейный график                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateLineChart(void)
  {
//--- Сохраним указатель на окно
   m_line_chart.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+LINECHART1_GAP_X;
   int y=m_window1.Y()+LINECHART1_GAP_Y;
//--- Установим свойства перед созданием
   m_line_chart.XSize(630);
   m_line_chart.YSize(280);
   m_line_chart.BorderColor(clrSilver);
   m_line_chart.VScaleParams(2,-2,4);
   m_line_chart.MaxData(int(m_series_total.ButtonText()));
//--- Создадим элемент управления
   if(!m_line_chart.CreateLineGraph(m_chart_id,m_subwin,x,y))
      return(false);
//--- (1) Установим размер массивам и (2) инициализируем их
   ResizeDataArrays();
   InitArrays();
//--- (1) Рассчитаем и (2) добавим серии на график
   CalculateSeries();
   AddSeries();
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_line_chart);
   return(true);
  }

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

class CProgram : public CWndEvents
  {
private:
   //--- Перерасчёт серий на графике
   void              RecalculatingSeries(void);
  };
//+------------------------------------------------------------------+
//| Перерасчёт серий на графике                                      |
//+------------------------------------------------------------------+
void CProgram::RecalculatingSeries(void)
  {
//--- (1) Установим размер массивам и (2) инициализируем их
   ResizeDataArrays();
   InitArrays();
//--- (1) Рассчитаем и (2) обновим серии
   CalculateSeries();
   UpdateSeries();
  }

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

Рис. 4. Тест элемента «Линейный график».

Рис. 4. Тест элемента «Линейный график».

Если включен чек-бокс параметра Max. limit size, то запускается режим автоматического изменения размера массивов серий в методе CProgram::AutoResizeLineChartSeries(). Этот алгоритм уже был подробно описан в начале этого раздела, поэтому для ознакомления с кодом этого метода (см. листинг ниже) достаточно будет подробных комментариев. 

class CProgram : public CWndEvents
  {
private:
   //--- Автоматическое изменение размера серий линейного графика
   void              AutoResizeLineChartSeries(void);
  };
//+------------------------------------------------------------------+
//| Автоматическое изменение размера серий линейного графика         |
//+------------------------------------------------------------------+
void CProgram::AutoResizeLineChartSeries(void)
  {
//--- Выйти, если отключено увеличение массива серий по таймеру
   if(!m_max_limit_size.CheckButtonState())
      return;
//--- Для указания направления изменения размера массивов
   static bool resize_direction=false;
//--- Если дошли до минимума размера массива
   if((int)m_series_size.GetValue()<=m_min_limit_size.GetValue())
     {
      //--- Переключим направление на увеличение массива
      resize_direction=false;
      //--- Если нужно изменять значение X
      if(m_increment_ratio.CheckButtonState())
        {
         //--- Для указания направления счётчика коэффициента приращения
         static bool increment_ratio_direction=true;
         //--- Если счётчик направлен на увеличение
         if(increment_ratio_direction)
           {
            //--- Если дошли до максимального ограничения, изменим направление счётчика на противоположное
            if(m_increment_ratio.GetValue()>=m_increment_ratio.MaxValue()-1)
               increment_ratio_direction=false;
           }
         //--- Если счётчик направлен на уменьшение
         else
           {
            //--- Если дошли до минимального ограничения, изменим направление счётчика на противоположное
            if(m_increment_ratio.GetValue()<=m_increment_ratio.MinValue()+1)
               increment_ratio_direction=true;
           }
         //--- Получим текущее значение параметра "Increment ratio" и изменим его по указанному направлению
         int increase_value=(int)m_increment_ratio.GetValue();
         m_increment_ratio.ChangeValue((increment_ratio_direction)? ++increase_value : --increase_value);
        }
     }
//--- Переключим направление на уменьшение массива если дошли до максимума
   if((int)m_series_size.GetValue()>=m_max_limit_size.GetValue())
      resize_direction=true;

//--- Если индикатор выполнения включен, отобразим процесс
   if(m_progress_bar.IsVisible())
     {
      if(!resize_direction)
         m_progress_bar.Update((int)m_series_size.GetValue(),(int)m_max_limit_size.GetValue());
      else
         m_progress_bar.Update(int(m_max_limit_size.GetValue()-m_series_size.GetValue()),(int)m_max_limit_size.GetValue());
     }
//--- Изменяем размер массива по направлению
   int size_of_series=(int)m_series_size.GetValue();
   m_series_size.ChangeValue((!resize_direction)? ++size_of_series : --size_of_series);
//--- Установим новый размер массивам
   ResizeDataArrays();
  }

Анимация графиков, как уже упоминалось ранее, будет осуществляться в таймере. Все необходимые действия расположим в методе CProgram::UpdateLineChartByTimer(). Программа сразу выходит из метода, если (1) форма свёрнута, или если (2) отключены режимы, в которых нужны обновления серий по таймеру. Кроме этого, ещё одним препятствием может выступать здесь задержка в поле ввода Delay. Если все проверки пройдены, то далее осуществляются необходимые расчёты для включенных режимов и обновляются серии на линейном графике.  

class CProgram : public CWndEvents
  {
private:
   //--- Обновление линейного графика по таймеру
   void              UpdateLineChartByTimer(void);
  };
//+------------------------------------------------------------------+
//| Обновление по таймеру                                            |
//+------------------------------------------------------------------+
void CProgram::UpdateLineChartByTimer(void)
  {
//--- Выйти, если форма свёрнута или в процессе перемещения
   if(m_window1.IsMinimized())
      return;
//--- Выйти, если отключена анимация
   if(!m_max_limit_size.CheckButtonState() && !m_run_speed.CheckButtonState())
      return;
//--- Задержка
   static int count=0;
   if(count<m_delay_ms.GetValue())
     {
      count+=TIMER_STEP_MSC;
      return;
     }
   count=0;
//--- Если включена опция "Бегущие серии", то будем смещать первое значение серий
   ShiftLineChartSeries();
//--- Если включено управление размером массивов серий по таймеру
   AutoResizeLineChartSeries();
//--- Инициализируем массивы
   InitArrays();
//--- (1) Рассчитаем и (2) обновим серии
   CalculateSeries();
   UpdateSeries();
  }

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

  • Параметр Number of series (комбобокс). Каждый раз при выборе нового значения в этом элементе количество серий на линейном графике будет изменяться. Блок кода для обработки этого события показан в листинге ниже:

...
//--- Событие выбора пункта в комбо-боксе
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Получим новое количество серий
      m_line_chart.MaxData((int)m_series_total.ButtonText());
      //--- (1) Установим размер массивам и (2) инициализируем их
      ResizeDataArrays();
      InitArrays();
      //--- (1) Рассчитаем, (2) добавим на график и (3) обновим серии
      CalculateSeries();
      AddSeries();
      UpdateSeries();
      return;
     }
...

Например, по умолчанию в комбобоксе установлен показ шести серий (значение 6). Изменим его на 3. Результат показан на скриншоте ниже:

 Рис. 5. Тест изменения количества серий на линейном графике.

Рис. 5. Тест изменения количества серий на линейном графике.

  • Параметры Max. limit size (чекбокс с полем ввода) и Size of series (поле ввода). При нажатии на эти элементы генерируется событие с идентификатором ON_CLICK_LABEL. Если нажали на элементе Size of series, то значение в поле ввода будет сбрасываться до минимального. Нажатие на элементе Max. limit size будет переводить состояние его чекбокса на противоположное. От состояния чекбокса зависит, показать или скрыть индикатор выполнения, который демонстрируется при включении режима автоматического изменения размеров серий на линейном графике. 

...
//--- Событие нажатия на текстовой метке элемента
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
      //--- Если это сообщение от элемента 'Size of series'
      if(sparam==m_series_size.LabelText())
        {
         //--- Перерасчёт серий на графике
         RecalculatingSeries();
         return;
        }
      //--- Если это сообщение от элемента 'Max. Limit Size'
      if(sparam==m_max_limit_size.LabelText())
        {
         //--- Показать или скрыть индикатор выполнения в зависимости от состояния чекбокса элемента 'Max. limit size'
         if(m_max_limit_size.CheckButtonState())
            m_progress_bar.Show();
         else
            m_progress_bar.Hide();
         //---
         return;
        }
     }
...

На скриншоте ниже показан пример, как это выглядит в процессе:

 Рис. 6. Тест линейного графика в режиме авто-изменения размеров серий.

Рис. 6. Тест линейного графика в режиме авто-изменения размеров серий.

  • При вводе значений в поля ввода элементов Increment ratio, Offset series и Size of series вызывается метод CProgram::RecalculatingSeries() для перерасчёта серий (см. листинг кода ниже). 

...
//--- Событие ввода нового значения в поле ввода
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- Если это сообщение от элемента 'Increment ratio' или 'Offset series' или 'Size of series'
      if(sparam==m_increment_ratio.LabelText() ||
         sparam==m_offset_series.LabelText() ||
         sparam==m_series_size.LabelText())
        {
         //--- Перерасчёт серий на графике
         RecalculatingSeries();
         return;
        }
      return;
     }
//--- События нажатия на кнопках-переключателях поля ввода
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC || id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Если это сообщение от элемента 'Increment ratio' или 'Offset series' или 'Size of series'
      if(sparam==m_increment_ratio.LabelText() ||
         sparam==m_offset_series.LabelText() ||
         sparam==m_series_size.LabelText())
        {
         //--- Перерасчёт серий на графике
         RecalculatingSeries();
         return;
        }
      return;
     }
...

На скриншоте ниже показан ещё один пример. Попробуйте установить в своей копии такие же параметры и посмотреть, как это выглядит в режиме анимации.

 Рис. 7. Тест режима «Бегущие серии».

Рис. 7. Тест режима «Бегущие серии».

После внесения изменений в копии классов стандартной библиотеки в область серий линейного графика корректно вмещаются многоэлементные массивы. На скриншоте ниже показан пример, когда размер серий равен тысяче элементов (см. параметр Size of series).

 Рис. 8. Тест серий с большим количеством данных.

Рис. 8. Тест серий с большим количеством данных.

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

 


Заключение

В этой статье были представлены классы кода для создания таких элементов интерфейса, как «Индикатор выполнения» и «Линейный график». 

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

 Рис. 9. Структура библиотеки на текущей стадии разработки.

Рис. 9. Структура библиотеки на текущей стадии разработки.

На этом мы завершаем основную часть серии статей о разработке библиотеки Easy And Fast для создания графических интерфейсов в среде торговых терминалов MetaTrader. Следующие статьи будут посвящены тому, как работать с этой библиотекой. Будет показано множество различных примеров, дополнений и обновлений. Любой при желании может присоединиться к развитию этого проекта. Если Вам понравился результат, тестируйте библиотеку на своих проектах, сообщайте о найденных ошибках и задавайте свои вопросы.

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

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


Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (26)
Anatoli Kazharski
Anatoli Kazharski | 30 авг 2016 в 21:12
Artyom Trishkin:

Анатоль, подскажи, есть ли возможность динамически менять описание процесса в прогресс-баре?

Допустим, имеем некий массив имён символов. Идём в цикле по этому массиву и получаем имена символов. Далее каждый символ обрабатывается программой для получения с него данных. Общий цикл отображается одним прогресс-баром, но хотелось бы ещё отображать процесс обработки каждого отдельного символа. Соответственно, и подписывать название процесса "обрабатываю CMG", потом, при обработке следующего, уже писать другое название процесса: "обрабатываю EQIX", и т.д.

Заранее не известно какие символы могут находиться в массиве - т.е. подготовить кучку прогресс-баров не получится, да и не оптимально это при более тысячи названий различных символов...

Сейчас нет такого метода. В следующем обновлении добавлю такую возможность для каждого элемента, у которого есть текстовое описание. То есть будет возможность изменять текст уже после создания элемента.
Artyom Trishkin
Artyom Trishkin | 30 авг 2016 в 22:48
Anatoli Kazharski:
Сейчас нет такого метода. В следующем обновлении добавлю такую возможность для каждого элемента, у которого есть текстовое описание. То есть будет возможность изменять текст уже после создания элемента.

Спасибо, с нетерпением ждём-с ;)

Вопрос: у тебя в примерах есть такой код построения прогресс-бара:

//+------------------------------------------------------------------+
//| Создаёт прогресс бар                                             |
//+------------------------------------------------------------------+
bool CProgram::CreateProgressBar(const int x_gap,const int y_gap)
  {
//--- Сохраним указатель на форму
   m_progress_bar.WindowPointer(m_window);
//--- Координаты
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;
//--- Установим свойства перед созданием
   m_progress_bar.YSize(15);
   m_progress_bar.BarYSize(11);
   m_progress_bar.BarXOffset(65);
   m_progress_bar.BarYOffset(2);
   m_progress_bar.BarBorderWidth(1);
   m_progress_bar.LabelText("Processing:");
   m_progress_bar.AreaColor(C'225,225,225');
   m_progress_bar.BarAreaColor(clrWhiteSmoke);
   m_progress_bar.BarBorderColor(clrWhiteSmoke);
   m_progress_bar.IsDropdown(true);
   m_progress_bar.AutoXResizeMode(true);
   m_progress_bar.AutoXResizeRightOffset(230);
//--- Создание элемента
   if(!m_progress_bar.CreateProgressBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Скрыть элемент
   m_progress_bar.Hide();
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_progress_bar);
   return(true);
  }
//+------------------------------------------------------------------+

для чего нужен признак выпадающего элемента:

m_progress_bar.IsDropdown(true);

???

Anatoli Kazharski
Anatoli Kazharski | 31 авг 2016 в 07:34
Artyom Trishkin:

Вопрос: у тебя в примерах есть такой код построения прогресс-бара:

для чего нужен признак выпадающего элемента:

???

Для исключения показа прогресс-бара при разворачивании формы, когда его не должно быть видно. 
Artyom Trishkin
Artyom Trishkin | 31 авг 2016 в 18:50
Anatoli Kazharski:
Для исключения показа прогресс-бара при разворачивании формы, когда его не должно быть видно. 
Это относится к любым объектам, которые можно построить при помощи твоей библиотеки?
Anatoli Kazharski
Anatoli Kazharski | 1 сен 2016 в 11:55
Artyom Trishkin:
Это относится к любым объектам, которые можно построить при помощи твоей библиотеки?
Да. Метод IsDropdown() находится в базовом классе CElement и доступен для всех элементов графического интерфейса. 
Какие проверки должен пройти торговый робот перед публикацией в Маркете Какие проверки должен пройти торговый робот перед публикацией в Маркете

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

Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1) Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1)

Этой статьей мы открываем девятую часть серии о разработке библиотеки для создания графических интерфейсов в среде торговых терминалов MetaTrader. Она состоит из двух глав, в которых представлены новые элементы управления и интерфейса: «Палитра для выбора цвета», «Кнопка для вызова цветовой палитры», «Индикатор выполнения» и «Линейный график».

Тестирование торговых стратегий на реальных тиках Тестирование торговых стратегий на реальных тиках

В данной статье мы покажем результаты тестирования простой торговой стратегии в 3-х режимах: "OHLC на M1", "Все тики" и "Каждый тик на основе реальных тиков" с использованием записанных тиков из истории.

Графические интерфейсы X: Обновления для библиотеки Easy And Fast (build 2) Графические интерфейсы X: Обновления для библиотеки Easy And Fast (build 2)

С момента предыдущей публикации статьи этой серии, библиотека Easy And Fast пополнилась новыми возможностями. Проведена частичная оптимизация схемы и кода библиотеки, что немного сократило потребление ресурсов CPU. Некоторые повторяющиеся методы во многих классах элементов были перенесены в базовый класс CElement.