ООП в MQL5 на примерах: обработка кодов ошибок и предупреждений

KlimMalgin | 4 мая, 2010

Краткое введение в ООП

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

Безусловно, будут использоваться структуры и классы. Это базовые элементы в объектно-ориентированных языках. Что такое структура, что такое класс и чем они отличаются?

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

Класс также как и структура представляет собой набор полей данных. Но класс - это более сложная и "гибкая" конструкция. Именно классы являются ключевым понятием в ООП. В документации указаны различия классов и структур, я повторюсь:

  • в объявлении используется ключевое слово class;
  • по умолчанию все члены класса имеют спецификатор доступа private, если не указано иное. Члены-данные структуры по умолчанию имеют тип доступа public, если не указано иное;
  • объекты классов всегда имеют таблицу виртуальных функций, даже если в классе не объявлено ни одной виртуальной функции. Структуры не могут иметь виртуальных функций;
  • к объектам класса можно применять оператор new, к структурам этот оператор применять нельзя;
  • классы могут наследоваться только от классов, структуры могут наследоваться только от структур.

 

Рассмотрим подробнее классы. Все поля класса делятся на два вида. Это члены-данные (переменные, массивы и др.) и функции, определенные внутри класса.

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

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

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

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

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

Если для поля класса используется модификатор private, то доступ к этому полю делается возможным только с помощью методов этого же класса, таким образом не допускается возможность его модификации извне. Модификатор protected также накладывает ограничения на доступ к полю извне, но при этом он делает возможным доступ к полям класса для методов этого же класса и для методов классов-наследников. Ну а public, наоборот, убирает все ограничения доступа и предоставляет свободный доступ к полям класса.

Создание включаемого mqh-файла

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

Для создания этого файла воспользуемся Мастером MQL5. В меню Файл -> Создать выберем пункт Включаемый файл (*.mqh) и перейдем Далее. В появившемся окне введем имя файла, я назвал его ControlErrors, и нажмем Готово. Перед вами откроется шаблон mqh-файла, в котором мы продолжим работать.

Приступаем к работе

Теперь все теоретические основы по ООП, которые могут пригодиться в процессе изучения этой статьи, Вы имеете на вооружении и можно двигаться дальше.

Рассмотрим код класса с объявлением всех его свойств и методов:

class ControlErrors
{
private:

   // Флаги, определяющие, какие виды отчетов нужно вести
   bool _PlaySound;    // Подавать или не подавать звуковой сигнал при появлении ошибки.
   bool _PrintInfo;    // Печатать информацию об ошибках в журнал экспертов
   bool _AlertInfo;    // Выдавать Alert c информацией об ошибках
   bool _WriteFile;    // Писать отчеты об ошибках в файл или нет
   
   // Структура для хранения информации об ошибке и элементы, использующие эту структуру
   struct Code
   {
      int code;      // Код ошибки
      string desc;   // Описание кода ошибки
   };
   Code Errors[];    // Массив, содержащий коды ошибок и их описания
   Code _UserError;  // Хранит информацию о пользовательской ошибке
   Code _Error;      // Хранит информацию о последней ошибке любого типа   
   
   // Различные служебные свойства
   short  _CountErrors;     // Количество ошибок, содержащихся в массиве Errors[]
   string _PlaySoundFile;   // Файл, который будет проигрываться при подаче сигнала
   string _DataPath;        // Путь к директории для сохранения логов

   
public:
   // Конструктор
   ControlErrors(void);
   
   // Методы для установки флагов
   void SetSound(bool value);          // Подавать звуковой сигнал при возникновении ошибки или нет
   void SetPrint(bool value);          // Писать информацию об ошибке в журнал экспертов или нет
   void SetAlert(bool value);          // Подавать сообщение Alert или нет
   void SetWriteFlag(bool flag);       // Установить флаг записи. true - вести логи, false - не вести
   
   // Методы для работы с ошибками
   int  mGetLastError();            // Возвращает содержимое системной переменной _LastError
   int  mGetError();                // Возвращает код последней полученной ошибки
   int  mGetTypeError();            // Возвращает тип ошибки (Пользовательская = 1 или предопределенная = 0)
   void mResetLastError();          // Обнуляет содержимое системной переменной _LastError
   void mSetUserError(ushort value, string desc = "");   // Устанавливает пользовательскую ошибку
   void mResetUserError();          // Обнуляет поля класса, содержащие информацию о пользовательской ошибке
   void mResetError();              // Обнуляет структуру, содержащую информацию о последней ошибке
   string mGetDesc(int nErr = 0);   // Возвращает описание ошибки по номеру, либо текущей, если номер не указан
   int Check(string st = "");       // Метод для проверки текущего состояния системы на наличие ошибок
   
   // Сигнальные методы(Alert, Print, Sound)
   void mAlert(string message = "");
   void mPrint(string message = "");
   void mSound();
      
   // Различные служебные методы
   void SetPlaySoundFile(string file); // Метод устанавливает имя файла для подачи звукового сигнала
   void SetWritePath(string path);     // Установить путь для хранения логов   
   int mFileWrite();                   // Запись в файл доступной информации о последней ошибке
};

Свойства класса

В начале следует объявление свойств класса, при чем ко всем свойствам применен модификатор private и тем самым становится невозможной работа со свойствами напрямую вне класса. Условно свойства разбиты на три группы:

  1. Флаги, определяющие, какие виды отчетов нужно вести. Все эти флаги принимают только два значения: true - означает, что данный вид отчетов (уведомлений) включен и false - обозначает, что данный вид отчетов не ведется.
    • _PlaySound - переменная, которая запрещает или разрешает проигрывать указанную мелодию или звук, когда возникает ошибка.
    • _PrintInfo - отвечает за добавление информации об ошибке в журнал экспертов.
    • _AlertInfo - разрешает или запрещает выдавать Alert с информацией об ошибке.
    • _WriteFile - разрешает или запрещает писать информацию об ошибке в файл.
  2. Структура для хранения информации об ошибке и элементы, которые используют эту структуру.
    • Code - сама по себе структура. Она была создана для удобства хранения информации об ошибках в массиве.
    • Errors - массив типа Code, т.е. каждый элемент массива представляет собой структуру Code.
    • _UserError - переменная типа Code. Используется для работы с пользовательскими ошибками.
    • _Error - переменная типа Code. Последняя появившаяся ошибка, помещается в эту переменную и дальнейшая работа с ошибкой идет именно через эту переменную.
  3. Остальные служебные свойства.
    • _CountErrors - переменная содержит количество ошибок, информация о которых должна храниться в массиве Errors. Используется для указания размера массива.
    • _PlaySoundFile - содержит имя файла, который будет проигрываться при подаче сигнала.
    • _DataPath - содержит путь и название лог-файла, в который записывается информация об ошибках.

С первой группой свойств, я думаю, все понятно: они разрешают или запрещают ведение тех или иных отчетов. А вот во второй группе представляет интерес структура Code. Что она собой представляет и почему в качестве элементов массива используется именно структура? Все довольно просто! Ведь гораздо удобнее хранить все необходимые данные в одном элементе массива, чем заводить отдельные массивы для кода ошибки и для ее описания. Для реализации такой возможности используется структура. Внутри структуры объявляются необходимые поля, в нашем случае это:

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

Далее по коду следуют переменные _UserError и _Error. Они обе содержат в себе информацию о последней возникшей ошибке, с той разницей, что _UserError хранит информацию о пользовательских ошибках, а _Error обо всех.

И последняя - третья группа свойств. К ней я отнес оставшиеся свойства, которые не вписываются по своему назначению ни в первую, ни во вторую группы. Всего их три. Первое - _CountErrors, в нем содержится количество ошибок, информация о которых хранится в массиве _Errors. Это свойство используется для задания размера массива _Errors в конструкторе и в некоторых методах для обращения к элементам массива. Второе свойство - _PlaySoundFile. Хранит имя звукового файла, который будет проигрываться при возникновении ошибки. И третье свойство - _DataPath. Хранит путь и название файла для ведения логов.

Методы класса. Конструктор

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

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

void ControlErrors::ControlErrors(void)
{
   SetAlert(false);
   SetPrint(false);
   SetSound(false);
   SetWriteFlag(false);
   SetPlaySoundFile("alert.wav");
   SetWritePath("LogErrors.txt");
   
   _CountErrors = 150;
   ArrayResize(Errors, _CountErrors);

   // Коды возврата торгового сервера
   Errors[0].code = 10004;Errors[0].desc = "Реквота";
   Errors[1].code = 10006;Errors[1].desc = "Запрос отвергнут";
   Errors[2].code = 10007;Errors[2].desc = "Запрос отменен трейдером";
   Errors[3].code = 10008;Errors[3].desc = "Ордер размещен";
   Errors[4].code = 10009;Errors[4].desc = "Заявка выполнена";
   Errors[5].code = 10010;Errors[5].desc = "Заявка выполнена частично";
   Errors[6].code = 10011;Errors[6].desc = "Ошибка обработки запроса";
   Errors[7].code = 10012;Errors[7].desc = "Запрос отменен по истечению времени";
   Errors[8].code = 10013;Errors[8].desc = "Неправильный запрос";
   Errors[9].code = 10014;Errors[9].desc = "Неправильный объем в запросе";
   Errors[10].code = 10015;Errors[10].desc = "Неправильная цена в запросе";
   Errors[11].code = 10016;Errors[11].desc = "Неправильные стопы в запросе";
   Errors[12].code = 10017;Errors[12].desc = "Торговля запрещена";
   Errors[13].code = 10018;Errors[13].desc = "Рынок закрыт";
   Errors[14].code = 10019;Errors[14].desc = "Нет достаточных денежных средств для выполнения запроса";
   Errors[15].code = 10020;Errors[15].desc = "Цены изменились";
   Errors[16].code = 10021;Errors[16].desc = "Отсутствуют котировки для обработки запроса";
   Errors[17].code = 10022;Errors[17].desc = "Неверная дата истечения ордера в запросе";
   Errors[18].code = 10023;Errors[18].desc = "Состояние ордера изменилось";
   Errors[19].code = 10024;Errors[19].desc = "Слишком частые запросы";
   Errors[20].code = 10025;Errors[20].desc = "В запросе нет изменений";
   Errors[21].code = 10026;Errors[21].desc = "Автотрейдинг запрещен сервером";
   Errors[22].code = 10027;Errors[22].desc = "Автотрейдинг запрещен клиентским терминалом";
   Errors[23].code = 10028;Errors[23].desc = "Запрос заблокирован для обработки";
   Errors[24].code = 10029;Errors[24].desc = "Ордер или позиция заморожены";
   Errors[25].code = 10030;Errors[25].desc = "Указан неподдерживаемый тип исполнения ордера по остатку";

   // Общие ошибки
   Errors[26].code = 4001;Errors[26].desc = "Неожиданная внутренняя ошибка";
   Errors[27].code = 4002;Errors[27].desc = "Ошибочный параметр при внутреннем вызове функции клиентского терминала";
   Errors[28].code = 4003;Errors[28].desc = "Ошибочный параметр при вызове системной функции";
   Errors[29].code = 4004;Errors[29].desc = "Недостаточно памяти для выполнения системной функции";
   Errors[30].code = 4005;Errors[30].desc = "Структура содержит объекты строк и/или динамических массивов и/или структуры с такими объектами и/или классы";
   Errors[31].code = 4006;Errors[31].desc = "Массив неподходящего типа, неподходящего размера или испорченный объект динамического массива";
   Errors[32].code = 4007;Errors[32].desc = "Недостаточно памяти для перераспределения массива либо попытка изменения размера статического массива";
   Errors[33].code = 4008;Errors[33].desc = "Недостаточно памяти для перераспределения строки";
   Errors[34].code = 4009;Errors[34].desc = "Неинициализированная строка";
   Errors[35].code = 4010;Errors[35].desc = "Неправильное значение даты и/или времени";
   Errors[36].code = 4011;Errors[36].desc = "Запрашиваемый размер массива превышает 2 гигабайта";
   Errors[37].code = 4012;Errors[37].desc = "Ошибочный указатель";
   Errors[38].code = 4013;Errors[38].desc = "Ошибочный тип указателя";
   Errors[39].code = 4014;Errors[39].desc = "Системная функция не разрешена для вызова";

}

Обратите внимание, что описание реализации происходит за пределами класса! В самом классе только объявлены методы! Хотя делать так не обязательно. Если хотите - вы можете описывать тело каждого метода в самом классе, но, на мой взгляд, это неудобно и сложно для восприятия.

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

Сначала в конструкторе класса устанавливаются все флаги и указываются звуковой и лог-файл: 

SetAlert(false);
SetPrint(false);
SetSound(false);
SetWriteFlag(false);
SetPlaySoundFile("alert.wav");
SetWritePath("LogErrors.txt"); 

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

Как вы могли заметить: все флаги принимают значение false. Т.е. по умолчанию при создании экземпляра класса ни один вид отчетов вестись не будет. Пользователь должен сам выбрать, какие отчеты необходимо вести и активировать их с помощью этих же методов "Set" в функции OnInit(). Точно также можно изменить имя и путь до файла ведения логов (путь задается относительно директории 'MetaTrader 5\MQL5\Files\') и звукового файла(путь задается относительно директории 'MetaTrader 5\Sounds\').

После установки флагов, инициализируем переменную _CountErrors, присваивая ей значение 150 (в массиве будет храниться информация о 149 ошибках) и затем с помощью функции ArrayResize() устанавливаем необходимый нам размер массива. После чего начинаем заполнять массив.

Методы установки флагов

После описания конструктора идет описание методов установки флагов и задания имен звукового и лог-файла:

void ControlErrors::SetAlert(bool value)
{
   _AlertInfo = value;
}

void ControlErrors::SetPrint(bool value)
{
   _PrintInfo = value;
}

void ControlErrors::SetSound(bool value)
{
   _PlaySound = value;
}

void ControlErrors::SetWriteFlag(bool flag)
{
   _WriteFile = flag;
}

void ControlErrors::SetWritePath(string path)
{
   _DataPath = path;
}

void ControlErrors::SetPlaySoundFile(string file)
{
   _PlaySoundFile = file;
}

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

Обращение к этим методам, так же как и ко всем остальным, имеет вид: 

тип Имя_класса::Имя_функции(описание_параметров)
{
   // тело функции
}

Далее следует описание методов по работе с ошибками и первые из них mGetLastError() и mResetLastError().

Методы mGetLastError() и mResetLastError() 

Название метода mGetLastError() говорит само за себя, он дублирует функцию GetLastError(). Но помимо вызова GetLastError(), для полученного кода ошибки в массиве _Errors ищется описание и вся информация об ошибке (код и его описание) сохраняется в переменной _Error, чтобы в последующем использовать сохраненное значение, а не вызывать каждый раз GetLastError().

Код метода:

int ControlErrors::mGetLastError(void)
{
   _Error.code = GetLastError();
   _Error.desc = mGetDesc(_Error.code);
   return _Error.code;
}

Метод mResetLastError() дублирует функцию ResetLastError():

void ControlErrors::mResetLastError(void)
{
   ResetLastError();
}

Методы для работы с последним сообщением об ошибке

Это два метода: mGetError() и mResetError().

Метод mGetError() возвращает код, содержащийся в _Error.code:

int ControlErrors::mGetError(void)
{
   return _Error.code;
}

Метод mResetError() обнуляет содержимое переменной _Error:

void ControlErrors::mResetError(void)
{
   _Error.code = 0;
   _Error.desc = "";
}

Метод определения типа ошибки mGetTypeError()

Следующий метод mGetTypeError(). Он проверяет, является последняя возникшая ошибка пользовательской, либо она предопределенная (содержится в массиве _Errors).

Код метода:

int ControlErrors::mGetTypeError(void)
{
   if (mGetError() < ERR_USER_ERROR_FIRST)
   {
      return 0;
   }
   else if (mGetError() >= ERR_USER_ERROR_FIRST)
   {
      return 1;
   }
   return -1;
}

Константа ERR_USER_ERROR_FIRST имеет значение 65536. С этого кода начинаются ошибки, задаваемые пользователем. Поэтому в теле метода осуществляется проверка последнего поступившего кода ошибки. Если метод возвращает значение ноль, значит ошибка предопределенная, если единицу - пользовательская.

Методы для работы с пользовательскими ошибками

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

Метод mSetUserError() используется для установки кода и описания пользовательской ошибки:

void ControlErrors::mSetUserError(ushort value, string desc = "")
{
   SetUserError(value);
   _UserError.code = value;
   _UserError.desc = desc;
}

В начале функция SetUserError() устанавливает предопределенную переменную _LastError в значение, равное ERR_USER_ERROR_FIRST + value. А затем значение value и заданное ему в соответствие описание сохраняются в переменной _UserError.

Второй метод mResetUserError() обнуляет поля переменной _UserError:

void ControlErrors::mResetUserError(void)
{
   _UserError.code = 0;
   _UserError.desc = "";
}

Этот метод работает только с переменной _UserError. Для обнуления значения системной переменной _LastError, предназначен другой метод: mResetLastError(), он описан выше.

Метод для получения описания кода ошибки

В классе также заведен специальный метод mGetDesc(), который при вызове вернет описание кода ошибки из массива Errors, либо из поля desc переменной _UserError, если ошибка задана пользователем:

string ControlErrors::mGetDesc(int nErr=0)
{
   int ErrorNumber = 0;
   string ReturnDesc = "";
   
   ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
   ErrorNumber = (nErr>0)?nErr:ErrorNumber;
   
   if ((ErrorNumber > 0) && (ErrorNumber < ERR_USER_ERROR_FIRST))
   {
      for (int i = 0;i<_CountErrors;i++)
      {
         if (Errors[i].code == ErrorNumber)
         {
            ReturnDesc = Errors[i].desc;
            break;
         }
      }
   }
   else if (ErrorNumber > ERR_USER_ERROR_FIRST)
   {
      ReturnDesc = (_UserError.desc=="")?"Пользовательская ошибка":_UserError.desc;
   }
      
   if (ReturnDesc == ""){return "Неизвестный код ошибки: "+(string)ErrorNumber;}
   return ReturnDesc;
}

У этого метода есть один параметр nErr и по умолчанию он равен нулю. Если при вызове метода параметру будет задано какое-либо значение, то поиск описания будет производиться для заданного значения, если же параметр задан не будет, то поиск описания будет производиться для последнего поступившего кода ошибки.

В начале, в методе объявляются две переменные: ErrorNumber - с помощью нее будет проводиться работа с кодом ошибки и ReturnDesc - в ней будет храниться полученное для ErrorNumber описание. В следующих двух строках, при присвоении значения ErrorNumber используется условный оператор ?:. Это упрощенный аналог конструкции if-else. 

ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
ErrorNumber = (nErr>0)?nErr:ErrorNumber;

В первой строке мы определяем: если была зафиксирована ошибка, т.е. mGetError() вернул не нулевой результат, то переменной ErrorNumber будет присвоен полученный код ошибки (значение, которое вернул метод mGetError()), в ином случае значение самой переменной ErrorNumber. Во второй строке производится такая же проверка, но для параметра метода mGetError(). В том случае, если значение nErr не нулевое, то оно присваивается переменной ErrorNumber.

После того как мы получили код ошибки - приступаем к поиску описания для этого кода. Если полученный код больше нуля и меньше значения ERR_USER_ERROR_FIRST, т.е. не является пользовательской ошибкой, то в цикле производим поиск описания. А если полученный код больше ERR_USER_ERROR_FIRST, то берем описание из поля desc переменной _UserError.

В самом конце проверяем: найдено ли описание и если не найдено, то возвращаем сообщение о неизвестном коде ошибки.

Сигнальные методы

К сигнальным методам относятся mAlert(), mPrint() и mSound(). По своему устройству эти методы очень похожи:

void ControlErrors::mAlert(string message="")
{
   if (_AlertInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Alert("Ошибка №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Alert(message);
      }   
   }
}

void ControlErrors::mPrint(string message="")
{
   if (_PrintInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Print("Ошибка №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Print(message);
      }
   }
}

void ControlErrors::mSound(void)
{
   if (_PlaySound == true)
   {
      PlaySound(_PlaySoundFile);
   }
}

Во всех трех методах в начале проверяется флаг на разрешение ведения отчета или выдачу сигнала. Затем в методах mAlert() и mPrint() проверяется входной параметр message на наличие сообщения, которое нужно показать в окне Alert'а или добавить в журнал. Если сообщение в message задано и код последней ошибки больше нуля, то выводится оно, если нет - то выводится стандартное сообщение. Метод mSound() никаких параметров не имеет, поэтому после проверки флага, в нем сразу вызывается функция PlaySound() для подачи звукового сигнала.

Метод Check()

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

int ControlErrors::Check(string st="")
{
   int errNum = 0;
   errNum = mGetLastError();
   mFileWrite();
   mAlert(st);
   mPrint(st);
   mSound();
   mResetError();
   mResetLastError();
   mResetUserError();
   return errNum;
}

У метода Check() есть один параметр типа string. Это пользовательское сообщение, которое передается в методы mAlert() и mPrint() для записи в отчеты.

Метод для записи сообщений в лог файл

Это метод под названием mFileWrite(). Если ведение лог-файла разрешено и путь до файла указан корректно - этот метод делает записи в указанном файле.

int ControlErrors::mFileWrite(string message = "")
{
   int      handle  = 0,
            _return = 0;
   datetime time    = TimeCurrent();
   string   text    = (message != "")?message:time+" - Ошибка №"+mGetError()+" "+mGetDesc();
   
   if (_WriteFile == true)
   {
      handle = FileOpen(_DataPath,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI);
      if (handle != INVALID_HANDLE)
      {
         ulong size = FileSize(handle);
         FileSeek(handle,size,SEEK_SET);
         _return = FileWrite(handle,text);
         FileClose(handle);
      }
   }
   return _return;
}

В начале объявляются четыре переменные: handle - для хранения хендла открытого файла, _return - для хранения возвращаемого значения, time, в которой сохраняется текущее время (время в которое будет сделана запись в файл) и text - текст сообщения, которое будет записано в файл. У метода mFileWrite() есть один входной параметр - message, в котором пользователь может передать любую строку, которую ему необходимо будет записать в файл.

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

После объявления переменных проверяется флаг _WriteFile и если ведение лог-файла разрешено, то с помощью функции FileOpen() файл открывается для перезаписи. Первым параметром FileOpen() является свойство DataPath, содержащее путь до файла и его имя. В качестве второго параметра подается набор флагов, которые определяют режим работы с файлом. В нашем случае используется четыре флага:

На следующем шаге проверяем - удалось ли открыть файл. Если не удалось, то handle будет иметь значение INVALID_HANDLE и на этом работа метода закончится. Но если все прошло благополучно, тогда мы получаем размер файла с помощью FileSize(), затем при помощи функции FileSeek() перемещаем положение файлового указателя в конец файла и функцией FileWrite() дописываем сообщение в конец файла. После всех этих операций функцией FileClose() закрываем ранее открытый файл.

В функцию FileSize() в качестве входного параметра необходимо подать хендл файла, размер которого мы хотим получить. Это единственный параметр у этой функции.

Для работы функции FileSeek() нужно указать три параметра:

Для работы функции FileWrite() необходимо хотя бы два параметра. Это хендл файла в который нужно записать текстовые данные, второй это текстовая строка, которую нужно записать и все последующие, так же текстовые строки, которые будут записаны в файл. Всего параметров должно быть не более 63.

Функции FileClose() так же требуется хендл файла, чтобы его закрыть.

Примеры использования

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

Итак, создадим объект класса:

#include <ControlErrors.mqh>

ControlErrors mControl;

Перед тем как создавать объект, в советник необходимо включить файл, содержащий описание класса. Это делается в самом начале программы при помощи директивы #include. И только потом создается объект, при чем выглядит это так же, как и создание новой переменной, только вместо типа данных подставляется название класса. 

Теперь выберем те отчеты об ошибках, которые хотим получать, делается это в функции OnInit(): 

int OnInit()
{
//---
mControl.SetAlert(true);
mControl.SetPrint(true);
mControl.SetSound(false);
mControl.SetWriteFlag(true);
mControl.SetPlaySoundFile("news.wav");
//---
return(0);
}

Напомню, что по умолчанию, при создании объекта все флаги, разрешающие ведение отчетов установлены в false, т.е. все отчеты запрещены. Поэтому в OnInit() не обязательно вызывать методы со значением false, как это сделано в примере выше (метод SetSound()). Эти методы также можно вызывать и в других фрагментах программы. К примеру, если вам понадобится отключить ведение отчетов при определенных условиях, то вы программируете эти условия и при их выполнении устанавливаете флагам нужные значения.

И последнее о чем здесь нужно сказать, это вызов методов в ходе работы программы и "отлов" ошибок. Эта часть работы ничего сложного собою не представляет, т.к. здесь можно пользоваться одним только методом Check(), предварительно установив все флаги:

mControl.Check();

Этот метод, как было сказано выше, выявит код возникшей ошибки, вызовет все методы, которые ведут отчеты и затем обнулит значения всех переменных, содержащих информацию о последней ошибке. Если тот вид работы с ошибками, который предоставляет Check(), по каким-то причинам не подходит, то можно составлять отчеты по своему из всех доступных методов класса.