(MQL) 2.6 Тип объекта. Класс «ETypeCode»

По | 25 июля, 2021
тип объекта

В библиотеке языка «MQL5» имеется класс «CList«. Он является классом динамического списка объектов. В иных языках программирования (например: Java, C#, ) такие списки называют коллекциями. Коллекции чем-то похожи на массивы, но с одним отличием. Массивы это набор однотипных данных, а коллекции могут содержать в себе набор значений как одного, так и различных типов.

Главное отличие списка «CList» от коллекции состоит в том, что он формируется из экземпляров класса «CObject» и/или его наследников. Количество таких объектов может исчисляться тысячами. Как тогда понять к какому типу относится тот или иной объект? Как отличить графический объект от списка отложенных заявок или от открытой заявки. А как отличить один графический объект (например: кнопка) от другого (например: поле ввода)?

Тип объекта

Если вы работали с другими языками программирования, то наверняка встречались с операторами «instanceof» или «typeof», которые помогали определить принадлежность объекта к тому или иному типу. В языке «MQL5» таких операторов нет. Поэтому мы напишем класс «ETypeCode», который возьмёт на себя эту функцию.

Для начала добавим новое перечисление. Для этого в конце файла «Enums.mqh» добавим:

//Все числовые значения этого перечисления должны быть отличны от 0
enum ENUM_OBJECT_TYPE_CODE
{
// Тип объекта "Узел"
 ENUM_OBJECT_NODE = 512,

 // Тип объекта "Список"
 ENUM_OBJECT_LIST = 1024,

 // Тип объекта "Графический элемент"
 ENUM_OBJECT_GRAPHIC = 1536,

 // Тип объекта "Заявка"
 ENUM_OBJECT_RECORD = 2048,

 // Тип объекта "Холст"
 ENUM_OBJECT_CANVAS = 2560,

 // Тип объекта "Событие"
 ENUM_OBJECT_EVENT = 3072,

 // Тип объекта "Время"
 ENUM_OBJECT_TIME = 3584
}

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

Например все объекты, со значением определителя равным «1536», являются графическими. Все объекты, со значением определителя равным «3584», являются объектами времени. И так далее. Все эти типы являются общими.

Каждый тип объекта может иметь несколько подтипов, а точнее 512. Так все объекты, у которых значения определителя типа лежат в диапазоне от «1536» до «2048», относятся к типу «графические». А вот к какому подтипу относится объект (кнопка, или поле ввода, или чекбокс) зависит уже от программиста.

Вот пример перечисления с подтипами:

enum ENUM_OBJECT_TYPE_CODE
{
 // Тип объекта"Узел"
 ENUM_OBJECT_NODE = 512,

 // Тип объекта"Список"
 ENUM_OBJECT_LIST = 1024,
 // Тип объекта "Список". Подтип "Открытые ордера" 
 ENUM_OBJECT_LIST_OPEN = 1030,
 // Тип объекта "Список". Подтип "Закрытые ордера"
 ENUM_OBJECT_LIST_CLOSE = 1035,
 // Тип объекта "Список". Подтип "Отложенные ордера"
 ENUM_OBJECT_LIST_PENDING = 1040,

 // Тип объекта "Графический элемент"
 ENUM_OBJECT_GRAPHIC = 1536,
// Тип объекта "Графический элемент". Подтип "Кнопка"
 ENUM_OBJECT_GRAPHIC_BUTTON = 1540,
// Тип объекта "Графический элемент". Подтип "Кнопка BUY"
 ENUM_OBJECT_GRAPHIC_BUTTON_BUY = 1541,
// Тип объекта "Графический элемент". Подтип "Кнопка SELL"
 ENUM_OBJECT_GRAPHIC_BUTTON_SELL = 1542,
// Тип объекта "Графический элемент". Подтип "Поле ввода"
 ENUM_OBJECT_GRAPHIC_EDIT = 1550,

 // Тип объекта "Заявка"
 ENUM_OBJECT_RECORD = 2048,

 // Тип объекта "Холст"
 ENUM_OBJECT_CANVAS = 2560,

 // Тип объекта "Событие"
 ENUM_OBJECT_EVENT = 3072,

 // Тип объекта "Время"
 ENUM_OBJECT_TIME = 3584
}

Данное перечисление можно дополнять или менять как вам захочется. Теперь перейдём к классу «ETypeCode».

Класс «ETypeCode»

Для начала добавим папку «TypeCode» в наш проект. Там создадим файл «ETypeCode.mqh».

ETypeCode

В нём прописываем следующее:

include "....\Helpers\Prepare.mqh"

class ETypeCode
{

};

Как видите к данному файлу мы подключаем файл «Prepare.mqh«. Ранее он подключался к «ETime.mqh«. Теперь нам надо открыть файл»ETime.mqh». Подключить файл «ETypeCode.mqh» и добавить переменную «codeType», которая будет хранить тип объекта.

include "..\TypeCode\ETypeCode.mqh"

class ETime
{
private:

DataTime eTimeCustom;
ulong codeType;

bool              Calculate(datetime tm=0);
bool              Init(datetime tm){ 
                            if(tm>0)Time(tm);
                            if(eTimeCustom.time==tm)return(true); 
                            return(false);
                  }


 public:
                  ETime();                 
                 ~ETime(); 
 ulong             getType()const{return (codeType);}
 void              setType(ulong set){codeType = set;} 

Затем добавляем методы «getType()» и «setType()». Теперь в начале конструктора класса «ETime» добавляем две строчки:

ETime::ETime()
{

//+---
// code - это экземпляр объекта "ECodeType". Он объявляется в файле "ECodeType.mqh"
code.Init(ENUM_OBJECT_TIME);
codeType = code.getType();

//+---
eTimeCustom.seconds = 0;
eTimeCustom.minutes = 0;
eTimeCustom.hours = 0;
eTimeCustom.days = 0;
eTimeCustom.weeks = 0;
eTimeCustom.months = 0;
eTimeCustom.years = 0;
eTimeCustom.mqlDateTime.sec = 0;
eTimeCustom.mqlDateTime.min = 0;
eTimeCustom.mqlDateTime.hour = 0;
eTimeCustom.mqlDateTime.day = 0;
eTimeCustom.mqlDateTime.day_of_week = 0;
eTimeCustom.mqlDateTime.day_of_year = 0;
eTimeCustom.mqlDateTime.mon = 0;
eTimeCustom.mqlDateTime.year = 0;
eTimeCustom.offset.inSeconds = 0;
eTimeCustom.offset.inMinutes = 0;
eTimeCustom.offset.inHours = 0;
eTimeCustom.lastDay = 0;
eTimeCustom.lastHour = 0;
eTimeCustom.lastMinute = 0;
eTimeCustom.lastMonth = 0;
eTimeCustom.lastWeek = 0;
eTimeCustom.lastYear = 0;

//+---
}

На этом изменения файла «ETime.mqh» закончены. Теперь возвращаемся к классу «ETypeCode». В нём объявляем следующие переменные и методы

class ETypeCode
   {
 private:
    string            terminal;
    string            program_type;
    string            program_name;
    string            object_type;  

 public:
                      ETypeCode();
                     ~ETypeCode(void);
// Возвращает версию терминала (в виде числа). 
 int               getTerminalVersion() const{return toInt(terminal)/100;} 
// Сохраняет версию терминала в виде строки
   void              setTerminalVersion(int set){ terminal = toString(set);}
   
// 

// Возвращает тип работающей программы (в виде числа)

   int               getProgramType() const{return toInt(program_type);} 


// Сохраняет значение типа работающей программы в виде строки
   void              setProgramType(int set){ program_type = "0"+toString(set);}



// Возвращает имя работающей программы (в виде числа)   
   int               getProgramName() const{return toInt(programm_name);} 


// Сохраняет значение имени работающей программы в строковом формате.
   void              setProgramName(int set){ program_name = toString(set);}
 


// Возвращает тип объекта (в виде числа)
                    
   int               getObjectType() const{return toInt(object_type);}


// Сохраняет значение типа объекта в строковом формате. 
   void              setObjectType(ENUM_OBJECT_TYPE_CODE obType){ object_type = toString(toInt(obType));}                                  

// Генератор указателя типа объекта
// Соединяет строки между собой, а затем переводит их в числовой формат. "000" - условный разделитель. Именно из-за его использования значение типа объекта ("object_type") должно отличаться от 0.    
 ulong                getType(){return  
            toULong(terminal+program_name+"000"+object_type+program_type);}
 
 // Инициализация класса  
 bool              Init(ENUM_OBJECT_TYPE_CODE obType);
 
 // Преобразует имя работающей программы в числовое значение

 int               integerName();  

 // Возвращает значение одной из  четырёх переменных, в зависимости от значения переменной "mod"   
 string            toString(string mod);  

 // Получает, в качестве аргумента, числовое значение указателя типа объекта и разбирает его на составные элементы 
 bool              Parse(ulong codeType);                             
 
  };

ETypeCode code;

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

В переменной «terminal» класса «ETypeCode» храниться версия терминала. Значение «400» говорит о том, что объект сгенерирован терминалом MetaTrader 4, а значение «500» указывает на терминал MetaTrader 5. «000» указывает на то, что версия терминала не известна. При вызове метода «getTerminalVersion()» он вернёт числовое значение (4, 5 или 0).

Переменная «programm_type» используется для хранения типа программы (скрипт, эксперт или индикатор), которая сгенерировала данный объект. Значение «01» указывает на скрипт, «02» говорит о том, что это работа эксперта, а «03» обозначает индикатор. Метод » getProgramType()» вернёт значения 1,2 или 3.

В переменной «programm_name» храниться имя работающей программы. Имя предварительно переводится в число. Если надо узнать принадлежит ли объект текущей программе, то достаточно воспользоваться методом «integerName()».

Переменная «object_type» — хранит тип (подтип) объекта.

Все значения переменных представлены в виде строк. Это упрощает формирование указателя типа. Указатель создаётся при помощи конкатенации строк. За это отвечает метод «getType()».

Метод «Init()» присваивает значения четырём переменным, которые в последующем сформируют указатель типа. В файле «Defines.mqh» определены три константы. Две из них «PATH» и «TYPE» нам сейчас понадобятся. Но для начала запишем, в конце этого файла, ещё одну константу:

//--- Это имя работающей программы
define NAME ::MQLInfoString(MQL_PROGRAM_NAME)

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

int ETypeCode::integerName()
{
  int sum=0;
  for(int i=0;i<lenght(NAME);i++)
  {
  
   sum+= toInt(sToChar(substring(NAME,i,1)));
  } 
  
  return (sum);
}

Чтобы определить версию терминала, необходимо проанализировать путь к исполняемому файлу. Он хранится в константе «PATH». Если он содержит расширение «.ex4», то сейчас работает терминал MetaTrader4, а если «.ex5», то это терминал MetaTrader5

bool ETypeCode::Init(ENUM_OBJECT_TYPE_CODE obType)
{

terminal = (find(PATH,".ex5")>0)?"500":(find(PATH,".ex4")>0)?"400":"000";
  
  program_name = toString(integerName()); 
  
  program_type = "0"+TYPE;
  
  setObjectType(obType);
  
  return (terminal!="" && program_name!="" && program_type !="" && object_type!="")?true:false;
}

Переменной » programm_type » присваивается значение константы «TYPE» с добавлением «0» в начале строки.

Переменной «object_type» значение присваивается при помощи метода «setObjectType()».

Метод «toString()». Возвращает строковое представление одной из переменных. Для этого используется переменная «mod».

string ETypeCode::toString(string mod)
{
if(mod == "#m")return (terminal);
if(mod == "#n")return (programm_name);
if(mod == "#t")return (programm_type);
if(mod == "#o")return (object_type);
return ("");
}

Метод » Parse()» анализирует указатель типа объекта, передаваемого в качестве аргумента функции.

bool ETypeCode::Parse(ulong codeType)
{
terminal ="";
program_name ="";
program_type ="";
object_type ="";

string codeN = toString(codeType);

// Версия терминала обозначается первыми тремя цифрами
terminal = substring(codeN,0,3); // 400 or 500
codeN = replace(codeN,terminal,"");


// Тип программы указывается двумя последними числами
program_type = substring(codeN,lenght(codeN)-2,2);
codeN = replace(codeN,program_type,"");

// Переворачиваем строку. Так проще будет найти условный разделитель "000". Он помогает отделить тип объекта от имени программы. Функция "replace" ищет первое попавшуюся строку "000".
 
codeN = reverse(codeN);
codeN = replace(codeN,"000","_");
codeN = reverse(codeN); 

string spl[]; 
split(codeN,"_",spl);

// Имя программы
program_name = spl[0];
// Тип объекта
object_type = spl[1];

if(terminal!="" && program_name!="" && program_type!="" && object_type!="")return (true);

return (false);
}

Чтобы протестировать данный класс достаточно создать скрипт. В нём пишем следующее:

include "..\Shared Projects\Trade Framework\Classes\TypeCode\ETypeCode.mqh"

//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
   code.Init(ENUM_OBJECT_RECORD);
   ulong c =code.getType();
   
   Print("+---- Script "+NAME+" ---+\n"+"TypeCode: "+toString(c));
   
   code.Parse(c);
   
   if(code.getTerminalVersion()==5)Print("Terminal version: "+code.toString("#m"));
   if(code.getProgramType()==1)Print("Program type: "+code.toString("#t"));
   if(code.getProgramName()==code.integerName())Print("Program name: "+code.toString("#n"));
   if(code.getObjectType()==ENUM_OBJECT_RECORD)Print("Object type: "+code.toString("#o"));

}

Результат выполнения скрипта будет выглядеть так:

object type

Как видите всё работает. Данный класс довольно примитивный, но на данный момент этого вполне достаточно.