Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XX): Creación y guardado de los recursos del programa

10 diciembre 2019, 08:06
Artyom Trishkin
0
952

Contenido

Concepto

Con frecuencia, al crear un programa, necesitamos usar sonidos e imágenes. En el lenguaje MQL existen diversas posibilidades de uso de estos datos, y todas ellas se relacionan con la necesidad de cargar los archivos desde el sandbox de archivos del terminal. Si, como resultado final, planeamos compilar el archivo, bastará con añadir el archivo como recurso y librarse de la necesidad de transmitir archivos adicionales para el funcionamiento del programa. Este método resulta bastante adecuado para la ubicación de programas en el Mercado mql5.com, dado que necesitamos ubicar solo el archivo ejecutable, pero no conviene en absoluto para ubicar el código fuente en CodeBase mql5.com, puesto que los *.wav de audio y los archivos gráficos en el formato *.bmp no se pueden colocar allí. Y sin ellos, el código fuente no está completo.
¿Qué podemos hacer en semejante situación? La conclusión es obvia: guardar todos los archivos necesarios en código fuente en archivos *.mqh de inclusión en forma de matrices binarias. Y después, al inicio del propio programa, crear a partir de los conjuntos de datos ya disponibles todos los archivos imprescindibles para el funcionamiento del programa en las carpetas necesarias. De esta forma, al iniciar solo el código del programa, este mismo creará todos los archivos necesarios para el trabajo, colocándolos en sus carpetas correspondientes, y posibilitando su correcto funcionamiento. Para el usuario del programa, este proceso transcurrirá de forma prácticamente imperceptible: apenas notará que el tiempo de inicio es ligeramente mayor. Este se invierte en la creación y el guardado de los archivos que aún no existen.

Para trabajar con los datos de los archivos, hemos creado dos clases:

  • una clase generadora de archivos a partir de los datos anteriormente preparados,
  • un clase para trabajar con la lista de archivos creados: la colección de objetos descriptores de archivos.
La clase generadora de archivos tendrá un conjunto de métodos estáticos para crear archivos a partir de los datos que les corresponden. Estos métodos estarán disponibles en cualquier parte de la biblioteca.
La clase para trabajar con los archivos ya creados tendrá entre sus componentes una lista con todos los archivos creados y los métodos de acceso a estos archivos según los datos guardados en la lista. En esencia, en la lista se guardarán las descripciones simples de los archivos creados (nombre y descripción del archivo), y ya según estos datos podremos obtener la ruta hasta el archivo físico para trabajar con el mismo.

Clase de generador de archivos

Comenzaremos con el archivo Datas.mqh. Vamos a añadir al mismo los mensajes necesarios, que se mostrarán al trabajar con las clases creadas.

Añadimos a la enumeración de los índices de los mensajes de texto las constantes que indican la ubicación de los nuevos mensajes en la matriz de los datos de texto de la 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:
   

Y añadimos a la matriz de mensajes de texto los índices de texto en ruso e inglés correspondientes a las constantes:

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


Conviene prestar atención a que la secuencia de ubicación de los textos en la matriz deberá corresponderse exactamente con la secuencia de declaración de las constantes de los índices en la enumeración.

Para trabajar con la clase generadora de archivos, deberemos crear una base de datos desde la que la clase tomará los datos sobre los archivos de audio e imagen. Dichos datos deberán ser matrices unsigned char. Para crearlas, deberemos disponer de archivos de audio (*.wav) y bitmap (*.bmp), que guardaremos en el código fuente de la biblioteca. A modp de ejemplo, ya hemos preparado varios datos de prueba. Los datos de sonido y bitmap se guardarán en archivos de inclusión por separado.

Vamos a crear en el directorio raíz de la biblioteca \MQL5\Include\DoEasy\ el archivo de inclusión DataSND.mqh, añadiendo de inmediato los nombres de las matrices de los archivos de audio que ubicaremos en las matrices (por cierto, podemos terminar de escribir los nombres de las matrices de datos más tarde, estos son solo necesarios para la búsqueda rápida del lugar donde se declara la matriz con los datos de este o aquel archivo en el listado, dado que las matrices pueden ser grandes, pero no mayores a 16 megabytes):

//+------------------------------------------------------------------+
//|                                                      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
*/
//+------------------------------------------------------------------+

Para insertar un archivo en el código fuente de un programa, podemos utilizar el punto del menú "Editar --> Insertar --> Archivo como matriz binaria":


Después de elegir dicho punto, se abrirá la ventana de selección del archivo, en la que deberemos encontrar el archivo anteriormente preparado para cargar sus datos en la matriz. La matriz se creará automáticamente, usando como base el nombre del archivo elegido (el ejemplo no está completo, dado que hay muchos datos binarios):

//+------------------------------------------------------------------+
//|                                                      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,

Dado que los datos de la matriz repiten completamente los datos del archivo, las matrices resultantes son bastante grandes. Es por eso que hemos añadido de antemano los nombres de las matrices creadas para saltar rápidamente al comienzo de cada una en la lista usando Ctrl+F.

Ahora solo queda añadir al listado de este archivo el número requerido de matrices de datos de audio. Ya hemos creado varios sonidos de prueba. Dado que el archivo resultante es grande, no tiene sentido mostrar su listado aquí. El lector podrá encontrarlo en los archivos de la biblioteca adjuntos a continuación.

Creamos exactamente de la misma forma un archivo con los datos bitmap llamado DataIMG.mqh. En él también hemos introducido dos matrices con la imagen de una bombilla LED de dos colores: una matriz contiene los datos con la imagen del LED verde, y la otra, los datos con la imagen del LED rojo:

//+------------------------------------------------------------------+
//|                                                      DataIMG.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"
//+------------------------------------------------------------------+
//| 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,


Y como ya sucedió en el ejemplo con los datos de audio, no vamos a mostrar el listado completo del archivo obtenido.

Para que los datos binarios de los archivos estén disponibles en la biblioteca, incluiremos los archivos con los datos en el archivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                                 |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Datas.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+

En el bloque de macrosustituciones del archivo Defines.mqh, añadimos una macrosustitución en la que se indiquen las carpetas de ubicación de los datos de recurso de la 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 de la subcarpeta de la biblioteca Resource\, se crearán de forma automática las carpetas Sounds y Images, en las que se crearán y guardarán los archivos de audio e imagen, respectivamente.

Dado que al crear los archivos a partir de matrices preparadas debemos especificar la extensión del archivo creado, para que el método de generación de archivos sepa qué archivo se ha creado exactamente y en qué carpeta debe ubicarse, necesitaremos la enumeración de los tipos de archivo cuyos datos se escriben en matrices binarias .
Al final del listado de Defines.mqh, añadimos la enumeración necesaria:

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

Puesto que todos los archivos de recurso se ubicarán en las dos carpetas Sounds y Images, que a su vez colocaremos en el directorio MQL5\Files\ del terminal, necesitaremos corregir el método PlaySound() de la clase CMessage. Abrimos el archivo \MQL5\Include\DoEasy\ Services\Message.mqh y
corregimos la ruta del archivo hasta el 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;
  }
//+------------------------------------------------------------------+

Para reproducir el archivo, especificamos la subcarpeta \Files\, ya que debemos almacenar todos los datos relativos a MQL5\ y esta carpeta, mientras que la ruta restante hasta el archivo se establece y transmite al método usando el parámetro file_name al crear el objeto de descripción del archivo.

Actualmente, esto es suficiente para crear las clases necesarias.

Ahora, vamos a crear en la carpeta \MQL5\Include\DoEasy\Services\ la nueva clase CFileGen en el archivo FileGen.mqh:

//+------------------------------------------------------------------+
//|                                                      FileGen.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"
#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[]);
  };
//+------------------------------------------------------------------+

En el archivo se ha incluido directamente la biblioteca de funciones de servicio DELib.mqh, dado que en esta ya se incluyen Defines.mqh y Message.mqh, imprescindibles para el funcionamiento de la clase:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property strict  // Es necesario para mql4
//+------------------------------------------------------------------+
//| Archivos de inclusión                                            |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
//+------------------------------------------------------------------+

Todas las variables de miembro de la clase y sus métodos tienen encabezados en el código, por lo que carece de sentido describir sus valores, todo está claro aquí. Echemos un vistazo a la implementación de los métodos.
Puesto que las variables de miembro de clase son estáticas, estas requieren una inicialización 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 crear un archivo a partir de una matriz de datos:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

El método usa la función estándar FileWriteArray(), que permite escribir en un archivo binario las matrices de cualquier dato excepto de tipo string.
Transmitimos al método el tipo de archivo escrito (audio o imagen), el nombre dle futuro archivo y la matriz con el conjunto de datos binarios del archivo creado.
Todas las acciones realizadas por el método se escriben en su listado, y resultan sencillas y comprensibles, por eso no vamos a detallarlas.

Método que retorna la bandera de presencia de un archivo en el directorio 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);
  }
//+------------------------------------------------------------------+

Transmitimos al método el tipo de archivo escrito y el nombre del archivo cuya presencia debemos comprobar. A continuación, establecemos el nombre del archivo, que consta de la ruta hasta el archivo, su nombre y su extensión. Después, con la ayuda de la función FileIsExist(), se retorna el resultado de la comprobación de la existencia de un archivo con ese nombre.

Método que establece el nombre del archivo, que consta de la ruta hasta el archivo, su nombre y su extensión:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Transmitimos al método el tipo de archivo escrito y el nombre del archivo a partir los cuales el método monta el nombre completo del archivo, que incluye: la carpeta de guardado de los archivos de la biblioteca, la subcarpeta de extensión del archivo creada por el método SetSubFolder(), el nombre transmitido (file_name) y la extensión del archivo, creada por el método Extension() según el tipo de archivo (audio o imagen). El resultado obtenido se registra en la variable de miembro de clase m_name.

Método para establecer el nombre de la subcarpeta dependiendo del tipo de archivo (audio o imagen):

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

Transmitimos al método el tipo de archivo, y dependiendo de este tipo, se registra en la variable de miembro de clase m_subfolder el nombre de la subcarpeta Images\ o Sounds\.

Método que retorna la extensión de un archivo según su 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;
  }
//+------------------------------------------------------------------+

Transmitimos al método el tipo de archivo. A continuación, de la representación de texto ( EnumToString()) de la constante de la enumeración ENUM_FILE_TYPE transmitida al método extraemos la expansión con la ayuda de la función StringSubstr() (para el archivo de audio "FILE_TYPE_WAV", se extrae la sublínea "WAV", para el archivo bitmap, "BMP"). Después, convertimos todos los símbolos de la sublínea extraída con la extensión del archivo en extensiones de línea con la ayuda de StringToLower(), añadiendo a la línea (antes de ella) el símbolo "punto" (.) y retornando el resultado obtenido desde el método.

Con esto, podemos dar por finalizada la creación de la clase generadora.

Ahora podemos almacenar datos binarios de cualquier archivo de audio y bitmap en el código fuente y crear automáticamente archivos completos a partir de estos datos durante el inicio del programa. Los archivos deben colocarse en las carpetas correspondientes, a las que accederá la biblioteca para obtener los archivos y usarlos para el propósito previsto.
Para obtenerlos de manera adecuada, necesitaremos describir de alguna manera los archivos existentes y disponer de acceso rápido y conveniente a ellos. Para lograrlo, crearemos la clase de colección de recursos del programa. Esta no será una colección que vamos a crear en la biblioteca (listas de punteros a los objetos de colección), sino una colección de objetos de descripción de los archivos.
Para cada archivo físico creado, generaremos un objeto que describa este archivo, especificando su nombre, ruta y descripción. Usaremos estos objetos descriptores para acceder a los archivos físicos. La descripción de un archivo en estos objetos puede ser cualquier descripción de texto que agreguemos a un objeto descriptor para cada archivo concreto.
Por ejemplo, para el sonido de un clic del ratón se puede usar "Clic del ratón 01", "Clic 01" o cualquier cosa que deseemos. Para obtener el descriptor del archivo necesario, simplemente introducimos la descripción en los parámetros de búsqueda. El método de búsqueda retornará el índice del objeto descriptor del archivo que podemos utilizar para obtener las propiedades del archivo físico.

Clase de colección de recursos de programa

Vamos a crear en la carpeta de la biblioteca \MQL5\Include\DoEasy\Collections\ la nueva clase CResourceCollection en el archivo ResourceCollection.mqh. Directamente en el nuevo listado de clases, escribimos otra clase más: la clase de objeto descriptor cuyas ejemplares serán añadidos para cada nuevo archivo y agregados a la colección de descriptores.

//+------------------------------------------------------------------+
//|                                           ResourceCollection.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"
#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);
  }
//+------------------------------------------------------------------+

Incluimos directamente en el listado los archivos necesarios: la clase de matriz dinámica de los punteros a los ejemplares de la clase CObject y sus herederos de la biblioteca estándar y la clase generadora de archivos de la biblioteca CFileGen.

Para que el objeto descriptor se pueda guardar en la lista CArrayObj, deberemos convertir este objeto en herederode la clase básica de la biblioteca estándar CObject.
Todas las variables y métodos de los miembros de la clase han sido descritos en los comentarios del código. No tiene sentido detenerse en un análisis detallado de su propósito.
Solo debemos matizar que el método virtual Compare() compara los campos de los dos objetos descriptores por defecto según su nombre. Si los campos comparados de los dos objetos son iguales, el método retornará cero. Si tenemos que comparar dos objetos según la descripción del archivo, deberemos establecer el modo mode igual a 1.
Entre tanto, el método IsEqual(), que retorna la bandera de igualdad de los objetos, compara los objetos solo según el nombre del archivo (ya que no es posible que existan en una misma carpeta dos archivos con nombres idénticos). El método retorna el resultado del funcionamiento del método Compare() con el modo mode por defecto (0), que se corresponde con la comparación según el nombre del archivo.

Vamos a implementar la clase de colección de los objetos descriptores de los archivos de recursos del 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);
  };
//+------------------------------------------------------------------+

El propósito de cada método también se describe en esta lista, por lo tanto, vamos a pasar a los métodos de clase.

Implementamos el constructor de la clase fuera del cuerpo de la clase:

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

Aquí: limpiamos la lista de objetos descriptores y asignamos a la lista la bandera de clasificación según el nombre del archivo (por defecto, 0).

Método para crear un archivo y añadir un objeto descriptor a la lista de colección:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Transmitimos al método el tipo del archivo creado (audio o imagen), el nombre del archivo, la descripción del archivo y el enlace a la matriz binaria de datos del archivo a partir del cual se creará el archivo.

Si no logramos crear el archivo con la ayuda del método Create() de la clase CFileGen, y el archivo realmente está ausente (podría existir ya, por lo que no se crearía nuevamente), retornamos false.
Si no había archivo, y ahora ha sido creado, retornamos desde el método el resultado del funcionamiento del método de creación del objeto descriptor y su adición a la lista de colección de los objetos descriptores de los archivos.

Método para crear un objeto descriptor y añadirlo a la lista de colección:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Transmitimos al método el nombre del archivo y su descripción.
Creamos un nuevo objeto descriptor del archivo, y si no hemos logrado crearlo, mostramos en el diario una entrada sobre ello y retornamos false.
A continuación, establecemos para el objeto descriptor nuevamente creado el nombre del archivo y la descripción del archivo y lo añadimos a la lista de colección. Si no hemos logrado añadir el objeto, este deberá ser eliminado para evitar fugas de memoria: eliminamos el objeto y retornamos false.
De lo contrario, retornamos true: el objeto ha sido correctamente creado y añadido a la colección.

Método para añadir un objeto descriptor a la lista de colección:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Transmitimos el objeto descriptor al método, establecemos para la lista de colección la bandera de clasificación según el nombre del archivo, y si ya hay un objeto con ese nombre en la lista, retornamos false.
De lo contrario, retornamos el resultado del funcionamiento del método de adición de un objeto a la lista.

Método que retorna el índice de un objeto descriptor a la lista de colección según la descripción del archivo:

//+----------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Transmitimos al método la descripción del archivo según la cual debemos encontrar el objeto descriptor. Creamos un ejemplar temporal del objeto descriptor y establecemos en su campo de descripción la descripción transmitida al método.
Establecemos para la lista de colección la bandera de clasificación según la descripción del archivo ( 1) y obtenemos el índice del objeto descriptor cuyo campo de descripción contiene el texto buscado.
Eliminamos necesariamente el objeto temporal y retornamos el índice obtenido (-1 en el caso de que no exista un objeto con esta descricpión en la lista de colección.

La clase de colección de los objetos descriptores de los archivos está lista.

Ahora, tenemos que añadir varios métodos al objeto básico de la biblioteca CEngine.

Abrimos el archivo \MQL5\Include\DoEasy\Engine.mqh e
incluimos en este el archivo de colección de los objetos descriptores:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                            |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+

Creamos el nuevo objeto de colección de los recursos del programa (colección de descriptores de archivo):

//+------------------------------------------------------------------+
//| 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

y añadimos tres nuevos métodos para trabajar con la colección de recursos del 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();
  };
//+------------------------------------------------------------------+

Los métodos tienen los mismos nombres que los métodos en la clase de colección de objetos descriptores y llaman a los métodos homónimos de esta clase.

Para poner a prueba las clases creadas, crearemos todos los archivos según los datos disponibles en los archivos de inclusión DataSND.mqh y DataIMG.mqh en matrices binarias. En el diario "Expertos", mostraremos los resultados de la creación de los archivos a partir de las matrices binarias, mostrando también el contenido de la lista de colección resultante de objetos descriptores de archivos. Además, reproduciremos uno de los sonidos y visualizaremos una imagen que consta de los dos archivos de imagen creados que muestran los LED rojo y verde en la esquina inferior derecha de la pantalla.

Simulación del acceso a los archivos creados automáticamente

Vamos a tomar el asesor TestDoEasyPart19.mq5 del artículo anterior y a guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\ Part20\ con el nuevo nombre TestDoEasyPart20.mq5.

Dado que los archivos deben crearse durante el primer inicio del programa, vamos a organizar en el manejador OnInit() el trabajo con las clases para crear los recursos del programa. Precisamente en él simularemos el resultado obtenido.

Añadimos al final del manejador OnInit() este bloque 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);
  }
//+------------------------------------------------------------------+

Aquí hemos organizado la creación de archivos a partir de los datos que se encuentran en las matrices binarias en los códigos fuente de la biblioteca. Después de crear cada archivo que sigue, comprobamos su existencia e imprimimos el resultado en el diario. Después de crear todos los archivos y sus descriptores, comprobamos la lista completa con todos los descriptores de los archivos añadidos a la colección en la etapa de creación de archivos.

En el listado se describen todas las acciones, que el lector podrá estudiar por sí mismo.

Después de compilar el asesor, este mostrará en el diario las entradas con los resultados de la creación de los archivos, reproducirá el sonido de una moneda al caer y mostrará en la esquina inferior derecha de la pantalla la figura de un LED que consta de dos imágenes. El usuario podrá alternar las imágenes clicando con el ratón sobre la imagen del LED (en esencia, se trata de un botón que tiene dos estados: on/off)



Como podemos ver, todo funciona como debe: en el diario se muestran los mensajes sobre la creación exitosa de los archivos, la figura del LED alterna el color al clicar sobre la imagen, y si abrimos el catálogo de datos del terminal (en el terminal, punto del menú archivo --> Abrir catálogo de datos) y entramos en la carpeta MQL5\Files\DoEasy\Resource\, encontraremos dos subcarpetas, Images y Sounds, en las que se encuentran todos los archivos recién creados.

¿Qué es lo próximo?

En el siguiente artículo, comenzaremos un nuevo apartado de la biblioteca: las clases comerciales y todo lo relacionado con ellas.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Parte 1: Concepto y organización de datos
Parte 2: Colecciones de las órdenes y transacciones históricas
Parte 3: Colección de órdenes y posiciones de mercado, organización de la búsqueda
Parte 4: Eventos comerciales. Concepto
Parte 5: Clases y colección de eventos comerciales. Envío de eventos al programa.
Parte 6. Eventos en la cuenta con compensación
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para el registro de los eventos de modificación de órdenes y posiciones
Parte 8. Eventos de modificación de órdenes y posiciones
Parte 9. Compatibilidad con MQL4 - Preparando los datos
Parte 10. Compatibilidad con MQL4 - Eventos de apertura de posición y activación de órdenes pendientes
Parte 11. Compatibilidad con MQL4 - Eventos de cierre de posiciones
Parte 12. Implementando la clase de objeto "cuenta" y la colección de objetos de cuenta
Parte 13. Eventos del objeto "cuenta"
Parte 14. El objeto "Símbolo"
Parte 15. Colección de objetos de símbolo
Parte 16. Eventos de la colección de símbolos
Parte 17. Interactividad de los objetos de la biblioteca
Parte 18. Interactividad del objeto de cuenta y cualquier otro objeto de la biblioteca
Parte 19. Clase de mensajes de la biblioteca


Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/7195

Archivos adjuntos |
MQL5.zip (3556.3 KB)
MQL4.zip (3553.08 KB)
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XIX): Clase de mensajes de la biblioteca Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XIX): Clase de mensajes de la biblioteca

En el artículo, analizaremos la muestra de mensajes de texto. Ahora que tenemos un número suficiente de mensajes de texto distintos, merece la pena que pensemos en organizar un método para guardarlos, mostrarlos y adaptarlos a otros idiomas desde el ruso. Asimismo, también deberíamos pensar en un modo de añadir nuevos idiomas a la biblioteca y alternar rápidamente entre ellos.

ZUP - zigzag universal con patrones Pesavento: Interfaz gráfica. Adiciones y mejoras. Tridente Andrews en ZUP ZUP - zigzag universal con patrones Pesavento: Interfaz gráfica. Adiciones y mejoras. Tridente Andrews en ZUP

En la versión 153, la edición de casi todos los parámetros del ZUP se puede realizar a través de la interfaz gráfica. En el artículo se ofrece una descripción de los últimos cambios en la interfaz gráfica del ZUP. También se describen los principales elementos del tridente de Andrews en ZUP para usar esta herramienta al analizar la situación de mercado.

Investigando las características estacionales de las series temporales financieras con la ayuda de diagramas Boxplot Investigando las características estacionales de las series temporales financieras con la ayuda de diagramas Boxplot

Investigando las características estacionales de las series temporales financieras con la ayuda de diagramas Boxplot. Cada diagrama de caja individual ofrece una buena imagen sobre la distribución de los valores en el conjunto de datos. A pesar de sus similitudes visuales, no debemos confundir el diagrama de caja con el gráfico de velas japonesas.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXI): Clases comerciales - El objeto comercial multiplataforma básico Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXI): Clases comerciales - El objeto comercial multiplataforma básico

En este artículo vamos a comenzar un nuevo apartado de la biblioteca, las clases comerciales, y también vamos a analizar la creación de un objeto comercial básico único para las plataformas MetaTrader 5 y MetaTrader 4. Dicho objeto comercial, al enviar una solicitud al servidor, presupondrá que los parámetros de la solicitud comercial que se le han transmitido han sido verificados y son correctos.