Библиотека для простого и быстрого создания программ для MetaTrader (Часть XX): Создание и хранение ресурсов программы

Artyom Trishkin | 2 сентября, 2019

Содержание

Концепция

Часто при создании программы, нам требуется использовать звуки и изображения. В языке MQL есть несколько возможностей использования таких данных, и все они связаны с необходимостью загружать файлы из файловой песочницы терминала. Если в конечном результате предполагается скомпилированный файл, то достаточно подключить файл как ресурс и избавиться от необходимости передавать дополнительные файлы для работы программы. Такой способ хорошо подходит для размещения программ в Маркете mql5.com, так как там требуется размещение только исполняемого файла, но этот способ совсем не подходит для размещения исходного кода в CodeBase mql5.com, так как звуковые *.wav и графические файлы в формате *.bmp туда разместить нельзя. А без них исходный код неполноценен.
Как же быть в такой ситуации? Вывод очевиден: хранить данные всех требуемых файлов в исходном коде в подключаемых файлах *.mqh в виде бинарных массивов. А далее в самой программе, при её запуске создавать из имеющихся наборов данных все необходимые для работы программы файлы в нужных папках. Таким образом, при запуске такого кода программа сама создаст все требуемые для своей работы файлы, разложит их по папкам и будет корректно работать. Для пользователя программы этот процесс пройдёт практически незамеченным, ну лишь немного увеличится время первого запуска, необходимое для создания и записи отсутствующих файлов.

Для работы с файловыми данными мы создадим два класса:

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

Класс-генератор файлов

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

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

//+------------------------------------------------------------------+
//| Список индексов текстовых сообщений библиотеки                   |
//+------------------------------------------------------------------+
enum ENUM_MESSAGES_LIB
  {
   MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST,      // Начало списка параметров
   MSG_LIB_PARAMS_LIST_END,                           // Конец списка параметров
   MSG_LIB_PROP_NOT_SUPPORTED,                        // Свойство не поддерживается
   MSG_LIB_PROP_NOT_SUPPORTED_MQL4,                   // Свойство не поддерживается в MQL4
   MSG_LIB_PROP_NOT_SUPPORTED_POSITION,               // Свойство не поддерживается у позиции
   MSG_LIB_PROP_NOT_SUPPORTED_PENDING,                // Свойство не поддерживается у отложенного ордера
   MSG_LIB_PROP_NOT_SUPPORTED_MARKET,                 // Свойство не поддерживается у маркет-ордера
   MSG_LIB_PROP_NOT_SUPPORTED_MARKET_HIST,            // Свойство не поддерживается у исторического маркет-ордера
   MSG_LIB_PROP_NOT_SET,                              // Значение не задано
   MSG_LIB_PROP_EMPTY,                                // Отсутствует
   
   MSG_LIB_SYS_ERROR,                                 // Ошибка
   MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER,                  // Ошибка. Такого символа нет на сервере
   MSG_LIB_SYS_FAILED_PUT_SYMBOL,                     // Не удалось поместить в обзор рынка. Ошибка: 
   MSG_LIB_SYS_NOT_GET_PRICE,                         // Не удалось получить текущие цены. Ошибка: 
   MSG_LIB_SYS_NOT_GET_MARGIN_RATES,                  // Не удалось получить коэффициенты взимания маржи. Ошибка: 
   MSG_LIB_SYS_NOT_GET_DATAS,                         // Не удалось получить данные
   
   MSG_LIB_SYS_FAILED_CREATE_STORAGE_FOLDER,          // Не удалось создать папку хранения файлов. Ошибка: 
   MSG_LIB_SYS_FAILED_ADD_ACC_OBJ_TO_LIST,            // Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию
   MSG_LIB_SYS_FAILED_CREATE_CURR_ACC_OBJ,            // Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта
   MSG_LIB_SYS_FAILED_OPEN_FILE_FOR_WRITE,            // Не удалось открыть для записи файл
   MSG_LIB_SYS_INPUT_ERROR_NO_SYMBOL,                 // Ошибка входных данных: нет символа
   MSG_LIB_SYS_FAILED_CREATE_SYM_OBJ,                 // Не удалось создать объект-символ
   MSG_LIB_SYS_FAILED_ADD_SYM_OBJ,                    // Не удалось добавить символ
   
   MSG_LIB_SYS_NOT_GET_CURR_PRICES,                   // Не удалось получить текущие цены по символу события
   MSG_LIB_SYS_EVENT_ALREADY_IN_LIST,                 // Такое событие уже есть в списке
   MSG_LIB_SYS_FILE_RES_ALREADY_IN_LIST,              // Такой файл уже создан и добавлен в список:
   MSG_LIB_SYS_FAILED_CREATE_RES_LINK,                // Ошибка. Не удалось создать объект-указатель на файл ресурса
   MSG_LIB_SYS_ERROR_ALREADY_CREATED_COUNTER,         // Ошибка. Уже создан счётчик с идентификатором
   MSG_LIB_SYS_FAILED_CREATE_COUNTER,                 // Не удалось создать счётчик таймера
   MSG_LIB_SYS_FAILED_CREATE_TEMP_LIST,               // Ошибка создания временного списка
   MSG_LIB_SYS_ERROR_NOT_MARKET_LIST,                 // Ошибка. Список не является списком рыночной коллекции
   MSG_LIB_SYS_ERROR_NOT_HISTORY_LIST,                // Ошибка. Список не является списком исторической коллекции
   MSG_LIB_SYS_FAILED_ADD_ORDER_TO_LIST,              // Не удалось добавить ордер в список
   MSG_LIB_SYS_FAILED_ADD_DEAL_TO_LIST,               // Не удалось добавить сделку в список
   MSG_LIB_SYS_FAILED_ADD_CTRL_ORDER_TO_LIST,         // Не удалось добавить контрольный ордер
   MSG_LIB_SYS_FAILED_ADD_CTRL_POSITION_TO_LIST,      // Не удалось добавить контрольую позицию
   MSG_LIB_SYS_FAILED_ADD_MODIFIED_ORD_TO_LIST,       // Не удалось добавить модифицированный ордер в список изменённых ордеров
    
   MSG_LIB_SYS_NO_TICKS_YET,                          // Ещё не было тиков
   MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT,              // Не удалось создать структуру объекта
   MSG_LIB_SYS_FAILED_WRITE_UARRAY_TO_FILE,           // Не удалось записать uchar-массив в файл
   MSG_LIB_SYS_FAILED_LOAD_UARRAY_FROM_FILE,          // Не удалось загрузить uchar-массив из файла
   MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT_FROM_UARRAY,  // Не удалось создать структуру объекта из uchar-массива
   MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,      // Не удалось сохранить структуру объекта в uchar-массив, ошибка
   MSG_LIB_SYS_ERROR_INDEX,                           // Ошибка. Значение "index" должно быть в пределах 0 - 3
   MSG_LIB_SYS_ERROR_FAILED_CONV_TO_LOWERCASE,        // Не удалось преобразовать строку в нижний регистр, ошибка
   
   MSG_LIB_SYS_ERROR_EMPTY_STRING,                    // Ошибка. Строка предопределённых символов пустая, будет использоваться
   MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY,        // Не удалось подготовить массив используемых символов. Ошибка 
   MSG_LIB_SYS_INVALID_ORDER_TYPE,                    // Не правильный тип ордера:
   

И в массив текстовых сообщений впишем соответствующие константам индексов тексты на русском и английском языках:

//+------------------------------------------------------------------+
string messages_library[][TOTAL_LANG]=
  {
   {"Начало списка параметров","The beginning of the event parameter list"},
   {"Конец списка параметров","End of the parameter list"},
   {"Свойство не поддерживается","Property is not support"},
   {"Свойство не поддерживается в MQL4","Property is not supported in MQL4"},
   {"Свойство не поддерживается у позиции","Property not supported for position"},
   {"Свойство не поддерживается у отложенного ордера","The property is not supported for a pending order"},
   {"Свойство не поддерживается у маркет-ордера","The property is not supported for a market-order"},
   {"Свойство не поддерживается у исторического маркет-ордера","The property is not supported for a history market-order"},
   {"Значение не задано","Value not set"},
   {"Отсутствует","Not set"},
   
   {"Ошибка ","Error "},
   {"Ошибка. Такого символа нет на сервере","Error. There is no such symbol on the server"},
   {"Не удалось поместить в обзор рынка. Ошибка: ","Failed to put in the market watch. Error: "},
   {"Не удалось получить текущие цены. Ошибка: ","Could not get current prices. Error: "},
   {"Не удалось получить коэффициенты взимания маржи. Ошибка: ","Failed to get margin rates. Error: "},
   {"Не удалось получить данные ","Failed to get data of "},
   
   {"Не удалось создать папку хранения файлов. Ошибка: ","Could not create file storage folder. Error: "},
   {"Ошибка. Не удалось добавить текущий объект-аккаунт в список-коллекцию","Error. Failed to add current account object to collection list"},
   {"Ошибка. Не удалось создать объект-аккаунт с данными текущего счёта","Error. Failed to create an account object with current account data"},
   {"Не удалось открыть для записи файл ","Could not open file for writing: "},
   {"Ошибка входных данных: нет символа ","Input error: no "},
   {"Не удалось создать объект-символ ","Failed to create symbol object "},
   {"Не удалось добавить символ ","Failed to add "},
   
   {"Не удалось получить текущие цены по символу события ","Failed to get current prices by event symbol "},
   {"Такое событие уже есть в списке","This event is already in the list"},
   {"Такой файл уже создан и добавлен в список: ","This file has already been created and added to the list: "},   
   {"Ошибка. Не удалось создать объект-указатель на файл ресурса","Error. Failed to create resource file link object"},
   
   {"Ошибка. Уже создан счётчик с идентификатором ","Error. Already created a counter with id "},
   {"Не удалось создать счётчик таймера ","Failed to create timer counter "},
   
   {"Ошибка создания временного списка","Error creating temporary list"},
   {"Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection"},
   {"Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"},
   {"Не удалось добавить ордер в список","Could not add order to the list"},
   {"Не удалось добавить сделку в список","Could not add deal to the list"},
   {"Не удалось добавить контрольный ордер ","Failed to add a control order "},
   {"Не удалось добавить контрольую позицию ","Failed to add a control position "},
   {"Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to the list of modified orders"},
   
   {"Ещё не было тиков","No ticks yet"},
   {"Не удалось создать структуру объекта","Could not create object structure"},
   {"Не удалось записать uchar-массив в файл","Could not write uchar array to file"},
   {"Не удалось загрузить uchar-массив из файла","Could not load uchar array from file"},
   {"Не удалось создать структуру объекта из uchar-массива","Could not create object structure from uchar array"},
   {"Не удалось сохранить структуру объекта в uchar-массив, ошибка ","Failed to save object structure to uchar array, error "},
   {"Ошибка. Значение \"index\" должно быть в пределах 0 - 3","Error. The \"index\" value must be between 0 - 3"},
   {"Не удалось преобразовать строку в нижний регистр, ошибка ","Failed to convert string to lowercase, error "},
   
   {"Ошибка. Строка предопределённых символов пустая, будет использоваться ","Error. String of predefined symbols is empty, the symbol will be used: "},
   {"Не удалось подготовить массив используемых символов. Ошибка ","Failed to create an array of used characters. Error "},
   {"Не правильный тип ордера: ","Invalid order type: "},

Следует обратить внимание, что последовательность расположения текстов в массиве должна точно соответствовать последовательности объявления констант индексов в перечислении.

Для работы класса-генератора файлов необходимо создать базы данных, из которых класс будет брать данные о звуковых файлах и файлах изображений. Такими данными должны являться unsigned char-массивы. Для их создания требуется наличие звуковых (*.wav) и растровых (*.bmp) файлов, которые мы будем хранить в исходниках библиотеки. Для примера я уже подготовил несколько тестовых данных. Звуковые и растровые данные будут храниться каждый в своём подключаемом файле.

Создадим в корневом каталоге библиотеки \MQL5\Include\DoEasy\ подключаемый файл DataSND.mqh и сразу впишем названия массивов данных тех звуковых файлов, которые будем размещать в массивах (впрочем, можно и после дописать названия массивов данных — они нужны лишь для быстрого поиска в листинге места объявления массива с данными того или иного файла, так как массивы могут быть большими, но не более 16 мегабайт):

//+------------------------------------------------------------------+
//|                                                      DataSND.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Звуки                                                            |
//+------------------------------------------------------------------+
/*
   sound_array_coin_01           // Звук упавшей монетки 1
   sound_array_coin_02           // Звук упавших монеток
   sound_array_coin_03           // Звук монеток
   sound_array_coin_04           // Звук упавшей монетки 2
   //---
   sound_array_click_01          // Звук щелчка по кнопке 1
   sound_array_click_02          // Звук щелчка по кнопке 2
   sound_array_click_03          // Звук щелчка по кнопке 3
   //---
   sound_array_cash_machine_01   // Звук кассового аппарата 1
*/
//+------------------------------------------------------------------+

Для вставки файла в исходный текст програмы можно воспользоваться пунктом меню "Правка --> Вставить --> Файл в виде бинарного массива":


После выбора данного пункта откроется окно выбора файла, в котором необходимо найти заранее подготовленный файл для загрузки его данных в массив. Массив будет создан автоматически на основании имени выбранного файла (пример не полный так как бинарных данных много):

//+------------------------------------------------------------------+
//|                                                      DataSND.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Звуки                                                            |
//+------------------------------------------------------------------+
/*
   sound_array_coin_01           // Звук упавшей монетки 1
   sound_array_coin_02           // Звук упавших монеток
   sound_array_coin_03           // Звук монеток
   sound_array_coin_04           // Звук упавшей монетки 2
   //---
   sound_array_click_01          // Звук щелчка по кнопке 1
   sound_array_click_02          // Звук щелчка по кнопке 2
   sound_array_click_03          // Звук щелчка по кнопке 3
   //---
   sound_array_cash_machine_01   // Звук кассового аппарата 1
*/
//+------------------------------------------------------------------+
//| Звук упавшей монетки 01                                          |
//+------------------------------------------------------------------+
unsigned char sound_array_coin_01[]=
  {
   0x52,0x49,0x46,0x46,0x1E,0x50,0x09,0x00,0x57,0x41,0x56,0x45,0x66,0x6D,0x74,0x20,
   0x12,0x00,0x00,0x00,0x03,0x00,0x02,0x00,0x44,0xAC,0x00,0x00,0x20,0x62,0x05,0x00,
   0x08,0x00,0x20,0x00,0x00,0x00,0x64,0x61,0x74,0x61,0x28,0x35,0x09,0x00,0x11,0xBA,
   0x20,0xBB,0x11,0xBA,0x20,0xBB,0x74,0x81,0x62,0xBB,0x74,0x81,0x62,0xBB,0xE9,0x37,
   0x4D,0xBB,0xE9,0x37,0x4D,0xBB,0x7C,0x41,0x13,0xBB,0x7C,0x41,0x13,0xBB,0x8F,0xE4,
   0x84,0xBA,0x8F,0xE4,0x84,0xBA,0x51,0x6D,0x05,0x3A,0x51,0x6D,0x05,0x3A,0xE1,0xC7,
   0x85,0x3A,0xE1,0xC7,0x85,0x3A,0xF5,0xA9,0xA1,0x39,0xF5,0xA9,0xA1,0x39,0xD6,0xF7,
   0x25,0xBA,0xD6,0xF7,0x25,0xBA,0x88,0x38,0x5B,0xBA,0x88,0x38,0x5B,0xBA,0xC8,0x31,
   0x05,0x39,0xC8,0x31,0x05,0x39,0x62,0xA0,0xF8,0x39,0x62,0xA0,0xF8,0x39,0x62,0xE5,
   0x39,0xBA,0x62,0xE5,0x39,0xBA,0x8A,0xAA,0x8B,0xBA,0x8A,0xAA,0x8B,0xBA,0xDC,0xF9,
   0x0B,0xBA,0xDC,0xF9,0x0B,0xBA,0xEA,0x27,0x97,0xBA,0xEA,0x27,0x97,0xBA,0xA1,0x5E,
   0xDA,0xBA,0xA1,0x5E,0xDA,0xBA,0x96,0x5B,0x56,0xBA,0x96,0x5B,0x56,0xBA,0x13,0xB9,
   0xA6,0xB8,0x13,0xB9,0xA6,0xB8,0x3B,0xFD,0x39,0xB7,0x3B,0xFD,0x39,0xB7,0x05,0x39,
   0x37,0xB9,0x05,0x39,0x37,0xB9,0x7D,0xED,0x18,0xBA,0x7D,0xED,0x18,0xBA,0x73,0xDD,
   0x8A,0xBA,0x73,0xDD,0x8A,0xBA,0xD1,0x83,0xD4,0xBA,0xD1,0x83,0xD4,0xBA,0xFF,0x94,
   0x0C,0xBB,0xFF,0x94,0x0C,0xBB,0xAF,0x13,0xF6,0xBA,0xAF,0x13,0xF6,0xBA,0x5A,0x27,
   0x4A,0xBA,0x5A,0x27,0x4A,0xBA,0x68,0xA7,0xA1,0x39,0x68,0xA7,0xA1,0x39,0xBE,0x9B,
   0x32,0x3A,0xBE,0x9B,0x32,0x3A,0xFD,0x21,0x0C,0x3A,0xFD,0x21,0x0C,0x3A,0x36,0xFF,
   0x21,0x3A,0x36,0xFF,0x21,0x3A,0xB5,0xA2,0x36,0x3A,0xB5,0xA2,0x36,0x3A,0xBB,0x73,
   0xE2,0xB9,0xBB,0x73,0xE2,0xB9,0x16,0xDA,0x16,0xBB,0x16,0xDA,0x16,0xBB,0x41,0x70,
   0x23,0xBB,0x41,0x70,0x23,0xBB,0xA9,0xC6,0x34,0xBA,0xA9,0xC6,0x34,0xBA,0x78,0x88,
   0x35,0x37,0x78,0x88,0x35,0x37,0xFB,0x4C,0xA9,0xBA,0xFB,0x4C,0xA9,0xBA,0xDA,0xAA,

Так как данные для массива полностью повторяют данные файла, то такие массивы получаются довольно-таки большими. Именно поэтому я и вписал заранее названия создаваемых массивов — чтобы быстро переходить к началу каждого в листинге при помощи поиска Ctrl+F.

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

И точно таким же образом создадим файл с растровыми данными с именем DataIMG.mqh. В него я тоже уже внёс два массива с изображением двухцветной лампочки-светодиода: в одном массиве — данные с изображением светодиода зелёного цвета, в другом — красного:

//+------------------------------------------------------------------+
//|                                                      DataIMG.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Изображения                                                      |
//+------------------------------------------------------------------+
/*
   img_array_spot_green             // Зелёный светодиод 16x16, 32 бит
   img_array_spot_red               // Красный светодиод 16x16, 32 бит
*/
//+------------------------------------------------------------------+
//| Изображение "Зелёный светодиод" 32 бит, альфа                    |
//+------------------------------------------------------------------+
unsigned char img_array_spot_green[]=
  {
   0x42,0x4D,0x38,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x28,0x00,
   0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x20,0x00,0x00,0x00,
   0x00,0x00,0x02,0x04,0x00,0x00,0xC3,0x0E,0x00,0x00,0xC3,0x0E,0x00,0x00,0x00,0x00,
   0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,

...

   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00
  };
//+------------------------------------------------------------------+
//| Изображение "Красный светодиод" 32 бит, альфа                    |
//+------------------------------------------------------------------+
unsigned char img_array_spot_red[]=
  {
   0x42,0x4D,0x38,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x28,0x00,
   0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x20,0x00,0x00,0x00,
   0x00,0x00,0x02,0x04,0x00,0x00,0xC3,0x0E,0x00,0x00,0xC3,0x0E,0x00,0x00,0x00,0x00,
   0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
   0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,

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

Чтобы бинарные данные файлов были доступны в библиотеке, подключим файлы с данными к файлу Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Datas.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+

В блоке макроподстановок файла Defines.mqh впишем макроподстановку с указанием папки расположения ресурсных данных библиотеки:

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
//--- "Описание функции с номером строки ошибки"
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Описание функции"
#define COUNTRY_LANG                   ("Russian")                // Язык страны
#define END_TIME                       (D'31.12.3000 23:59:59')   // Конечная дата для запросов данных истории счёта
#define TIMER_FREQUENCY                (16)                       // Минимальная частота таймера библиотеки в милисекундах
//--- Параметры таймера коллекции ордеров и сделок
#define COLLECTION_ORD_PAUSE           (250)                      // Пауза таймера коллекции ордеров и сделок в милисекундах
#define COLLECTION_ORD_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера коллекции ордеров и сделок
#define COLLECTION_ORD_COUNTER_ID      (1)                        // Идентификатор счётчика таймера коллекции ордеров и сделок
//--- Параметры таймера коллекции аккаунтов
#define COLLECTION_ACC_PAUSE           (1000)                     // Пауза таймера коллекции аккаунтов в милисекундах
#define COLLECTION_ACC_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера аккаунтов
#define COLLECTION_ACC_COUNTER_ID      (2)                        // Идентификатор счётчика таймера аккаунтов
//--- Параметры таймера1 коллекции символов
#define COLLECTION_SYM_PAUSE1          (100)                      // Пауза таймера1 коллекции символов в милисекундах (для сканирования символов в обзоре рынка)
#define COLLECTION_SYM_COUNTER_STEP1   (16)                       // Шаг приращения счётчика таймера1 символов
#define COLLECTION_SYM_COUNTER_ID1     (3)                        // Идентификатор счётчика таймера1 символов
//--- Параметры таймера2 коллекции символов
#define COLLECTION_SYM_PAUSE2          (300)                      // Пауза таймера2 коллекции символов в милисекундах (для событий списка символов в обзоре рынка)
#define COLLECTION_SYM_COUNTER_STEP2   (16)                       // Шаг приращения счётчика таймера2 символов
#define COLLECTION_SYM_COUNTER_ID2     (4)                        // Идентификатор счётчика таймера2 символов
//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID          (0x7779)                   // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID           (0x777A)                   // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID           (0x777B)                   // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID          (0x777C)                   // Идентификатор списка коллекции аккаунтов
#define COLLECTION_SYMBOLS_ID          (0x777D)                   // Идентификатор списка коллекции символов
//--- Параметры данных для файловых операций
#define DIRECTORY                      ("DoEasy\\")               // Каталог библиотеки для расположения папок объектов классов
#define RESOURCE_DIR                   ("DoEasy\\Resource\\")     // Каталог библиотеки для расположения папок ресурсов
//--- Параметры символов
#define CLR_DEFAULT                    (0xFF000000)               // Цвет по умолчанию
#define SYMBOLS_COMMON_TOTAL           (1000)                     // Общее количество рабочих символов
//+------------------------------------------------------------------+

Внутри подпапки библиотеки Resource\ будут автоматически создаваться папки Sounds и Images для создания и хранения в них файлов звуков и изображений соответственно.

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

//+------------------------------------------------------------------+
//| Данные для работы с ресурсными данными программы                 |
//+------------------------------------------------------------------+
enum ENUM_FILE_TYPE
  {
   FILE_TYPE_WAV,                                           // wav-файл
   FILE_TYPE_BMP,                                           // bmp-файл
  };
//+------------------------------------------------------------------+

Так как все файлы ресурсов библиотеки будем располагать в двух папках Sounds и Images, которые в свою очередь будем размещать в каталог MQL5\Files\ терминала, то нам необходимо подправить метод PlaySound() класса CMessage. Откроем файл \MQL5\Include\DoEasy\ Services\Message.mqh и
внесём корректировку пути файла в метод PlaySound():

//+------------------------------------------------------------------+
//| Воспроизводит звуковой файл                                      |
//+------------------------------------------------------------------+
bool CMessage::PlaySound(const string file_name)
  {
   bool res=::PlaySound("\\Files\\"+file_name);
   CMessage::m_global_error=(res ? ERR_SUCCESS : ::GetLastError());
   return res;
  }
//+------------------------------------------------------------------+

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

Этого на данный момент достаточно для создания необходимых нам классов.

Теперь создадим в папке \MQL5\Include\DoEasy\Services\ новый класс CFileGen в файле FileGen.mqh:

//+------------------------------------------------------------------+
//|                                                      FileGen.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Класс генератора файлов                                          |
//+------------------------------------------------------------------+
class CFileGen
  {
private:
   static string     m_folder_name;    // Имя папки хранения файлов ресурсов библиотеки
   static string     m_subfolder;      // Имя подпапки хранения звуковых или растровых файлов
   static string     m_name;           // Имя файла
   static int        m_handle;         // Хэндл файла
//--- Устанавливает имя (1) файла, (2) подпапки
   static void       SetName(const ENUM_FILE_TYPE file_type,const string file_name);
   static void       SetSubFolder(const ENUM_FILE_TYPE file_type);
//--- Возвращает расширение файла по его типу
   static string     Extension(const ENUM_FILE_TYPE file_type);
public:
//--- Возвращает (1) установленное имя, (2) флаг наличия файла в каталоге ресурсов
   static string     Name(void)  { return CFileGen::m_name; }
   static bool       IsExist(const ENUM_FILE_TYPE file_type,const string file_name);
//--- Создаёт файл из массива данных
   static bool       Create(const ENUM_FILE_TYPE file_type,const string file_name,const uchar &file_data_array[]);
  };
//+------------------------------------------------------------------+

К файлу сразу же подключена библиотека сервисных функций DELib.mqh, так как к ней уже подключены Defines.mqh и Message.mqh, необходимые для работы класса:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property strict  // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
//+------------------------------------------------------------------+

Все переменные-члены класса и его методы озаглавлены в коде, и описывать их назначение не имеет смысла — там всё понятно. Рассмотрим реализацию методов.
Так как переменные-члены класса статические, то они требуют явной инициализации:

//+------------------------------------------------------------------+
//| Инициализация статических переменных                             |
//+------------------------------------------------------------------+
string CFileGen::m_folder_name=RESOURCE_DIR;
string CFileGen::m_subfolder="\\";
string CFileGen::m_name=NULL;
int    CFileGen::m_handle=INVALID_HANDLE;
//+------------------------------------------------------------------+

Метод создания файла из массива данных:

//+------------------------------------------------------------------+
//| Создаёт файл из массива данных                                   |
//+------------------------------------------------------------------+
bool CFileGen::Create(const ENUM_FILE_TYPE file_type,const string file_name,const uchar &file_data_array[])
  {
   //--- Установим имя файла, состоящее из пути к файлу, его имени и расширения
   CFileGen::SetName(file_type,file_name);
   //--- Если такой файл уже существует - возвращаем false
   if(::FileIsExist(CFileGen::m_name))
      return false;
   //--- Открываем файл с созданным именем для записи
   CFileGen::m_handle=::FileOpen(CFileGen::m_name,FILE_WRITE|FILE_BIN);
   //--- Если файл создать не удалось, получаем код ошибки, вывоим сообщение об ошибке открытия файла и возвращаем false
   if(CFileGen::m_handle==INVALID_HANDLE)
     {
      int err=::GetLastError();
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_OPEN_FILE_FOR_WRITE),"\"",CFileGen::m_name,"\". ",CMessage::Text(MSG_LIB_SYS_ERROR),"\"",CMessage::Text(err),"\" ",CMessage::Retcode(err));
      return false;
     }
   //--- Записываем в бинарный файл содержимое массива file_data_array[], закрываем файл и возвращаем true

   ::FileWriteArray(CFileGen::m_handle,file_data_array);
   ::FileClose(CFileGen::m_handle);
   return true;
  }
//+------------------------------------------------------------------+

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

Метод, возвращающий флаг наличия файла в каталоге ресурсов:

//+------------------------------------------------------------------+
//| Возвращает флаг наличия файла в каталоге ресурсов                |
//+------------------------------------------------------------------+
bool CFileGen::IsExist(const ENUM_FILE_TYPE file_type,const string file_name)
  {
   CFileGen::SetName(file_type,file_name);
   return ::FileIsExist(CFileGen::m_name);
  }
//+------------------------------------------------------------------+

В метод передаётся тип записываемого файла и имя файла, наличие которого необходимо проверить. Затем устанавливается имя файла, состоящее из пути к файлу, его имени и расширения и при помощи функции FileIsExist() возвращается результат проверки существования файла с таким именем.

Метод, устанавливающий имя файла, состоящее из пути к файлу, его имени и расширения:

//+------------------------------------------------------------------+
//| Устанавливает имя файла                                          |
//+------------------------------------------------------------------+
void CFileGen::SetName(const ENUM_FILE_TYPE file_type,const string file_name)
  {
   CFileGen::SetSubFolder(file_type);
   CFileGen::m_name=CFileGen::m_folder_name+CFileGen::m_subfolder+file_name+CFileGen::Extension(file_type);
  }
//+------------------------------------------------------------------+

В метод передаётся тип записываемого файла и имя файла,из которых метод соберёт полное имя файла, включающее папку хранения файлов библиотеки, подпапку по расширению файла, создаваемую методом SetSubFolder(), переданное в метод имя ( file_name) и расширение файла, создаваемое методом Extension() по типу файла (звук или изображение). Полученный результат записывается в переменную-член класса m_name.

Метод для установки имени подпапки в зависимости от типа файла (звук или изображение):

//+------------------------------------------------------------------+
//| Устанавливает имя подпапки                                       |
//+------------------------------------------------------------------+
void CFileGen::SetSubFolder(const ENUM_FILE_TYPE file_type)
  {
   CFileGen::m_subfolder=(file_type==FILE_TYPE_BMP ? "Images\\" : file_type==FILE_TYPE_WAV ? "Sounds\\" : "");
  }
//+------------------------------------------------------------------+

В метод передаётся тип файла, и в зависимости от этого типа в переменную-член класса m_subfolder записывается наименование подпапки Images\ или Sounds\.

Метод, возвращающий расширение файла по его типу:

//+------------------------------------------------------------------+
//| Возвращает расширение файла по его типу                          |
//+------------------------------------------------------------------+
string CFileGen::Extension(const ENUM_FILE_TYPE file_type)
  {
   string ext=::StringSubstr(::EnumToString(file_type),10);
   if(!::StringToLower(ext))
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_CONV_TO_LOWERCASE),CMessage::Retcode(::GetLastError()));
   return "."+ext;
  }
//+------------------------------------------------------------------+

В метод передаётся тип файла. Затем из текстового представления ( EnumToString()) переданной в метод константы перечисления ENUM_FILE_TYPE извлекается расширение при помощи функции StringSubstr() (для звукового файла из строки "FILE_TYPE_WAV" извлекается подстрока "WAV", для растрового файла — "BMP"), затем все символы извлечённой подстроки с расширением файла преобразуются в строчные при помощи StringToLower(), к строке (перед ней) добавляется знак "точка" (.) и полученный результат возвращается из метода.

На этом создание класса-генератора файлов завершено.

Теперь у нас есть возможность хранить в исходном коде бинарные данные любых звуковых и растровых файлов и автоматически при запуске программы создавать из этих данных полноценные файлы, которые будут размещены в соответствующие папки, из которых библиотека будет получать эти файлы и использовать их по назначению.
Но для удобного их получения нам необходимо как-то описать имеющиеся в наличии файлы и иметь к ним быстрый и удобный доступ. Для этого мы создадим класс-коллекцию ресурсов программы. Но это будет не такая коллекция, которые мы создаём в библиотеке — списки указателей на объекты коллекций, а коллекция объектов-описаний файлов.
Для каждого созданного физического файла мы будем создавать объект-описатель этого файла, в котором будет прописано его имя, путь и описание. И получать доступ к физическим файлам мы будем по этим объектам-описателям. А описанием файла в таких объектах будет любое текстовое описание, которое мы будем добавлять к объекту-описателю для каждого конкретного файла.
Например, для звука щелчка мышью можно описание сделать в виде строки "Звук щелчка мышью 01", или "Клик 01", или.., да что угодно — как удобно, так и описываем созданные файлы. А затем, для получения описателя требуемого файла, мы просто ищем его по нашему описанию, вводя его в параметры поиска. Метод поиска вернёт нам индекс объекта-описателя файла, и по нему мы получим свойства уже физичесого файла.

Класс-коллекция ресурсов программы

В папке библиотеки \MQL5\Include\DoEasy\Collections\ создадим новый класс CResourceCollection в файле ResourceCollection.mqh. Прямо в листинге этого нового класса напишем ещё один класс — класс объекта-описателя, экземпляры которого мы будем создавать для каждого нового файла и добавлять в коллекцию описателей:

//+------------------------------------------------------------------+
//|                                           ResourceCollection.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\\Services\\FileGen.mqh"
//+------------------------------------------------------------------+
//| Класс объекта-описантеля файла ресурса библиотеки                |
//+------------------------------------------------------------------+
class CResObj : public CObject
  {
private:
   string            m_file_name;      // Путь + имя файла + расширение
   string            m_description;    // Текстовое описание файла
public:
//--- Устанавливает (1) имя файла, (2) описание файла
   void              FileName(const string name)                  { this.m_file_name=name;      }
   void              Description(const string descr)              { this.m_description=descr;   }
//--- Возвращает (1) имя файла, (2) описание файла
   string            FileName(void)                         const { return this.m_file_name;    }
   string            Description(void)                      const { return this.m_description;  }
//--- Сравнивает объекты CResObj между собой по свойствам (для поиска равных объектов-ресурсов)
   bool              IsEqual(const CResObj* compared_obj)   const { return this.Compare(compared_obj,0)==0; }
//--- Сравнивает объекты CResObj между собой по всем свойствам (для сортировки)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Конструктор
                     CResObj(void){;}
  };
//+------------------------------------------------------------------+
//| Сравнивает объекты CResObj между собой                           |
//+------------------------------------------------------------------+
int CResObj::Compare(const CObject *node,const int mode=0) const
  {
   const CResObj *obj_compared=node;
   if(mode==0)
      return(this.m_file_name>obj_compared.m_file_name ? 1 : this.m_file_name<obj_compared.m_file_name ? -1 : 0);
   else
      return(this.m_description>obj_compared.m_description ? 1 : this.m_description<obj_compared.m_description ? -1 : 0);
  }
//+------------------------------------------------------------------+

К листингу сразу же подключим необходимые файлы — класс динамического массива указателей на экземпляры класса CObject и его наследников стандартной библиотеки и класс-генератор файлов библиотеки CFileGen.

Чтобы объект-описатель можно было хранить в списке CArrayObj, нам необходимо этот объект сделать наследником базового класса стандартной библиотеки CObject.
Все переменные-члены класса и методы класса описаны в коде в комментариях, и не будем останавливаться на подробном разборе их назначения.
Уточню только, что виртуальный метод Compare() сравнивает поля двух объектов-описателей по умолчанию по имени файла. При равенстве сравниваемых полей двух объектов, метод возвращает ноль. Если требуется сравнить объекты по описанию файла, то режим mode нужно задать равным 1.
А метод IsEqual(), возвращающий флаг равенства объектов, сравнивает объекты только по имени файла (ведь двух файлов с одинаковыми именами в одной папке быть не может). Метод возвращает результат работы метода Compare() с режимом mode по умолчанию (0), что соответствует сравнению по имени файла.

Напишем класс-коллекцию объектов-описателей файлов ресурсов программы:

//+------------------------------------------------------------------+
//| Класс-коллекция объектов-описателей файлов ресурсов              |
//+------------------------------------------------------------------+
class CResourceCollection
  {
private:
//--- Список указателей на объекты-описатели
   CArrayObj         m_list_dscr_obj;
//--- Создаёт объект-описатель файла и добавляет его в список
   bool              CreateFileDescrObj(const string file_name,const string descript);
//--- Добавляет в список объектов-описателей новый объект
   bool              AddToList(CResObj* element);
public:
//--- Создаёт файл и добавляет его описание в список
   bool              CreateFile(const ENUM_FILE_TYPE file_type,const string file_name,const string descript,const uchar &file_data_array[]);
//--- Возвращает (1) список указателей на объекты-описатели, (2) индекс объекта-описателя файла по описанию
   CArrayObj        *GetList(void)  { return &this.m_list_dscr_obj;  }
   int               GetIndexResObjByDescription(const string file_description);
//--- Конструктор
                     CResourceCollection(void);
  };
//+------------------------------------------------------------------+

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

За пределами тела класса напишем конструктор класса:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CResourceCollection::CResourceCollection()
  {
   this.m_list_dscr_obj.Clear();
   this.m_list_dscr_obj.Sort();
  }
//+------------------------------------------------------------------+

Здесь: очищается список объектов-описателей и списку ставится флаг сортировки по имени файла (по умолчанию 0).

Метод создания файла и добавления объекта-описателя в список-коллекцию:

//+------------------------------------------------------------------+
//| Создаёт файл и добавляет его объект-описатель в список           |
//+------------------------------------------------------------------+
bool CResourceCollection::CreateFile(const ENUM_FILE_TYPE file_type,const string file_name,const string descript,const uchar &file_data_array[])
  {
   if(!CFileGen::Create(file_type,file_name,file_data_array))
     {
      if(!::FileIsExist(CFileGen::Name()))
         return false;
     }
   return this.CreateFileDescrObj(file_type,CFileGen::Name(),descript);
  }
//+------------------------------------------------------------------+

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

Если файл создать не удалось при помощи метода Create() класса CFileGen, и файл действительно отсутствует (он может уже существовать, и по этой причине повторно создан быть не может), то возвращаем false.
Если же файла не было, и теперь он создан, то возвращаем из метода результат работы метода создания объекта-описателя и добавления его в список-коллекцию объектов-описателей файлов.

Метод создания объекта-описателя файла и добавления его в список-коллекцию:

//+------------------------------------------------------------------+
//| Создаёт объект-описатель файла и добавляет его в список          |
//+------------------------------------------------------------------+
bool CResourceCollection::CreateFileDescrObj(const string file_name,const string descript)
  {
   CResObj *res_dscr=new CResObj();
   if(res_dscr==NULL)
     {
      Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_RES_LINK));
      return false;
     }
   res_dscr.FileName(file_name);
   res_dscr.Description(descript);
   if(!this.AddToList(res_dscr))
     {
      delete res_dscr;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

В метод передаются имя файла и его описание.
Создаём новый объект-описатель файла, и если создать объект не удалось, то выведем об этом сообщение в журнал и вернём false.
Далее устанавливаем для вновь созданного объекта-описателя имя файла и описание файла и добавляем его в список-коллекцию. Если объект добавить не удалось, то объект должен быть уничтожен во-избежание утечки памяти — удаляем объект и возвращаем false.
Иначе возвращаем true —  объект успешно создан и добавлен в коллекцию.

Метод, добавляющий новый объект-описатель в список-коллекцию:

//+------------------------------------------------------------------+
//| Добавляет в список объектов-описателей файлов новый объект       |
//+------------------------------------------------------------------+
bool CResourceCollection::AddToList(CResObj *element)
  {
   this.m_list_dscr_obj.Sort();
   if(this.m_list_dscr_obj.Search(element)>WRONG_VALUE)
      return false;
   return this.m_list_dscr_obj.Add(element);
  }
//+------------------------------------------------------------------+

В метод передаётся объект-описатель, списку-коллекции устанавливается флаг сортировки по имени файла, и если объект с таким именем уже есть в списке, то возвращаем false.
Иначе — возвращаем результат работы метода добавления объекта в список.

Метод, возвращающий индекс объекта-описателя в списке-коллекции по описанию файла:

//+------------------------------------------------------------------+
//| Возвращает индекс объекта-описателя файла по описанию файла      |
//+------------------------------------------------------------------+
int CResourceCollection::GetIndexResObjByDescription(const string file_description)
  {
   CResObj *obj=new CResObj();
   if(obj==NULL)
      return WRONG_VALUE;
   obj.Description(file_description);
   this.m_list_dscr_obj.Sort(1);
   int index=this.m_list_dscr_obj.Search(obj);
   delete obj;
   return index;
  }
//+------------------------------------------------------------------+

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

Класс коллекции объектов-описателей файлов готов.

Теперь нам необходимо добавить несколько методов в базовый объект библиотеки CEngine.

Откроем файл \MQL5\Include\DoEasy\Engine.mqh и
подключим к нему файл коллекции объектов-описателей:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
//+------------------------------------------------------------------+

Создадим новый объект-коллекцию ресурсов программы (коллекция описателей файлов):

//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CAccountsCollection  m_accounts;                      // Коллекция аккаунтов
   CSymbolsCollection   m_symbols;                       // Коллекция символов
   CResourceCollection  m_resource;                      // Список ресурсов
   CArrayObj            m_list_counters;                 // Список счётчиков таймера
   int                  m_global_error;                  // Код глобальной ошибки
   bool                 m_first_start;                   // Флаг первого запуска
   bool                 m_is_hedge;                      // Флаг хедж-счёта
   bool                 m_is_tester;                     // Флаг работы в тестере
   bool                 m_is_market_trade_event;         // Флаг торгового события на счёте
   bool                 m_is_history_trade_event;        // Флаг торгового события в истории счёта
   bool                 m_is_account_event;              // Флаг события изменения аккаунта
   bool                 m_is_symbol_event;               // Флаг события изменения свойств символа
   ENUM_TRADE_EVENT     m_last_trade_event;              // Последнее торговое событие на счёте
   int                  m_last_account_event;            // Последнее событие в свойствах счёта
   int                  m_last_symbol_event;             // Последнее событие в свойствах символа
//--- Возвращает индекс счётчика по id

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

//--- Таймер
   void                 OnTimer(void);
//--- Устанавливает список используемых символов
   bool                 SetUsedSymbols(const string &array_symbols[])   { return this.m_symbols.SetUsedSymbols(array_symbols);}
   
//--- Создаёт файл ресурса
   bool                 CreateFile(const ENUM_FILE_TYPE file_type,const string file_name,const string descript,const uchar &file_data_array[])
                          {
                           return this.m_resource.CreateFile(file_type,file_name,descript,file_data_array);
                          }
//--- Возвращает список ссылок на ресурсы
   CArrayObj           *GetListResource(void)                                 { return this.m_resource.GetList();                               }
   int                  GetIndexResObjByDescription(const string file_name)   { return this.m_resource.GetIndexResObjByDescription(file_name);  }

//--- Возвращает (1) милисекунды, (2) причину, (3) источник события из его long-значения
   ushort               EventMSC(const long lparam)               const { return this.LongToUshortFromByte(lparam,0);         }
   ushort               EventReason(const long lparam)            const { return this.LongToUshortFromByte(lparam,1);         }
   ushort               EventSource(const long lparam)            const { return this.LongToUshortFromByte(lparam,2);         }
   
//--- Конструктор/Деструктор
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Методы имеют те же названия, что и методы в классе-коллекции объектов-описателей и вызывают одноимённые методы из этого класса.

Для тестирования созданных классов создадим все файлы по имеющимся данным в подключаемых файлах DataSND.mqh и DataIMG.mqh в бинарных массивах. В журнал "Эксперты" выведем результаты создания файлов из бинарных массивов и выведем содержание получившегося списка-коллекции объектов-описателей файлов. Также проиграем один из звуков и выведем в правый нижний угол экрана изображение, состоящее из двух созданных файлов-изображений красного и зелёного светодиодов.

Тестирование доступа к автоматически созданным файлам

Возьмём советник TestDoEasyPart19.mq5 из прошлой статьи и сохраним его в новую папку \MQL5\Experts\TestDoEasy\ Part20\ под новым именем TestDoEasyPart20.mq5.

Так как файлы должны создаваться во время первого запуска программы, то и работу с классами по созданию ресурсов программы организуем в обработчике OnInit(). В нём же и протестируем полученный результат.

Добавим в обработчик OnInit(), в самый его конец, такой блок кода:

//--- Установка параметров торгового класса CTrade
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Создание и проверка файлов ресурсов
   Print("\n",TextByLanguage("--- Проверка успешности создания файлов ---","--- Verifying that the files were created ---"));
   string dscr=TextByLanguage("Проверка существования файла: ","Checking the existence of a file: ");
   
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"),sound_array_coin_01);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_coin_01"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Sound fallen coins"),sound_array_coin_02);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_coin_02"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Sound of coins"),sound_array_coin_03);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_coin_03"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"),sound_array_coin_04);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_coin_04"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Click on the button sound 1"),sound_array_click_01);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_click_01"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Click on the button sound 1"),sound_array_click_02);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_click_02"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Click on the button sound 1"),sound_array_click_03);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_click_03"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","The sound of the cash machine"),sound_array_cash_machine_01);
   if(CFileGen::IsExist(FILE_TYPE_WAV,"sound_array_cash_machine_01"))
      Print(dscr+CFileGen::Name(),": OK");

   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   if(CFileGen::IsExist(FILE_TYPE_BMP,"img_array_spot_green"))
      Print(dscr+CFileGen::Name(),": OK");
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);
   if(CFileGen::IsExist(FILE_TYPE_BMP,"img_array_spot_red"))
      Print(dscr+CFileGen::Name(),": OK");

//--- Проверка списка описания файлов
   Print("\n",TextByLanguage("--- Проверка списка описания файлов ---","--- Checking the file description list ---"));
   CArrayObj* list_res=engine.GetListResource();
   if(list_res!=NULL)
     {
      //--- В цикле просмотрим весь список описаний файлов
      for(int i=0;i<list_res.Total();i++)
        {
         CResObj *res=list_res.At(i);
         if(res==NULL)
            continue;
         //--- Распечатаем в журнал пути к файлам и описание файлов
         string type=(StringFind(res.FileName(),"\\Sounds\\")>0 ? TextByLanguage("Звук ","Sound ") : TextByLanguage("Изображение ","Image "));
         Print(type,string(i+1),": ",TextByLanguage("Имя файла :","File name: "),res.FileName()," (",res.Description(),")");
         
         //--- Если текущее описание соответствует звуку упавшей монетки 1, то проиграем этот звук
         if(res.Description()==TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"))
           {
            CMessage::PlaySound(res.FileName());
           }
        }
      //--- Создание изображения красно-зелёного светодиода
      //--- Получим индексы описаний изображений красного и зелёного светодиодов
      int index_r=engine.GetIndexResObjByDescription(TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""));
      int index_g=engine.GetIndexResObjByDescription(TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""));
      if(index_g>WRONG_VALUE && index_r>WRONG_VALUE)
        {
         //--- Получим из списка два объекта с описанием файлов
         CResObj *res_g=list_res.At(index_g);
         CResObj *res_r=list_res.At(index_r);
         if(res_g==NULL || res_r==NULL)
           {
            Print(TextByLanguage("Не удалось получить данные с описанием файла изображения","Failed to get image file description data"));
            return(INIT_SUCCEEDED);
           }
         //--- Создадим на основе файлов изображений кнопку
         long chart_ID=ChartID();
         string name=prefix+"RedGreenSpot";
         if(ObjectCreate(chart_ID,name,OBJ_BITMAP_LABEL,0,0,0))
           {
            ObjectSetString(chart_ID,name,OBJPROP_BMPFILE,0,"\\Files\\"+res_g.FileName());   // Изображение для нажатой кнопки
            ObjectSetString(chart_ID,name,OBJPROP_BMPFILE,1,"\\Files\\"+res_r.FileName());   // Изображение для отжатой кнопки
            ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,CORNER_RIGHT_LOWER);
            ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER);
            ObjectSetInteger(chart_ID,name,OBJPROP_STATE,true);
            ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,16); 
            ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,16);
            ObjectSetInteger(chart_ID,name,OBJPROP_XOFFSET,0); 
            ObjectSetInteger(chart_ID,name,OBJPROP_YOFFSET,0);
            ObjectSetInteger(chart_ID,name,OBJPROP_BACK,false);
            ObjectSetInteger(chart_ID,name,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS);
            ChartRedraw(chart_ID);
           }
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

В листинге описаны все действия — оставим их для самостоятельного разбора.

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



Как видим — всё создаётся, в журнал выводятся сообщения об успешном создании файлов, изображение светодиода переключает цвет при щелчке по изображению, а если открыть каталог данных терминала (в терминале пункт меню Файл --> Открыть каталог данных), зайти в папку MQL5\Files\DoEasy\Resource\, то там будет две подпапки — Images и Sounds, в которых лежат все только что созданные файлы.

Что дальше

Со следующей статьи мы открываем новый раздел библиотеки — торговые классы и всё с ними связанное.

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

К содержанию

Статьи этой серии:

Часть 1. Концепция, организация данных
Часть 2. Коллекция исторических ордеров и сделок
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска
Часть 4. Торговые события. Концепция
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу
Часть 6. События на счёте с типом неттинг
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций
Часть 8. События модификации ордеров и позиций
Часть 9. Совместимость с MQL4 - Подготовка данных
Часть 10. Совместимость с MQL4 - События открытия позиций и активации отложенных ордеров
Часть 11. Совместимость с MQL4 - События закрытия позиций
Часть 12. Класс объекта "аккаунт", коллекция объектов-аккаунтов
Часть 13. События объекта "аккаунт"
Часть 14. Объект "Символ"
Часть 15. Коллекция объектов-символов
Часть 16. События коллекции символов
Часть 17. Интерактивность объектов библиотеки
Часть 18. Интерактивность объекта-аккаунт и любых других объектов библиотеки
Часть 19. Класс сообщений библиотеки