Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XX): criação e armazenamento de recursos de programas

10 janeiro 2020, 14:15
Artyom Trishkin
0
670

Sumário

Conceito

Muitas vezes, ao criar um programa, precisamos usar sons e imagens. Na linguagem MQL, existem várias maneiras de usar esse tipo de dados que estão relacionadas à necessidade de fazer upload de arquivos da área restrita de arquivos do terminal. Se o resultado final deve ser um arquivo compilado, basta anexar o arquivo como recurso e eliminar a necessidade de transferir arquivos adicionais para o programa funcionar. Apesar de este método ser adequado para publicar programas no Mercado mql5.com, pois aqui basta botar o arquivo executável, esse método não é de todo adequado para colocar o código fonte no CodeBase mql5.com, pois os arquivos de som *.wav e os de gráficos *.bmp não podem ser colocados nele. Além disso, sem eles, o código fonte não funciona bem.
O que fazer em tal situação? É óbvio que se devem armazenar os dados de todos os arquivos necessários no código fonte em arquivos de inclusão *.mqh na forma de arrays binários. Em seguida, ao inicializar o programa em si, a partir dos conjuntos de dados é necessário criar nas pastas necessárias todos os arquivos necessários para o programa funcionar. Assim, ao executar o código, o próprio programa cria todos os arquivos necessários, coloca-os em pastas, e funciona corretamente. Para o usuário do programa, este processo passa quase despercebido, porém, apenas o tempo da primeira inicialização necessária para criar e gravar os arquivos ausentes aumenta ligeiramente.

Para trabalhar com dados de arquivos, criaremos duas classes:

  • uma classe-gerador de arquivos a partir de dados prontos,
  • uma classe para trabalhar com a lista de arquivos criados, nomeadamente com a coleção de objetos-especificadores de arquivos.
A classe-gerador de arquivos terá um conjunto de métodos estáticos para criar arquivos a partir de dados que correspondam a eles. Além disso, esses métodos estarão disponíveis em qualquer lugar da biblioteca.
A classe para trabalhar com os arquivos criados incluirá uma lista contendo todos os arquivos e métodos criados para ter acesso a eles de acordo com os dados armazenados na lista. Em essência, a lista conterá descrições simples dos arquivos criados (nome e descrição do arquivo) e, de acordo com esses dados, mediante consulta, sempre podemos obter o caminho para quelquer arquivo físico e trabalhar com ele.

Classe-gerador de arquivos

Vamos começar com o arquivo Datas.mqh. Nós adicionamos a ele as mensagens necessárias, que podem ser exibidas quando são criadas as classes.

À enumeração de índices de mensagens de texto adicionamos constantes que apontam para o local de novas mensagens no array de dados de texto da biblioteca:

//+------------------------------------------------------------------+
//| List of the library's text message indices                       |
//+------------------------------------------------------------------+
enum ENUM_MESSAGES_LIB
  {
   MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST,      // Beginning of the parameter list
   MSG_LIB_PARAMS_LIST_END,                           // End of the parameter list
   MSG_LIB_PROP_NOT_SUPPORTED,                        // Property not supported
   MSG_LIB_PROP_NOT_SUPPORTED_MQL4,                   // Property not supported in MQL4
   MSG_LIB_PROP_NOT_SUPPORTED_POSITION,               // Property not supported for position
   MSG_LIB_PROP_NOT_SUPPORTED_PENDING,                // Property not supported for pending order
   MSG_LIB_PROP_NOT_SUPPORTED_MARKET,                 // Property not supported for market order
   MSG_LIB_PROP_NOT_SUPPORTED_MARKET_HIST,            // Property not supported for historical market order
   MSG_LIB_PROP_NOT_SET,                              // Value not set
   MSG_LIB_PROP_EMPTY,                                // Not set
   
   MSG_LIB_SYS_ERROR,                                 // Error
   MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER,                  // Error. No such symbol on server
   MSG_LIB_SYS_FAILED_PUT_SYMBOL,                     // Failed to place to market watch. Error: 
   MSG_LIB_SYS_NOT_GET_PRICE,                         // Failed to get current prices. Error: 
   MSG_LIB_SYS_NOT_GET_MARGIN_RATES,                  // Failed to get margin ratios. Error: 
   MSG_LIB_SYS_NOT_GET_DATAS,                         // Failed to get data
   
   MSG_LIB_SYS_FAILED_CREATE_STORAGE_FOLDER,          // Failed to create folder for storing files. Error: 
   MSG_LIB_SYS_FAILED_ADD_ACC_OBJ_TO_LIST,            // Error. Failed to add current account object to collection list
   MSG_LIB_SYS_FAILED_CREATE_CURR_ACC_OBJ,            // Error. Failed to create account object with current account data
   MSG_LIB_SYS_FAILED_OPEN_FILE_FOR_WRITE,            // Could not open file for writing
   MSG_LIB_SYS_INPUT_ERROR_NO_SYMBOL,                 // Input error: no symbol
   MSG_LIB_SYS_FAILED_CREATE_SYM_OBJ,                 // Failed to create symbol object
   MSG_LIB_SYS_FAILED_ADD_SYM_OBJ,                    // Failed to add symbol
   
   MSG_LIB_SYS_NOT_GET_CURR_PRICES,                   // Failed to get current prices by event symbol
   MSG_LIB_SYS_EVENT_ALREADY_IN_LIST,                 // This event is already in the list
   MSG_LIB_SYS_FILE_RES_ALREADY_IN_LIST,              // This file already created and added to list:
   MSG_LIB_SYS_FAILED_CREATE_RES_LINK,                // Error. Failed to create object pointing to resource file
   MSG_LIB_SYS_ERROR_ALREADY_CREATED_COUNTER,         // Error. Counter with ID already created
   MSG_LIB_SYS_FAILED_CREATE_COUNTER,                 // Failed to create timer counter
   MSG_LIB_SYS_FAILED_CREATE_TEMP_LIST,               // Error creating temporary list
   MSG_LIB_SYS_ERROR_NOT_MARKET_LIST,                 // Error. This is not a market collection list
   MSG_LIB_SYS_ERROR_NOT_HISTORY_LIST,                // Error. This is not a history collection list
   MSG_LIB_SYS_FAILED_ADD_ORDER_TO_LIST,              // Could not add order to the list
   MSG_LIB_SYS_FAILED_ADD_DEAL_TO_LIST,               // Could not add deal to the list
   MSG_LIB_SYS_FAILED_ADD_CTRL_ORDER_TO_LIST,         // Failed to add control order
   MSG_LIB_SYS_FAILED_ADD_CTRL_POSITION_TO_LIST,      // Failed to add control position
   MSG_LIB_SYS_FAILED_ADD_MODIFIED_ORD_TO_LIST,       // Could not add modified order to the list of modified orders
    
   MSG_LIB_SYS_NO_TICKS_YET,                          // No ticks yet
   MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT,              // Could not create object structure
   MSG_LIB_SYS_FAILED_WRITE_UARRAY_TO_FILE,           // Could not write uchar array to file
   MSG_LIB_SYS_FAILED_LOAD_UARRAY_FROM_FILE,          // Could not load uchar array from file
   MSG_LIB_SYS_FAILED_CREATE_OBJ_STRUCT_FROM_UARRAY,  // Could not create object structure from uchar array
   MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,      // Failed to save object structure to uchar array, error
   MSG_LIB_SYS_ERROR_INDEX,                           // Error. "index" value should be within 0 - 3
   MSG_LIB_SYS_ERROR_FAILED_CONV_TO_LOWERCASE,        // Failed to convert string to lowercase, error
   
   MSG_LIB_SYS_ERROR_EMPTY_STRING,                    // Error. Predefined symbols string empty, to be used
   MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY,        // Failed to prepare array of used symbols. Error 
   MSG_LIB_SYS_INVALID_ORDER_TYPE,                    // Invalid order type:
   

No array de mensagens de texto escrevemos os textos em russo e inglês correspondentes às constantes dos índices:

//+------------------------------------------------------------------+
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: "},


Devemos notar que a ordem dos textos no array deve corresponder exatamente à ordem de declaração das constantes dos índices na enumeração.

Para que a classe-gerador de arquivos funcione, é necessário criar bancos de dados a partir dos quais a classe pegará dados dos arquivos de som e dos arquivos de imagem. Os arrays unsigned char devem ter esse tipo de dados. Sua criação requer a presença de arquivos de som (*.wav) e de bitmap (*.bmp), que armazenaremos no código fonte da biblioteca. Para exemplificar, eu já tenho preparados alguns dados de teste. Os dados de som e de bitmap serão armazenados cada um em seu próprio arquivo incluído.

No diretório raiz da biblioteca \MQL5\Include\DoEasy\ criamos o arquivo incluído DataSND.mqh e imediatamente escrevemos o nome dos arrays de dados daqueles arquivos de som que colocaremos nos arrays (no entanto, depois podemos alterar os nomes dos arrays de dados, pois eles são necessários apenas para pesquisa rápida na listagem do local onde declarado um array contendo um determinado arquivo, uma vez que os arrays podem ser grandes, mas não mais do que de 16 megabytes):

//+------------------------------------------------------------------+
//|                                                      DataSND.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
//+------------------------------------------------------------------+
//| Звуки                                                            |
//+------------------------------------------------------------------+
/*
   sound_array_coin_01           // Falling coin 1
   sound_array_coin_02           // Falling coins
   sound_array_coin_03           // Coins
   sound_array_coin_04           // Falling coin 2
   //---
   sound_array_click_01          // Button click 1
   sound_array_click_02          // Button click 2
   sound_array_click_03          // Button click 3
   //---
   sound_array_cash_machine_01   // Cash register 1
*/
//+------------------------------------------------------------------+

Para inserir um arquivo no código fonte do programa, podemos usar o item de menu "Editar --> Inserir --> Arquivo como matriz binária":


Depois de selecionar este item, será aberta uma janela, na qual precisaremos encontrar um arquivo preparado anteriormente para carregar seus dados num array. O array será criado automaticamente com base no nome do arquivo selecionado (o exemplo não está completo, pois há muitos dados binários):

//+------------------------------------------------------------------+
//|                                                      DataSND.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Audio                                                            |
//+------------------------------------------------------------------+
/*
   sound_array_coin_01           // Falling coin 1
   sound_array_coin_02           // Falling coins
   sound_array_coin_03           // Coins
   sound_array_coin_04           // Falling coin 2
   //---
   sound_array_click_01          // Button click 1
   sound_array_click_02          // Button click 2
   sound_array_click_03          // Button click 3
   //---
   sound_array_cash_machine_01   // Cash register 1
*/
//+------------------------------------------------------------------+
//| Falling coin 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,

Como os dados do array repetem completamente os dados do arquivo, esses arrays resultam bastante grandes. É por isso que digitei com antecedência os nomes dos arrays criados, para pular rapidamente para o início de cada listagem usando o atalho de pesquisa Ctrl+F.

Agora, resta adicionar à listagem desse arquivo o número necessário de arrays de dados de som. Já criei vários sons de teste e, como o arquivo ficou grande, aqui não faz sentido listá-lo. Em vez disso, podemos vê-lo nos arquivos da biblioteca anexados no final do artigo.

Exatamente da mesma maneira criaremos um arquivo contendo dados de bitmap chamado DataIMG.mqh. Nele, eu também já inseri dois arrays com a imagem de uma lâmpada LED de duas cores: um array de dados com um LED verde, no outro, um vermelho:

//+------------------------------------------------------------------+
//|                                                      DataIMG.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
//+------------------------------------------------------------------+
//| Images                                                           |
//+------------------------------------------------------------------+
/*
   img_array_spot_green             // Green LED 16x16, 32 bit
   img_array_spot_red               // Red LED 16x16, 32 bit
*/
//+------------------------------------------------------------------+
//| Green LED 32 bit, alpha                                          |
//+------------------------------------------------------------------+
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
  };
//+------------------------------------------------------------------+
//| Red LED 32 bit, alpha                                            |
//+------------------------------------------------------------------+
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,

Da mesma maneira que no exemplo com dados de som, não forneço aqui a listagem completa do arquivo resultante.

Para disponibilizar dados do arquivo binário na biblioteca, anexamos os arquivos de dados ao arquivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Datas.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+

No bloco de substituição de macro do arquivo Defines.mqh digitamos a substituição de macro indicando a pasta dos dados de recurso da biblioteca:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG                   ("Russian")                // Country language
#define END_TIME                       (D'31.12.3000 23:59:59')   // End date for account history data requests
#define TIMER_FREQUENCY                (16)                       // Minimal frequency of the library timer in milliseconds
//--- Parameters of the orders and deals collection timer
#define COLLECTION_ORD_PAUSE           (250)                      // Orders and deals collection timer pause in milliseconds
#define COLLECTION_ORD_COUNTER_STEP    (16)                       // Increment of the orders and deals collection timer counter
#define COLLECTION_ORD_COUNTER_ID      (1)                        // Orders and deals collection timer counter ID
//--- Parameters of the account collection timer
#define COLLECTION_ACC_PAUSE           (1000)                     // Account collection timer pause in milliseconds
#define COLLECTION_ACC_COUNTER_STEP    (16)                       // Account timer counter increment
#define COLLECTION_ACC_COUNTER_ID      (2)                        // Account timer counter ID
//--- Symbol collection timer 1 parameters
#define COLLECTION_SYM_PAUSE1          (100)                      // Pause of the symbol collection timer 1 in milliseconds (for scanning market watch symbols)
#define COLLECTION_SYM_COUNTER_STEP1   (16)                       // Increment of the symbol timer 1 counter
#define COLLECTION_SYM_COUNTER_ID1     (3)                        // Symbol timer 1 counter ID
//--- Symbol collection timer 2 parameters
#define COLLECTION_SYM_PAUSE2          (300)                      // Pause of the symbol collection timer 2 in milliseconds (for events of the market watch symbol list)
#define COLLECTION_SYM_COUNTER_STEP2   (16)                       // Increment of the symbol timer 2 counter
#define COLLECTION_SYM_COUNTER_ID2     (4)                        // Symbol timer 2 counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x7779)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777A)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777B)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777C)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777D)                   // Symbol collection list ID
//--- Data parameters for file operations
#define DIRECTORY                      ("DoEasy\\")               // Library directory for storing object folders
#define RESOURCE_DIR                   ("DoEasy\\Resource\\")     // Library directory for storing resource folders
//--- Symbol parameters
#define CLR_DEFAULT                    (0xFF000000)               // Default color
#define SYMBOLS_COMMON_TOTAL           (1000)                     // Total number of working symbols
//+------------------------------------------------------------------+

Dentro da subpasta da biblioteca Resource\ serão criadas automaticamente as pastas Sounds e Images para criar e armazenar nelas arquivos de som e de imagens, respectivamente.

Como, ao criar arquivos de arrays preparados, precisamos especificar a extensão do arquivo criado, então, para lhe mostrar ao método de criação de arquivo qual arquivo será criado e em qual das duas pastas colocá-lo, necessitamos um enumeração de tipos de dados, cujos dados serão gravados em arrays binários .
No final da listagem Defines.mqh, digitamos a enumeração necessária:

//+------------------------------------------------------------------+
//| Data for working with program resource data                      |
//+------------------------------------------------------------------+
enum ENUM_FILE_TYPE
  {
   FILE_TYPE_WAV,                                           // wav file
   FILE_TYPE_BMP,                                           // bmp file
  };
//+------------------------------------------------------------------+

Como colocaremos todos os arquivos dos recursos da biblioteca nas pastas Sounds e Images, pastas essas que por sua vez botaremos no diretório MQL5\ Files\ do terminal, precisaremos ajustar o método PlaySound() da classe CMessage. Abrimos o arquivo \MQL5\Include\DoEasy\ Services\Message.mqh e
fazemos ajustes no caminho do arquivo no método PlaySound():

//+------------------------------------------------------------------+
//| Play an audio file                                               |
//+------------------------------------------------------------------+
bool CMessage::PlaySound(const string file_name)
  {
   bool res=::PlaySound("\\Files\\"+file_name);
   CMessage::m_global_error=(res ? ERR_SUCCESS : ::GetLastError());
   return res;
  }
//+------------------------------------------------------------------+

Neste caso, para reproduzir o arquivo, especificamos a subpasta \Files\ do terminal, uma vez que vamos armazenar todos os dados referentes a MQL5\ e a esta pasta, enquanto o restante caminho do arquivo é registrado ao criar o objeto-descrição do arquivo e é transferido a este método pelo parâmetro file_name.

Por enquanto, isso é suficiente para criar as classes que precisamos.

Agora, na pasta \MQL5\Include\DoEasy\Services\ criaremos a nova classe CFileGen no arquivo FileGen.mqh:

//+------------------------------------------------------------------+
//|                                                      FileGen.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| File generator class                                             |
//+------------------------------------------------------------------+
class CFileGen
  {
private:
   static string     m_folder_name;    // Name of a folder library resource files are stored in
   static string     m_subfolder;      // Name of a subfolder storing audio or bitmap files
   static string     m_name;           // File name
   static int        m_handle;         // File handle
//--- Set a (1) file, (2) subfolder name
   static void       SetName(const ENUM_FILE_TYPE file_type,const string file_name);
   static void       SetSubFolder(const ENUM_FILE_TYPE file_type);
//--- Return file extension by its type
   static string     Extension(const ENUM_FILE_TYPE file_type);
public:
//--- Return the (1) set name, (2) the flag of a file presence in the resource directory
   static string     Name(void)  { return CFileGen::m_name; }
   static bool       IsExist(const ENUM_FILE_TYPE file_type,const string file_name);
//--- Create a file out of the data array
   static bool       Create(const ENUM_FILE_TYPE file_type,const string file_name,const uchar &file_data_array[]);
  };
//+------------------------------------------------------------------+

Imediatamente, a biblioteca de funções de serviço DELib.mqh é anexada ao arquivo, uma vez que a ela já estão anexadas Defines.mqh e Message.mqh, necessárias para o funcionamento da classe:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
#property strict  // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
//+------------------------------------------------------------------+

Todos os membros-variáveis da classe e seus métodos são mencionados no código, e não faz sentido descrever seu propósito. Consideremos a implementação dos métodos.
Como os membros-variáveis da classe são estáticos, eles requerem inicialização explícita:

//+------------------------------------------------------------------+
//| Initialization of static variables                               |
//+------------------------------------------------------------------+
string CFileGen::m_folder_name=RESOURCE_DIR;
string CFileGen::m_subfolder="\\";
string CFileGen::m_name=NULL;
int    CFileGen::m_handle=INVALID_HANDLE;
//+------------------------------------------------------------------+

Método para criar um arquivo a partir de um array de dados:

//+------------------------------------------------------------------+
//| Create a file out of a data array                                |
//+------------------------------------------------------------------+
bool CFileGen::Create(const ENUM_FILE_TYPE file_type,const string file_name,const uchar &file_data_array[])
  {
   //--- Set a file name consisting of the file path, its name and extension
   CFileGen::SetName(file_type,file_name);
   //--- If such a file already exists, return 'false'
   if(::FileIsExist(CFileGen::m_name))
      return false;
   //--- Open the file with the generated name for writing
   CFileGen::m_handle=::FileOpen(CFileGen::m_name,FILE_WRITE|FILE_BIN);
   //--- If failed to create the file, receive an error code, display the file opening error message and return '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;
     }
   //--- Write the contents of the file_data_array[] array, close the file and return 'true'

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

O método usa a função padrão FileWriteArray(), que permite registrar numa arquivo binário arrays contento quaisquer tipos de dados, exceto strings.
Ao método são transferidos o tipo do arquivo registrado (som ou imagem), o nome do futuro arquivo e o array contendo o conjunto de dados binários do arquivo a ser criado.
Todas as ações, realizadas pelo método, são registradas em sua listagem - elas são simples e claras, por isso não vamos debruçar-nos sobre ela em detalhes.

Método que retorna o sinalizador que indica se o arquivo existe no diretório de recursos:

//+------------------------------------------------------------------+
//| The flag of a file presence in the resource directory            |
//+------------------------------------------------------------------+
bool CFileGen::IsExist(const ENUM_FILE_TYPE file_type,const string file_name)
  {
   CFileGen::SetName(file_type,file_name);
   return ::FileIsExist(CFileGen::m_name);
  }
//+------------------------------------------------------------------+

Ao método é transferido o tipo do arquivo registrado e o nome do arquivo, cuja presença é necessário verificar. Em seguida, é definido o nome do arquivo consistindo no seu caminho, nome e extensão. Com ajuda da função FileIsExist() é retornado o resultado da verificação de presença de um arquivo com tal nome.

Método que define o nome do arquivo consistindo no seu caminho, nome e extensão:

//+------------------------------------------------------------------+
//| Set a file name                                                  |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Ao método são transferidos o tipo do arquivo registrado e o nome do arquivo, e a partir deles o método coleta o nome completo do arquivo incluindo a pasta de armazenamento de arquivos da biblioteca, a subpasta segundo a extensão do arquivo, criado pelo método SetSubFolder(), transferido para o método nome (file_name) e a extensão do arquivo, criada pelo método Extension() de acordo com o tipo de arquivo (som ou imagem). O resultado é registrado numa variável-membro da classe m_name.

Método para definir o nome da subpasta, dependendo do tipo de arquivo (som ou imagem):

//+------------------------------------------------------------------+
//| Set a subfolder 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\\" : "");
  }
//+------------------------------------------------------------------+

Dependendo do tipo de arquivo que é transferido ao método, na variável-membro da classe m_subfolder é registrado o nome da subpasta Images\ ou Sounds\.

Método que retorna a extensão do arquivo por seu tipo:

//+------------------------------------------------------------------+
//| Return file extension by its type                                |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Ao método é transferido o tipo de arquivo. Depois, a partir da notação ( EnumToString()) transferida ao método da constante da enumeração ENUM_FILE_TYPE é extraída a extensão com ajuda da função StringSubstr() (para o arquivo de som, da string "FILE_TYPE_WAV" é extraída a substring "WAV", já para o arquivo de bitmap, "BMP"), em seguida, todos os símbolos da substring extraída contendo a extensão do arquivo são convertidos em strings com ajuda de StringToLower(), no início da string é adicionado o sinal de ponto '.' e o do método é retornado resultado.

Assim fica concluída a criação de uma classe-gerador de arquivos.

Agora, no código fonte podemos armazenar dados binários de arquivos de som e de bitmap e, automaticamente, quando iniciado o programa, a partir desses dados criar arquivos completos, que serão colocados em pastas das quais a biblioteca os receberá e utilizará.
No entanto, para os obter facilmente, de alguma maneira precisamos descrever os arquivos existentes e ter um acesso rápido e conveniente a eles. Para fazer isso, criaremos uma classe-coleção de recursos do programa. Porém, ela, em vez de ser aquela coleção que criamos na biblioteca (listas de ponteiros para objetos da coleção), será uma coleção de objetos-descrições de arquivos.
Para cada arquivo físico criado, criaremos um objeto-especificador, no qual serão gravados seu nome, caminho e descrição. Vamos ter acesso a arquivos físicos através desses objetos-especificadores. Nesse tipo de objetos, qualquer descrição textual, que adicionarmos ao objeto-especificador para cada arquivo específico, será uma descrição do arquivo.
Por exemplo, para o som de clique do mouse, pode-se fazer uma descrição na forma da string "Som de clique do mouse 01", "Clique 01" e assim por diante, desde que conveniente. Em seguida, para obter a descrição do arquivo necessário, nós a buscamos de acordo com nossa descrição, inserindo-a nos parâmetros de pesquisa. O método de pesquisa nos retornará o índice do objeto-especificador de arquivo e, a partir dele, obteremos as propriedades do arquivo físico.

Classe-coleção de recursos de programa

Na pasta da biblioteca \MQL5\Include\DoEasy\Collections\ criamos a nova classe CResourceCollection no arquivo ResourceCollection.mqh. Diretamente na listagem da nova classe escreveremos mais uma classe, nomeadamente a classe do objeto-especificador, cujas instâncias criaremos para cada novo arquivo e adicionaremos à coleção de especificadores:

//+------------------------------------------------------------------+
//|                                           ResourceCollection.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\\Services\\FileGen.mqh"
//+------------------------------------------------------------------+
//| Descriptor object class for the library resource file            |
//+------------------------------------------------------------------+
class CResObj : public CObject
  {
private:
   string            m_file_name;      // Path + file name + extension
   string            m_description;    // File text description
public:
//--- Set (1) file name, (2) file description
   void              FileName(const string name)                  { this.m_file_name=name;      }
   void              Description(const string descr)              { this.m_description=descr;   }
//--- Return (1) file name, (2) file description
   string            FileName(void)                         const { return this.m_file_name;    }
   string            Description(void)                      const { return this.m_description;  }
//--- Compare CResObj objects by properties (to search for equal resource objects)
   bool              IsEqual(const CResObj* compared_obj)   const { return this.Compare(compared_obj,0)==0; }
//--- Compare CResObj objects by all properties (for sorting)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Constructor
                     CResObj(void){;}
  };
//+------------------------------------------------------------------+
//| Compare CResObj objects                                          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

À listagem anexamos imediatamente os arquivos necessários: a classe do array dinâmico de ponteiros para as instâncias da classe CObject e dos seus herdeiros da biblioteca padrão e a classe-gerador de arquivos da biblioteca CFileGen.

Para que o objeto-especificador possa ser armazenado na lista CArrayObj, precisamos tornar este objeto num herdeiro da classe base da biblioteca CObject padrão.
Todas as variáveis-membro da classe e os métodos da classe estão comentados no código, e não vamos nos debruçar sobre eles em detalhes.
Vou esclarecer apenas que o método virtual Compare() compara os campos de dois objetos-especificadores de acordo com o nome do arquivo por padrão. Se os campos de dois objetos a serem comparados forem iguais, o método retornará zero. Se for necessário comparar os objetos de acordo com a descrição do arquivo, será necessário definir o modo mode como 1.
Já o método IsEqual(), que retorna o sinalizador indicando se dois objetos são iguais, compara os objetos apenas de acordo com o nome do arquivo (afinal não podem haver dois arquivos com o mesmo nome na mesma pasta). O método retorna o resultado da operação do método Compare() com o modo mode por padrão (0), o que corresponde à comparação segundo o nome do arquivo.

Escrevamos uma classe-coleção de objetos-especificadores de arquivos de recursos do programa:

//+------------------------------------------------------------------+
//| Collection class of resource files descriptor objects            |
//+------------------------------------------------------------------+
class CResourceCollection
  {
private:
//--- List of pointers to descriptor objects
   CArrayObj         m_list_dscr_obj;
//--- Create a file descriptor object and add it to the list
   bool              CreateFileDescrObj(const string file_name,const string descript);
//--- Add a new object to the list of descriptor objects
   bool              AddToList(CResObj* element);
public:
//--- Create a file and add its description to the list
   bool              CreateFile(const ENUM_FILE_TYPE file_type,const string file_name,const string descript,const uchar &file_data_array[]);
//--- Return the (1) list of pointers to descriptor objects, (2) index of the file descriptor object by description
   CArrayObj        *GetList(void)  { return &this.m_list_dscr_obj;  }
   int               GetIndexResObjByDescription(const string file_description);
//--- Constructor
                     CResourceCollection(void);
  };
//+------------------------------------------------------------------+

Neste caso, também é descrito o propósito de cada método diretamente na listagem, por isso começamos já a analisar os métodos da classe.

Fora do corpo da classe, escrevemos o construtor da classe:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CResourceCollection::CResourceCollection()
  {
   this.m_list_dscr_obj.Clear();
   this.m_list_dscr_obj.Sort();
  }
//+------------------------------------------------------------------+

Neste caso, é limpa a lista de objetos-especificadores e para a lista é colocado um sinalizador de classificação de acordo com o nome do arquivo (por padrão 0).

Método para criar um arquivo e adicionar um objeto-especificador à lista-coleção:

//+------------------------------------------------------------------+
//| Create a file and add its descriptor object to the list          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Ao método são transferidos o tipo de arquivo criado (som ou imagem), o nome do arquivo, a descrição do arquivo e a referência ao array binário de dados do arquivo a partir do qual ele será criado.

Se não for possível criar o arquivo com ajuda do método Create() da classe CFileGen e o arquivo realmente estiver faltando (ele pode já existir e, por isso, não poder ser criado novamente), retornaremos false.
Se não havia arquivo e agora foi criado, do método retornamos o resultado do método de criação de objeto-especificador e o do da sua adição à lista-coleção de objetos-especificadores de arquivos.

Método para criar um objeto-especificador de arquivo e adicioná-lo à lista-coleção:

//+------------------------------------------------------------------+
//| Create a file descriptor object and add it to the list           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Ao método são transferidos o nome de arquivo e sua descrição.
Criamos um novo objeto-especificador de arquivo , e se não for possível criar o objeto, imprimimos no log uma mensagem sobre isto e retornamos false.
Em seguida, para o objeto-especificador recém-criado definimos o nome de arquivo, a descrição de arquivo, e o adicionamos à lista-coleção. Se não for possível adicionar o objeto, ele deverá ser destruído para evitar vazamento de memória, sendo assim, excluímos o objeto e retornamos false.
Caso contrário, retornamos true (objeto criado e adicionado à coleção com sucesso).

Método que adiciona um novo objeto-especificador à lista-coleção:

//+------------------------------------------------------------------+
//| Add a new object to the list of file descriptor objects          |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Ao método é transferido o objeto-especificador, na lista-coleção é definido um sinalizador de classificação por nome de arquivo, e se já houver um objeto com o mesmo nome na lista, retornaremos false.
Caso contrário, retornamos o resultado do método de adição de objeto à lista.

Método que retorna o índice do objeto-especificador na lista-coleção de acordo com a descrição do arquivo:

//+----------------------------------------------------------------------+
//| Return the index of the file descriptor object by a file description |
//+----------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Ao método é transferida a descrição do arquivo, segundo a qual é necessário encontrar o objeto-especificador. Criamos uma instância temporária do objeto-especificador e no seu campo de descrição definimos a descrição de arquivo transferida ao método.
Para a lista-coleção definimos um sinalizador de classificação por descrição de arquivo ( 1) e obtemos o índice do objeto-especificador cujo campo de descrição contém o texto da pesquisa.
Certamente, teremos que excluir o objeto temporário e retornar o índice obtido (-1 se na lista-coleção não existir um objeto com tal descrição).

A classe de coleção de objetos-especificadores de arquivo está pronta.

Agora, precisamos adicionar alguns métodos ao objeto base da biblioteca CEngine.

Abrimos o arquivo \MQL5\Include\DoEasy\Engine.mqh e
anexamos a ele o arquivo da coleção de objetos-especificadores:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/pt/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/pt/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+

Criamos um novo objeto-coleção de recursos do programa (coleção de especificadores de arquivo):

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CResourceCollection  m_resource;                      // Resource list
   CArrayObj            m_list_counters;                 // List of timer counters
   int                  m_global_error;                  // Global error code
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   bool                 m_is_account_event;              // Account change event flag
   bool                 m_is_symbol_event;               // Symbol change event flag
   ENUM_TRADE_EVENT     m_last_trade_event;              // Last account trading event
   int                  m_last_account_event;            // Last event in the account properties
   int                  m_last_symbol_event;             // Last event in the symbol properties
//--- Return the counter index by id

e adicionamos três novos métodos para trabalhar com a coleção de recursos do programa:

//--- Timer
   void                 OnTimer(void);
//--- Set the list of used symbols
   bool                 SetUsedSymbols(const string &array_symbols[])   { return this.m_symbols.SetUsedSymbols(array_symbols);}
   
//--- Create a resource file
   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);
                          }
//--- Return the list of links to resources
   CArrayObj           *GetListResource(void)                                 { return this.m_resource.GetList();                               }
   int                  GetIndexResObjByDescription(const string file_name)   { return this.m_resource.GetIndexResObjByDescription(file_name);  }

//--- Return event (1) milliseconds, (2) reason and (3) source from its 'long' value
   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);         }
   
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Os métodos têm os mesmos nomes que os métodos na classe-coleção de objetos-especificadores e chamam os métodos com o mesmo nome a partir desta classe.

Para testar as classes geradas, criaremos todos os arquivos de acordo com os dados disponíveis nos arquivos incluídos DataSND.mqh e DataIMG.mqh em arrays binários. No log "Experts", exibiremos os resultados da criação de arquivos a partir de arrays binários e o conteúdo da lista-coleção resultante de objetos-especificadores de arquivos. Também tocaremos um dos sons e exibiremos no canto inferior direito da tela uma imagem composta por dois arquivos de imagem criados de LEDs vermelho e verde.

Testando o acesso a arquivos gerados automaticamente

Pegamos o EA TestDoEasyPart19.mq5 do artigo anterior e o salvamos na nova pasta \MQL5\Experts\TestDoEasy\ Part20\ usando o novo nome TestDoEasyPart20.mq5.

Como os arquivos devem ser criados durante a primeira execução do programa, realizaremos o trabalho com as classes para criar recursos do programa no manipulador OnInit(). Nele, testamos o resultado obtido.

Adicionamos ao manipulador OnInit(), no final, este bloco de código:

//--- Set CTrade trading class parameters
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Create and check the resource files
   Print("\n",TextByLanguage("--- Проверка успешности создания файлов ---","--- Verifying files were created ---"));
   string dscr=TextByLanguage("Проверка существования файла: ","Checking existence of file: ");
   
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Sound of 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","Sound of 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 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 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 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("Звук кассового аппарата","Sound of 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");

//--- Check the file description list
   Print("\n",TextByLanguage("--- Проверка списка описания файлов ---","--- Checking file description list ---"));
   CArrayObj* list_res=engine.GetListResource();
   if(list_res!=NULL)
     {
      //--- Let's see the entire list of file descriptions
      for(int i=0;i<list_res.Total();i++)
        {
         CResObj *res=list_res.At(i);
         if(res==NULL)
            continue;
         //--- Display the paths to the files and the file description in the journal
         string type=(StringFind(res.FileName(),"\\Sounds\\")>0 ? TextByLanguage("Звук ","Sound ") : TextByLanguage("Изображение ","Image "));
         Print(type,string(i+1),": ",TextByLanguage("Имя файла :","File name: "),res.FileName()," (",res.Description(),")");
         
         //--- If the current description corresponds to the falling coin sound 1, play the appropriate sound
         if(res.Description()==TextByLanguage("Звук упавшей монетки 1","Sound of falling coin 1"))
           {
            CMessage::PlaySound(res.FileName());
           }
        }
      //--- Create the image of the red-green LED
      //--- Get the indices of red and green LEDs image descriptions
      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)
        {
         //--- Get two objects with files description from the list
         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);
           }
         //--- Create a button based on image files
         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());   // Released button image
            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);
  }
//+------------------------------------------------------------------+

Aqui criamos arquivos a partir de dados localizados em arrays binários no código fonte da biblioteca. Após criar cada arquivo, verificamos sua existência e imprimimos o resultado no log. Após criar todos os arquivos e seus especificadores, verificamos a lista completa de especificadores de arquivos adicionados à coleção no estágio de criação de arquivos.

A listagem descreve todas as ações. Vamos deixá-las para auto-análise.

Após compilar o EA, ele exibe no lo registros sobre os resultados da criação de arquivos, reproduz o som de uma moeda que cai e exibe um padrão de LED de duas imagens no canto inferior direito da tela. Podemos alternar as imagens clicando na imagem do LED (afinal, de fato, este é um botão que possui dois estados lig/deslig)



Como se pode ver, tudo está pronto, as mensagens sobre a criação bem-sucedida de arquivos são exibidas no log, a imagem do LED muda de cor quando a imagem é clicada e, se abrirmos o diretório de dados do terminal (no terminal, item de menu Arquivo -> Abrir diretório de dados), vamos para a pasta som de uma moeda descartada, haverá duas subpastas - Imagens e Sons - contendo todos os arquivos recém-criados.

O que vem agora?

No próximo artigo, abrimos uma nova seção da biblioteca falando sobre se deve armazenar os dados classes de negociação e tudo relacionado a elas.

Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

Artigos desta série:

Parte 1. Conceito, gerenciamento de dados e primeiros resultados
Parte 2. Coleção do histórico de ordens e negócios
Parte 3. Coleção de ordens e posições de mercado, busca e ordenação
Parte 4. Eventos de Negociação. Conceito
Parte 5. Classes e coleções de eventos de negociação. Envio de eventos para o programa
Parte 6. Eventos da conta netting
Parte 7. Eventos de ativação da ordem stoplimit, preparação da funcionalidade para os eventos de modificação de ordens e posições
Parte 8. Eventos de modificação de ordens e posições
Parte 9. Compatibilidade com a MQL4 - preparação dos dados
Parte 10. Compatibilidade com a MQL4 - eventos de abertura de posição e ativação de ordens pendentes
Parte 11. Compatibilidade com a MQL4 - eventos de encerramento de posição
Parte 12. Implementação da classe de objeto "conta" e da coleção de objetos da conta
Parte 13. Eventos do objeto conta
Parte 14. O objeto símbolo
Parte 15. Coleção de objetos-símbolos
Parte 16. Eventos de coleção de símbolos
Parte 17. Interatividade de objetos de biblioteca
Parte 18. Interatividade do objeto-conta e quaisquer de outros objetos da biblioteca
Parte 19. Classe de mensagens de biblioteca


Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/7195

Arquivos anexados |
MQL5.zip (3556.3 KB)
MQL4.zip (3553.08 KB)
Guia Prático do MQL5: Teste de estresse de uma estratégia de negociação utilizando os símbolos personalizados Guia Prático do MQL5: Teste de estresse de uma estratégia de negociação utilizando os símbolos personalizados

O artigo considera uma abordagem para o teste de estresse de uma estratégia de negociação usando os símbolos personalizados. Uma classe de símbolo personalizada é criada para essa finalidade. Esta classe é utilizada para receber os dados de ticks de fontes de terceiros, bem como realizar alterações das propriedades do símbolo. Com base nos resultados do trabalho realizado, nós consideraremos várias opções para alterar as condições de negociação, sob as quais uma estratégia de negociação está sendo testada.

Analisador Sintático HTML com o curl Analisador Sintático HTML com o curl

O artigo fornece a descrição de uma biblioteca simples para análise sintática (parser) de código HTML usando componentes de terceiros. Em particular, ela abrange as possibilidades de acessar dados que não podem ser recuperados usando os métodos HTTP GET e POST. Nós selecionaremos um site com páginas não muito extensas e tentaremos obter alguns dados interessantes dele.

Construtor de estratégia baseado nos padrões de Merill Construtor de estratégia baseado nos padrões de Merill

No artigo anterior, nós consideramos a aplicação dos padrões de Merill a vários dados, como em valores de preço em um gráfico de par de moeda e de indicadores padrão do MetaTrader 5: ATR, WPR, CCI, RSI, entre outros. Agora, vamos tentar criar um conjunto para a construção de estratégias baseado nos padrões de Merill.

Desenvolvimento do Oscilador Pivô Médio: um novo Indicador para a Média Móvel Acumulada Desenvolvimento do Oscilador Pivô Médio: um novo Indicador para a Média Móvel Acumulada

Este artigo apresenta o Oscilador Pivô Médio (PMO), uma implementação da média móvel cumulativa (CMA) como um indicador de negociação para as plataformas MetaTrader. Em particular, nós introduzimos primeiro o Pivô Médio (PM) como um índice de normalização para as séries temporais que calcula a fração entre qualquer ponto de dados e o CMA. Em seguida, nós criamos o PMO como a diferença entre as médias móveis aplicadas a dois sinais de PM. Também são relatadas algumas experiências preliminares realizadas no símbolo EURUSD para testar a eficácia do indicador proposto, deixando um amplo espaço para considerações e melhorias adicionais.