English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Разработка и реализация новых виджетов на основе класса CChartObject

Разработка и реализация новых виджетов на основе класса CChartObject

MetaTrader 5Индикаторы | 17 ноября 2010, 15:58
3 407 1
investeo
investeo

Введение

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

В этой статье я опишу процесс использования классов Стандартной библиотеки для объектов пользовательского интерфейса, мы рассмотрим применение новых классов CChartObjectProgressBar, CChartObjectSpinner и CChartEditTable, созданных на основе класса CChartObjectEdit. В классе CChartEditTable используется динамический двумерный массив объектов, это работающий пример реализации работы с двумерными динамическими массивами в MQL5.

 

1. Класс CChartObject и его потомки

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

Объекты создаются при помощи функции ObjectCreate(), тип создаваемого объекта и его свойства задаются в параметрах функции. Все объекты на графике имеют свойства, которые могут быть типа Integer, Double или String. Установка и получение свойств объектов производится  при помощи специальных функций ObjectGetInteger(), ObjectSetInteger(), ObjectGetDouble(), ObjectSetDouble(), ObjectGetString() и ObjectSetString(). Также есть функции подсчета, перемещения и удаления объектов на любом графике.

В MQL5 при использовании объектно-ориентированного подхода работа с различными объектами на графике производится при помощи класса CChartObject и его потомков.

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

 

Рисунок 1. Класс CChartObject и его потомки 

Рисунок 1. Класс CChartObject и его потомки


Как мы видим, на рисунке есть несколько классов, отмеченных маленьким треугольником в правом нижнем углу.

Возможности основных классов расширяются в классах-потомках путем добавления новых переменных и методов обработки объекта. В классах-потомках методы создания и возврата типа объекта Create() и Type() могут различаться.

Рассмотрим это на примере: класс CChartObjectTrend является родительским классом для классов CChartObjectTrendByAngle, CChartObjectChannel, CChartObjectStdDevChannel, CChartObjectRegression и CChartObjectPitchfork.

Класс CChartObjectTrend является базовым классом для объектов со свойствами OBJPROP_RAY_RIGHT и OBJPROP_RAY_LEFT, выглядит он следующим образом:

class CChartObjectTrend : public CChartObject
  {
public:
   //--- методы доступа к свойствам объекта
   bool              RayLeft() const;
   bool              RayLeft(bool new_sel);
   bool              RayRight() const;
   bool              RayRight(bool new_sel);
   //--- методы создания объекта
   bool              Create(long chart_id,string name,int window,
                                datetime time1,double price1,datetime time2,double price2);
   //--- методы идентификации объекта
   virtual int       Type() const { return(OBJ_TREND); }
   //--- методы работы с файлами
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
  };

Перед определением методов есть комментарии, позволяющие различать предназначение методов.

Функции RayLeft() и RayRight() являются методами доступа к свойствам объекта. Их реализация заключается в вызове методов ObjectGetInteger() и ObjectSetInteger() объекта CChartObjectTrend.

bool CChartObjectTrend::RayLeft(bool new_ray)
  {
//--- проверка
   if(m_chart_id==-1) return(false);
//---
   return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,new_ray));
  }

Метод Create() отвечает за создание и присоединение объекта к графику.

Он вызывает метод ObjectCreate(), указывая тип объекта OBJ_TREND в качестве одного из параметров:

bool CChartObjectTrend::Create(long chart_id,string name,int window,
                                   datetime time1,double price1,datetime time2,double price2)
  {
   bool result=ObjectCreate(chart_id,name,OBJ_TREND,window,time1,price1,time2,price2);
   if(result) result&=Attach(chart_id,name,window,2);
//---
   return(result);
  }

Методы Save() и Load() сохраняют и загружают данные объекта на диск при помощи функций FileWriteInteger() и FileLoadInteger().

bool CChartObjectTrend::Save(int file_handle)
  {
   bool result;
//--- проверка
   if(file_handle<=0) return(false);
   if(m_chart_id==-1) return(false);
//--- запись
   result=CChartObject::Save(file_handle);
   if(result)
     {
      //--- пишем в файл значение свойства "Ray left"
      if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name,
                                                        OBJPROP_RAY_LEFT),CHAR_VALUE)!=sizeof(char))
      return(false);
      //--- пишем в файл значение свойства "Ray right"
      if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name, 
                                                                OBJPROP_RAY_RIGHT),CHAR_VALUE)!=sizeof(char))
       return(false);
     }
//---
   return(result);
  }

bool CChartObjectTrend::Load(int file_handle)
  {
   bool result;
//--- проверка
   if(file_handle<=0) return(false);
   if(m_chart_id==-1) return(false);
//--- чтение
   result=CChartObject::Load(file_handle);
   if(result)
     {
      //--- читаем значение свойства "Ray left"
      if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,
                                                 FileReadInteger(file_handle,CHAR_VALUE)))return(false);
      //--- читаем значение свойства "Ray right"
      if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_RIGHT,
                                                 FileReadInteger(file_handle,CHAR_VALUE))) return(false);
     }
//---
   return(result);
  }

Рассмотрим кратко определения потомков класса CChartObjectTrend.

В класс CChartObjectTrendByAngle добавлено свойство Angle(), а в качестве типа в функции Type() возвращается OBJ_TRENDBYANGLE:

class CChartObjectTrendByAngle : public CChartObjectTrend
  {
public:
   //--- методы доступа к свойствам объекта
   double            Angle() const;
   bool              Angle(double angle);
   //--- методы создания объекта
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2);
   //--- методы идентификации объекта
   virtual int       Type() { return(OBJ_TRENDBYANGLE); }
  };

Класс CChartObjectChannel при идентификации возвращает тип OBJ_CHANNEL, он работает с каналами, поэтому при создании в метод Create() ему передаются три параметра цена/время:

class CChartObjectChannel : public CChartObjectTrend
  {
public:
   //--- метод создания объекта
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2,datetime time3,double price3);
   //--- метод идентификации объекта
   virtual int       Type() const { return(OBJ_CHANNEL); }
  };

В класс CChartObjectStdDevChannel добавлен дополнительный параметр параметр deviation, указываемый при создании в методе Create() и модификаторы этого параметра Deviations():

class CChartObjectStdDevChannel : public CChartObjectTrend
  {
public:
   //--- методы доступа к свойствам объекта
   double            Deviations() const;
   bool              Deviations(double deviation);
   //--- методы создания объекта
   bool              Create(long chart_id,string name,int window,
                           datetime time1,datetime time2,double deviation);
   //--- методы идентификации объекта
   virtual int       Type() const { return(OBJ_STDDEVCHANNEL); }
   //--- методы работы с файлами
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
  };

Класс CChartObjectRegression создает трендовую линию регресии, у него переопределены лишь методы Create() и Type() родительского класса CChartObjectTrend:

class CChartObjectRegression : public CChartObjectTrend
  {
public:
   //--- метод создания объекта
   bool              Create(long chart_id,string name,int window,datetime time1,datetime time2);
   //--- метод идентификации объекта
   virtual int       Type() const { return(OBJ_REGRESSION); }
  };

Класс CChartObjectPitchfork отвечает за работу с объектом типа "вилы Эндрюса", здесь изменились лишь методы Create() и Type().

class CChartObjectPitchfork : public CChartObjectTrend
  {
public:
   //--- метод создания объекта
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2,datetime time3,double price3);
   //--- метод идентификации объекта
   virtual int       Type() const { return(OBJ_CHANNEL); }
  };

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

  • меняется метод создания объекта Create();
  • меняется метод идентификации объекта Type();
  • добавляются новые методы доступа к свойствам.

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

Перед тем, как пойти дальше,  давайте рассмотрим использование методов класса CChartObject в графических объектах.

Вместо того, чтобы использовать семейство методов ObjectSet/ObjectGet и свойств объекта, достаточно объявить объект типа CChartObject или его потомка и вызывать методы, которые изменяют соответствующие свойства. Для упрощения приведем пример обыкновенной надписи (Label):

Код:

void OnStart()
  {
//---
   string label_name="my_OBJ_LABEL_object";
   if(ObjectFind(0,label_name)<0)
     {
      Print("Объект ",label_name," не найден. Код ошибки = ",GetLastError());
      ObjectCreate(0,label_name,OBJ_LABEL,0,0,0);           
      ObjectSetInteger(0,label_name,OBJPROP_XDISTANCE,200);
      ObjectSetInteger(0,label_name,OBJPROP_YDISTANCE,300);
      ObjectSetInteger(0,label_name,OBJPROP_COLOR,White);
      ObjectSetString(0,label_name,OBJPROP_TEXT,UP);
      ObjectSetString(0,label_name,OBJPROP_FONT,"Wingdings");
      ObjectSetInteger(0,label_name,OBJPROP_FONTSIZE,10);
      ObjectSetDouble(0,label_name,OBJPROP_ANGLE,-45);
      ObjectSetInteger(0,label_name,OBJPROP_SELECTABLE,false);
      ChartRedraw(0);                                      
     }
  }

можно реализовать при помощи объектно-ориентированного подхода:

1. Объявляем объект типа CChartObjectLabel:

CChartObjectLabel label;

2. Производим над ним нужные действия: 

int OnInit()
  {
//---
   label.Create(0, label_name, 0, 0);
   label.X_Distance(200);
   label.Y_Distance(300);
   label.Color(White);
   label.Description(UP);
   label.Font("Wingdings");
   label.FontSize(10);
   label.Angle(-45);
   label.Selectable(false);
//---
   return(0);
  }

Как видите, главным отличием является то, что мы теперь не используем строку label_name:

string label_name="my_OBJ_LABEL_object";

и не вызываем функции ObjectSetInteger(), ObjectGetInteger(), ObjectSetDouble() и ObjectGetDouble() с этой строкой в качестве параметра. Вместо этого мы объявляем объект класса CChartObjectLabel и используем его методы. Это не только упрощает сам код, но и значительно ускоряет его написание.

Редактор MetaEditor 5 предоставляет функционал выбора свойств/методов объекта при добавления точки (.) к экземпляру класса. Теперь нет необходимости постоянно листать справку по MQL5 в поисках свойств при установке и получении необходимых свойств объекта.

Аналогично, для получения или установки свойств "луч влево" и "луч вправо" класса CChartObjectTrend, рассмотренного выше, достаточно объявить объект класса CChartObjectTrend и вызвать методы RayRight() или RayLeft():

CChartObjectTrend trendline;
trendline.RayRight(true); 


2. Класс ProgressBar

Первым виджетом, который сделаем, будет ProgressBar. Полоса ProgressBar показывает состояние выполнения некоторой операции, от 0 до x процентов.

Для общности, давайте не будем ограничивать его максимальным значением 100, пусть это будет любое положительное целое значение. Нам понадобится цветная полоса, которая будет изменяться в размерах в соответствии со значением состояния. Первая мысль, которая приходит в голову - использование двух прямоугольников, но я выбрал другой путь: использование двух объектов типа CChartObjectEdit различного цвета, расположенных один внутри другого.

Это упрощает написание кода и добавление текстовой строки, которая может быть помещена в полосу для показа значения. Индикаторы ProgressBar могут быть горизонтальными либо вертикальными в зависимости от того, что требуется показать.


2.1. Реализация класса ProgressBar

Класс CChartObjectProgress унаследован от класса CChartObjectEdit.

Я добавил в него private-переменные для хранения значений и их ограничений: m_value, m_min, m_max.

Ориентация полосы ProgressBar определяется значением переменной m_direction типа integer. Цвет хранится в переменной m_color. Метод идентификации возвращает тип OBJ_EDIT, поскольку для нашем случае мы его не используем.

Отметим, что переменная m_bar типа CChartObjectEdit объявлена внутри определения класса - это внутренняя полоса, которая изменяет свой размер в зависимости от значения переменной m_value. Дополнительные переменные m_name и m_chart хранят внутренние значения переменной m_bar.

class CChartObjectProgressBar : public CChartObjectEdit
  {
private:
   int               m_value;
   int               m_min;
   int               m_max;
   int               m_direction;
   color             m_color;
   CChartObjectEdit  m_bar;
   string            m_name;
   long              m_chart_id;

public:
   int               GetValue();
   int               GetMin();
   int               GetMax();

   void              SetValue(int val);
   void              SetMin(int val);
   void              SetMax(int val);

   void              SetColor(color bgcol,color fgcol);
   bool              Create(long chart_id,string name,int window,int X,int Y,
                           int sizeX,int sizeY,int direction);

   //--- метод идентификации объекта
   virtual int       Type() const { return(OBJ_EDIT); }
};

Метод Create() создает объект ProgressBar и присоединяет его к графику.

Как видно, в случае вертикальной ориентации переменная Y вычитается из переменной sizeY, т.к. обычно объект класса CChartObjectEdit рисуется сверху вниз, а я хочу рисовать внутренний прямоугольник снизу вверх:

bool CChartObjectProgressBar::Create(long chart_id,string name,int window,int X,int Y,
                                          int sizeX,int sizeY,int direction=0)
  {
   bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0);

   m_name=name;
   m_chart_id=chart_id;
   m_direction=direction;

   if(direction!=0)
     {
      Y=Y-sizeY;
     }

   ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_COLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true);

   result&=m_bar.Create(chart_id,name+"m_bar",window,X,Y,sizeX,sizeY);
   m_bar.Color(White);
   m_bar.ReadOnly(true);
   m_bar.Selectable(false);

//---
   if(result) result&=Attach(chart_id,name,window,1);
   result&=X_Distance(X);
   result&=Y_Distance(Y);
   result&=X_Size(sizeX);
   result&=Y_Size(sizeY);
//---
   return(result);
  }

Метод SetColor() устанавливает цвет обоих прямоугольников и их фона:

void CChartObjectProgressBar::SetColor(color bgCol,color fgCol=White)
  {
   m_color=bgCol;
   m_bar.BackColor(m_color);
   m_bar.Color(fgCol);
  }

Метод SetValue() отвечает за установку значения переменной m_val и пересчет размера внутреннего прямоугольника.

Для полос с горизонтальной и вертикальной ориентацией расчет размера производится по-разному:

void CChartObjectProgressBar::SetValue(int val)
  {
   if(m_direction==0) // горизонтальный ProgressBar
     {
      double sizex=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_XSIZE,0);

      double stepSize=sizex/(m_max-m_min);

      m_value=val;
      m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(),
                   m_bar.X_Distance(),m_bar.Y_Distance(),(int)MathFloor(stepSize*m_value),m_bar.Y_Size());
        } 
    else
      {
      double sizey=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_YSIZE,0);

      double stepSize=sizey/(m_max-m_min);
      m_value=val;
      m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(),
                   m_bar.X_Distance(),(int)(this.Y_Distance()+sizey-MathFloor(stepSize*m_value)),
                   m_bar.X_Size(),(int)MathFloor(stepSize*m_value));

     }

   m_bar.Description(IntegerToString(m_value));
  }


2.2. Демонстрация использования класса ProgressBar

Поскольку мы реализовали класс CChartObjectProgressBar, пришло время увидеть его в действии.

Для помещения нового Progress Bar на график достаточно объявить объект класса CChartObjectProgressBar, создать его методом Create() и вызвать соответствующие методы установки свойств:

progressBar.Create(0, "progressBar1", 0, 10, 10, 200, 40);
progressBar.SetColor(YellowGreen);
progressBar.SetMin(0);
progressBar.SetMax(100);
progressBar.SetValue(0);

Я написал демонстрационный советник, который размещает на графике шесть различных объектов ProgressBar и меняет их свойства при клике мышкой на любой из этих объектов графика:

Полный исходный код этого и других демонстрационных примеров находятся в приложении, ролик приведен ниже:

 


3. Класс Spinner

Виджет Spinner представляет собой поле ввода с двумя кнопками.

Они используются для увеличения или уменьшения значения поля ввода при нажатии на одну из кнопок.

При разработке данного объекта я хотел ограничиваться лишь целочисленными значениями, поэтому объект Spinner был разработан на базе значений типа double. У него также есть возможность задавать размер шага, т.е. число, на которое будет увеличиваться или уменьшаться текущее значение.  Он также должен иметь максимальные и минимальные значения в качестве ограничителей.


3.1. Реализация класса Spinner

В MQL5 есть классы CChartObjectEdit и CChartObjectButton, которые могут быть объединены в один класс CChartObjectSpinner. Этот класс наследован от класса CChartObjectEdit и содержит два закрытых (private) члена - объекты класса CChartObjectButton.

Ограничения для минимального и максимального значения переменной m_value хранятся в членах класса m_min и m_max, а точность (до n-го десятичного знака) хранится в переменной m_precision. Нам потребуются методы для доступа к текущему значению, установки шага изменения значения и установки значения переменной m_value.

class CChartObjectSpinner: public CChartObjectEdit
  {

private:
   double            m_value;
   double            m_stepSize;
   double            m_min;
   double            m_max;
   int               m_precision;
   string            m_name;
   long              m_chart_id;
   CChartObjectButton m_up,m_down;

public:
   double            GetValue();
   double            GetMin();
   double            GetMax();

   void              SetValue(double val);
   void              SetMin(double val);
   void              SetMax(double val);

   double            Inc();
   double            Dec();

   bool              Create(long chart_id,string name,int window,int X,int Y,
                               int sizeX,int sizeY,double val,double stepSize,int precision);

   //--- метод идентификации объекта
   virtual int       Type() const { return(OBJ_EDIT); }
   
  };

Метод Create() создает новый объект CChartObjectSpinner и присоединяет его к графику.

Два объекта класса CChartObjectButtons создаются на графике справа от объекта класса CChartObjectEdit, высота каждого из них составляет половину высоты объекта CChartObjectEdit.

Кнопки инкремента и декремента обозначены знаками "+" и "-".

bool CChartObjectSpinner::Create(long chart_id,string name,int window,int X,int Y,
                                     int sizeX,int sizeY,double val=0.0,double stepSize=1.0,int precision=8)
  {
   bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0);

   m_name=name;
   m_chart_id=chart_id;
   m_value=val;
   m_stepSize=stepSize;
   m_precision=precision;

   ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_COLOR,Black);
   ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true);

   result&=m_up.Create(chart_id, name+"_up", window, X+sizeX, Y, 15, sizeY/2);
   result&=m_down.Create(chart_id, name+"_down", window, X+sizeX, Y+sizeY/2, 15, sizeY/2);
   m_up.Description("+");
   m_down.Description("-");
   ObjectSetString(chart_id,name,OBJPROP_TEXT,0,(DoubleToString(m_value,precision)));

//---
   if(result) result&=Attach(chart_id,name,window,1);
   result&=X_Distance(X);
   result&=Y_Distance(Y);
   result&=X_Size(sizeX);
   result&=Y_Size(sizeY);
//---
   return(result);
  }

Метод SetValue() устанавливает значение закрытой переменной m_value только в том случае, если оно находится в диапазоне от m_min до m_max.

void CChartObjectSpinner::SetValue(double val)
  {
   if(val>=m_min && val<=m_max) m_value=val;
   this.Description(DoubleToString(m_value));
  }

Метод Inc() увеличивает значение на заданный шаг, но не более чем до значения m_max.

Отметим, что при сравнении значений до указанной точности я использовал функцию NormalizeDouble().

double CChartObjectSpinner::Inc(void)
  {
   if(NormalizeDouble(m_max-m_value-m_stepSize,m_precision)>0.0) m_value+=m_stepSize;
   else m_value=m_max;
   this.Description(DoubleToString(m_value, m_precision));
   m_up.State(false);
   return m_value;
  }

Метод Dec() уменьшает значение на заданную величину шага, но не менее чем до значения, указанного в переменной m_min.

double CChartObjectSpinner::Dec(void)
  {
   if(NormalizeDouble(m_value-m_stepSize-m_min,m_precision)>0.0)
      m_value-=m_stepSize; else m_value=m_min;
   this.Description(DoubleToString(m_value,m_precision));
   m_down.State(false);

   return m_value;
  }


3.2. Демонстрация использования класса Spinner

Пришло время протестировать объекты класса Spinner.

Для их использования достаточно объявить объект класса CChartObjectSpinne и использовать методы Create(), SetMin() и SetMax().

   spinner.Create(0, "spinner1", 0, 10, 10, 200, 40, 0.0, 0.4);
   spinner.SetMin(0);
   spinner.SetMax(100);

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

Это производится внутри функции OnChartEvent():

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Проверяем событие - нажатие на кнопку мыши
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
     
     if (sparam=="spinner1_up") spinner.Inc();
     if (sparam=="spinner1_down") spinner.Dec();
     if (sparam=="spinner2_up") spinner2.Inc();
     if (sparam=="spinner2_down") spinner2.Dec();
     if (sparam=="spinner3_up") spinner3.Inc();
     if (sparam=="spinner3_down") spinner3.Dec();
     
     label.Description(DoubleToString(NormalizeDouble(spinner.GetValue()+spinner2.GetValue()+spinner3.GetValue(),10),10));
   
   ChartRedraw();
     }
  }

Посмотрите ролик:

 

 

4. Класс CChartObjectEditTable

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

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

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


4.1. Реализация класса CChartObjectEditTable

Класс CChartObjectEditTable содержит указатель CArrayObj  на двумерный массив объектов, переменные класса m_rows и m_columns хранят число строк и столбцов таблицы.

В качестве префикса всех ячеек таблицы (объектов типа CChartObjectEdit) используется строковая переменная m_baseName. Методы GetColor(), SetColor(), GetText() и SetText() предназначены для установки и получения цвета и текста любой указанной ячейки. Метод Delete() удаляет все объекты, созданные при помощи метода Create().

class CChartObjectEditTable
  {

private:
   CArrayObj        *array2D;
   int               m_rows;
   int               m_cols;
   string            m_baseName;

public:

   bool              Create(long chart_id,string name,int window,int rows,int cols,int startX,int startY,
                                int sizeX,int sizeY,color Bg,int deltaX,int deltaY);
   bool              Delete();
   bool              SetColor(int row,int col,color newColor);
   color             GetColor(int row,int col);
   bool              SetText(int row,int col,string newText);
   string            GetText(int row,int col);


  };

В методе Create() создается двумерная динамическая таблица объектов класса CChartObjectEdit.

Обратите внимание на способ создания двумерных массивов в MQL5: сначала мы объявляем указатель на двумерный массив, затем заполняем массив объектами класса CArrayObj(), тем самым мы создаем массивы в массиве. Все массивы можно представить в виде столбцов таблицы.

Каждый столбец содержит строки с объектами класса CChartObjectEdit, каждый из них является отображаемой ячейкой таблицы.

bool CChartObjectEditTable::Create(long chart_id,string name,int window,int rows=1,int cols=1,
                                       int startX=0,int startY=0,int sizeX=15,int sizeY=15,
                                  color Bg=White,int deltaX=5,int deltaY=5)
  {
   m_rows=rows;
   m_cols=cols;
   m_baseName=name;
   int i=0,j=0;

   array2D=new CArrayObj();
   if (array2D==NULL) return false;
   
   for(j=0; j<m_cols; j++)
     {
      CArrayObj *new_array=new CArrayObj();
      if (array2D==NULL) return false;
   
      array2D.Add(new_array);
      for(i=0; i<m_rows; i++)
        {
         CChartObjectEdit *new_edit=new CChartObjectEdit();

         new_edit.Create(chart_id, name+IntegerToString(i)+":"+IntegerToString(j), window, 
                         startX+j*(sizeX+deltaX), startY+i*(sizeY+deltaY), sizeX, sizeY);
         new_edit.BackColor(Bg);
         new_edit.Color(White);
         new_edit.Selectable(false);
         new_edit.ReadOnly(true);
         new_edit.Description("");
         new_array.Add(new_edit);
        }
     }

   return true;
  }

Метод SetColor() устанавливает цвет любой ячейки. Сначала он находит массив столбцов, а затем n-й элемент этого массива.

Затем значение цвета данного элемента изменяется путем вызова метода BackColor():

bool CChartObjectEditTable::SetColor(int row,int col,color newColor)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         element.BackColor(newColor);

         return true;
        }
     }

   return false;
  }

Алгоритм метода GetColor() аналогичный методу нахождения ячейки, реализованного в SetColor(), отличие лишь в том, что он возвращает значение цвета любой заданной ячейки.

color CChartObjectEditTable::GetColor(int row,int col)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         return element.BackColor();
        }
     }

   return NULL;
  }

Метод SetText() находит элемент и устанавливает значение текстовой надписи вызовом метода Description().

bool CChartObjectEditTable::SetText(int row,int col,string newText)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         element.Description(newText);

         return true;
        }
     }

   return false;
  }

Метод Delete() удаляет все объекты, созданные методом Create().

Сначала он очищает все массивы столбцов а затем удаляет из памяти объект array2D.

bool CChartObjectEditTable::Delete(void)
  {
   for(int j=0; j<m_cols; j++)
     {
      CArrayObj *column_array=array2D.At(j);
      column_array.Clear();
      delete column_array;
     }
   delete array2D;
   return true;
  }


4.2. Демонстрация использования класса CChartObjectEditTable

Для использования виджета CChartObjectEditTable необходимо объявить объект CChartEditTable и использовать метод Create() с параметрами, определяющими количество строк и столбцов таблицы.

Затем при помощи модификаторов свойств мы можем изменять цвет и текст любой ячейки.

table.Create(0,"t",0,1,10,10,10,15,15,Yellow);
table.SetColor(2,2,Red);
table.SetText(2,2,"2");

Пожалуйста посмотрите скрипт, который иллюстрирует возможности использования объекта CChartObjectEditTable.

Исходный код скрипта находится в приложении к статье.

 


Выводы

В данной статье я описал процесс создания новых виджетов на базе класса CChartObject.

Использовать разработанные виджеты очень просто, достаточно добавить всего несколько строк кода.

Для этого необходимо включить файл ChartObjectsExtControls.mqh в код советника или индикатора.


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/196

Прикрепленные файлы |
progressbarea.mq5 (3.67 KB)
spinnerdemoea.mq5 (2.83 KB)
edittabledemo.mq5 (4.15 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
---
--- | 17 нояб. 2010 в 16:41

Отличная демонстрация!!!



И очень актуальна для текущего конкурса! Обязательно участвуйте!

Димитар Манов:"На Чемпионате я боюсь только исключительных обстоятельств"(ATC 2010) Димитар Манов:"На Чемпионате я боюсь только исключительных обстоятельств"(ATC 2010)
В недавнем отчете Бориса Одинцова одним из самых стабильных советников Чемпионата был назван эксперт болгарского Участника Manov. Мы решили поговорить с автором этой разработки и выяснить, в чем заключается секрет ее успеха. В интервью Димитар Манов рассказывает о том, какую ситуацию его эксперт не переживет, почему он не использует индикаторы и рассчитывает ли на победу в соревновании.
Как использовать торговые классы Стандартной библиотеки при написании советника Как использовать торговые классы Стандартной библиотеки при написании советника
В статье рассказывается о том, как использовать основной функционал торговых классов Стандартной библиотеки при написании советников, в которых применяется открытие, закрытие и модификация позиции, проверка свободной маржи перед размещением торговых ордеров, размещение и удаление отложенных ордеров. Показано, как использовать торговые классы для получения свойств ордеров и сделок.
Параллельные вычисления в MetaTrader 5 штатными средствами Параллельные вычисления в MetaTrader 5 штатными средствами
Время является неизменной ценностью на протяжении всей истории человечества, и мы стремимся не расходовать его понапрасну. Из этой статьи вы узнаете, как можно ускорить работу вашего эксперта, если у вашего компьютера многоядерный процессор. Причем, реализация описываемого метода не требует знания каких-либо еще языков кроме MQL5.
Александр Ануфренко: "Знал бы, где упасть - перинку бы подстелил" (ATC 2010) Александр Ануфренко: "Знал бы, где упасть - перинку бы подстелил" (ATC 2010)
Рискованная разработка Александра Ануфренко (Anufrenko321) в течение трех недель не покидала первую тройку Чемпионата. Пережив на прошлой неделе чудовищный стоп-лосс, эксперт потерял около $60 000, но сейчас вновь подбирается к лидирующим позициям. Автор этого интересного эксперта решил рассказать о принципах работы и особенностях своего детища.