
轻松快捷开发 MetaTrader 程序的函数库 (第二十部分):创建和存储程序资源
内容
概念
在开发应用程序时,我们经常需要音频和图像。 MQL 语言提供了若干种运用此类数据的方法,所有这些方法都需要从终端文件沙箱里载入文件。 如果最终结果是编译后的文件,您只需将文件作为资源连接,且不必再为程序操作传递其他文件。 此方法非常适合将程序发送到 mql5.com 市场,因为那里只接受可执行文件。 然而,此方法不适用于将源代码放置到 mql5.com 代码库,因为音频 *.wav 文件和图形 *.bmp 文件不能放置于此。 没有它们,源代码会缺少一些重要功能。
解决方案非常明显:所有要求文件的数据都应以二进制数组形式存储在 *.mqh 包含文件的源代码中。 启动应用程序时,所有要求的文件都会在现有数据集合中的相应目录中生成。 因此,启动此类代码时,应用程序将生成所有必需的文件,将它们放置在相应文件夹中,即可正常工作。 所有这些对于用户来说都是无缝发生的,唯一的例外是首次启动时由于需要生成并写入尚未存在的文件,所以时间略有增加。
我们将创建两个用于处理文件数据的类:
- 文件生成器类,从准备好的数据里抽取,
- 用于处理已创建文件列表的类 — 描述文件对象的集合。
用于处理已生成文件的类将包含所有已创建文件的列表,以及通过列表中存储的数据访问这些文件的方法。 实际上,该列表还包含已创建文件的简单描述(文件名和描述)。 这些数据令我们始终可以访问物理文件,并进行处理。
文件生成器类
我们从 Datas.mqh 文件开始。 我们向其中添加必要的消息。 操作所创建类的过程中可以显示它们。
在文本消息索引枚举里添加常量,指定新消息在函数库文本数据数组中的位置:
//+------------------------------------------------------------------+ //| 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:
按照相应索引常数将英语和俄语文本写入文本消息数组:
//+------------------------------------------------------------------+ string messages_library[][TOTAL_LANG]= { {"Начало списка параметров","Beginning of the event parameter list"}, {"Конец списка параметров","End of the parameter list"}, {"Свойство не поддерживается","Property not supported"}, {"Свойство не поддерживается в MQL4","Property not supported in MQL4"}, {"Свойство не поддерживается у позиции","Property not supported for position"}, {"Свойство не поддерживается у отложенного ордера","Property not supported for pending order"}, {"Свойство не поддерживается у маркет-ордера","Property not supported for market order"}, {"Свойство не поддерживается у исторического маркет-ордера","Property not supported for historical market order"}, {"Значение не задано","Value not set"}, {"Отсутствует","Not set"}, {"Ошибка ","Error "}, {"Ошибка. Такого символа нет на сервере","Error. No such symbol on server"}, {"Не удалось поместить в обзор рынка. Ошибка: ","Failed to put in 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 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 already in the list"}, {"Такой файл уже создан и добавлен в список: ","This file has already been created and added to list: "}, {"Ошибка. Не удалось создать объект-указатель на файл ресурса","Error. Failed to create resource file link object"}, {"Ошибка. Уже создан счётчик с идентификатором ","Error. Already created counter with id "}, {"Не удалось создать счётчик таймера ","Failed to create timer counter "}, {"Ошибка создания временного списка","Error creating temporary list"}, {"Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of market collection"}, {"Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of history collection"}, {"Не удалось добавить ордер в список","Could not add order to list"}, {"Не удалось добавить сделку в список","Could not add deal to list"}, {"Не удалось добавить контрольный ордер ","Failed to add control order "}, {"Не удалось добавить контрольую позицию ","Failed to add control position "}, {"Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to 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. \"index\" value should be between 0 - 3"}, {"Не удалось преобразовать строку в нижний регистр, ошибка ","Failed to convert string to lowercase, error "}, {"Ошибка. Строка предопределённых символов пустая, будет использоваться ","Error. String of predefined symbols is empty, symbol will be used: "}, {"Не удалось подготовить массив используемых символов. Ошибка ","Failed to create array of used characters. Error "}, {"Неправильный тип ордера: ","Invalid order type: "},
请注意,数组中的文本序列应与枚举中声明索引常量的序列完全匹配。
为了令文件生成器类正常工作,我们需要创建一个数据库,该类将从中抽取音频和图像文件数据。 此类数据应采用 unsigned char(无符号字节) 数组的形式。 为了创建它们,我们需要将音频(*.wav)和位图(*.bmp)文件存储在函数库源中。 我已经准备了一些测试数据作为示例。 音频和位图文件应分别存储在各自的包含文件中。
在函数库的 \MQL5\Include\DoEasy\ 根目录下,创建 DataSND.mqh 包含文件并加入将要转化到数组里的音频文件名 (数据数组名称可以稍后再加,因为数组不能超过 16 MB,且它们仅在需要快速搜索含有指定文件数据的数组声明位置时才会用到):
//+------------------------------------------------------------------+ //| 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 */ //+------------------------------------------------------------------+
若要将文件插入程序源代码,请单击编辑-> 插入-> 文件作为二进制数组:
这将打开文件选择窗口,您可在其中找到先前准备好的文件,并将其数据加载至数组。 该数组是根据所选文件的名称自动生成的(由于二进制数据很多,因此示例并不完整):
//+------------------------------------------------------------------+ //| 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,
由于数组数据完全与文件一重复,因此生成的数组很大。 这就是为什么我预先输入所创建的数组名称,然后使用 Ctrl+F 迅速跳至清单中每个数组开头的原因。
现在仅剩任务就是将所需数量的音频数据数组添加到文件清单中。 我已经创建了几个测试音频。 由于文件很大,因此在这里显示其清单毫无意义。 您可以在下面随附的函数库文件中找到它。
创建位图数据 DataDataG.mqh 文件也是用完全相同的方式。 该文件已经创建了两个数组,描绘了一个双色 LED 灯泡:一个绿色,另一个红色 LED 的图像数据:
//+------------------------------------------------------------------+ //| 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,
就像在音频数据的示例中一样,我在这里没有提供结果文件的完整清单。
将含有数据的文件包含到 Defines.mqh 文件中,以便文件的二进制数据在函数库中可用:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "DataSND.mqh" #include "DataIMG.mqh" #include "Datas.mqh" #ifdef __MQL4__ #include "ToMQL4.mqh" #endif //+------------------------------------------------------------------+
在 Defines.mqh 文件的宏替换区域块中,添加宏替换,指定函数库资源数据所处的文件夹:
//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+
在 Resource\ 函数库子文件夹中,自动生成 Sounds 和 Images 文件夹,分别用于创建和存储音频和图像文件。
从准备好的数组里抽取数据创建文件时,我们需要指定其扩展名。 为了让文件生成方法确切知道创建何种文件以及应将其放置在哪个文件夹中,我们需要文件类型枚举,指示其数据应写入相应的二进制数组。
在 Defines.mqh 清单的末尾添加必要的枚举:
//+------------------------------------------------------------------+ //| Data for working with program resource data | //+------------------------------------------------------------------+ enum ENUM_FILE_TYPE { FILE_TYPE_WAV, // wav file FILE_TYPE_BMP, // bmp file }; //+------------------------------------------------------------------+
由于所有函数库资源文件都位于终端的 MQL5\Files\ 下的 Sounds 和 Images 文件夹中,因此我们需要调整 CMessage 类的 PlaySound() 方法。 打开 \MQL5\Include\DoEasy\ Services\Message.mqh,并在 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; } //+------------------------------------------------------------------+
若要播放文件,我们指定 \Files\子文件夹,因为所有我们要存储的数据都相对于 MQL5\ 文件夹,而其余文件路径已设置,并在创建文件描述对象时通过 file_name 参数传递给该方法。
当前,这些足以创建必要的类。
在 \MQL5\Include\DoEasy\Services\,在 FileGen.mqh 文件里创建 CFileGen 新类:
//+------------------------------------------------------------------+ //| 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[]); }; //+------------------------------------------------------------------+
文件立即包含服务函数的 DELib.mqh 函数库,因其内已包含类操作所需的 Defines.mqh 和 Message.mqh:
//+------------------------------------------------------------------+ //| DELib.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "Message.mqh" #include "TimerCounter.mqh" //+------------------------------------------------------------------+
所有类成员变量及其方法在代码中都有注释标题,故于此没有必要描述它们的目的 —了然于目。 我们来研究方法的实现。
鉴于类成员变量是静态的,因此需要显式初始化:
//+------------------------------------------------------------------+ //| 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; //+------------------------------------------------------------------+
从数组抽取数据并创建文件的方法:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法利用 FileWriteArray() 文件标准函数,除字符串以外该函数允许将任何数据数组写入二进制文件。
该方法接收输出文件的类型(音频或图像),未来文件的名称,以及创建文件所需的一组二进制数据数组。
该方法执行的所有动作都编写在其清单中 — 它们简单易懂。 此处无需赘述。
该方法返回资源目录中文件是否存在的标志:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
该方法接收输出文件的类型和需要查验其存在与否的文件名称。 然后设置文件路径、名称和扩展名组成的文件名,以及 FileIsExist() 函数返回的检查同名文件是否存在的结果。
方法设置文件路径、文件名和扩展名:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
该方法接收输出文件类型和一个文件名,其中,然后拼接为包括存储函数库文件夹的完整文件名,子文件夹由 SetSubFolder() 方法创建,文件名通过( file_name )参数传递给方法,而文件扩展名由 Extension() 方法遵照文件类型(音频或 图片)创建。 将获得的结果写入 m_name 类成员变量。
根据文件类型(音频或图像)设置子文件夹名称的方法:
//+------------------------------------------------------------------+ //| 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\\" : ""); } //+------------------------------------------------------------------+
该方法接收文件类型。 基于该类别,m_subfolder 类成员变量会收到 Images\ 或 Sounds\ 子文件夹名称。
该方法按类别返回文件扩展名:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法接收文件类型。 接下来,从传递给方法的 ENUM_FILE_TYPE 枚举常量的文本表示形式(EnumToString())中,利用 StringSubstr() 函数(对于音频文件,从 “FILE_TYPE_WAV” 字符串中提取 “WAV” 子字符串,对于位图文件,则为 “BMP”)提取扩展名。 然后,利用 StringToLower() 将提取的文件扩展名中的所有字母转换为小写,将“句号”字符(.)添加到 字符串(在它前面),并返回自方法得到的结果。
文件生成器类的创建既已完毕。
现在,我们可以在源代码中存储任何音频和位图文件的二进制数据,并在程序启动期间自动依据这些数据创建完整的文件。 这些文件将位于函数库将要访问的相应文件夹中,以便读取文件并将其用于预期目的。
为了令它们更便捷,我们需要以某种方式描述现存文件,并能够快速便捷地访问它们。 为此,我们将创建程序资源集合类。 我们并非要在函数库中创建集合(指向集合对象的指针列表),而是文件描述对象的集合。
对于每个所要创建的物理文件,我们将生成一个描述该文件的对象,并在其内指定名称、路径和描述。 我们将使用这些描述符对象访问物理文件。 文件描述的构成,包括针对每个特定文件的任何我们想要加入描述符对象里的文本描述。
例如,单击鼠标的声音可能被描述为 "Mouse click 01","Click 01" 或您喜欢的任何字眼。 为了获得必要文件的描述符,我们只需在搜索参数中输入描述即可。 搜索方法返回文件描述符对象索引,我们再依据该索引来获取物理文件的属性。
程序资源集合类
在 \MQL5\Include\DoEasy\Collections\ 函数库文件夹下,在 ResourceCollection.mqh 文件里创建新类 CResourceCollection。 在新类的清单中,编写另一个类 — 描述符对象类,针对每个新文件创建实例,并将其添加到描述符集合当中:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
立即将必要的文件包括在清单中 — 指向 CObject 类实例的动态数组的类,和其标准库的后代,以及生成 CFileGen 函数库文件的类。
为了能够将描述符对象存储在 CArrayObj 列表中,我们需要令该对象作为 CObject 标准库基类的后代。
所有类成员变量和方法均在代码注释中描述。 详细分析它们的目的毫无必要。
请注意,默认情况下,Compare() 虚方法按文件名比较两个描述符对象的字段。 如果两个对象的比较字段相等,则该方法返回零。 如果对象要按文件描述比较,则应将模式(mode)设置为 1。
IsEqual() 方法仅按文件名比较对象并返回对象相等标志(因为在一个文件夹中不能有两个名称相等的文件)。 默认情况下,该方法以 “mode”(0)返回 Compare() 方法的操作结果,该结果对应于按文件名进行比较。
我们来实现描述程序资源文件的描述符对象集合类:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+
在此,每个方法的目的也在清单中予以设置,所以我们转至类方法。
在类的实体之外编写类构造函数:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CResourceCollection::CResourceCollection() { this.m_list_dscr_obj.Clear(); this.m_list_dscr_obj.Sort(); } //+------------------------------------------------------------------+
在此,清除描述符对象的列表,并为列表设置了按文件名排序的标志(默认为 0)。
创建文件并将描述符对象添加到集合列表的方法:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
该方法接收所创建文件的类型(音频或图像),文件名,文件描述以及指向构成文件的二进制文件数据数组。
如果利用 CFileGen 类的 Create() 方法创建文件失败,且文件实际上不存在(或是该文件可能已经存在,因此无法重新创建),则返回 false。
如果文件从未存在,且刚被创建,则返回方法操作结果生成的描述符对象,并将其添加到来自方法中的文件描述符对象集合列表之中。
创建文件描述符对象,并将其添加到集合列表的方法:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法接收文件名和其描述。
创建新的文件描述符对象。 如果创建对象失败,在日志显示相应的消息,并返回 false。
接着,在新创建的描述符对象里设置文件名,以及文件描述,并将其添加到集合列表。 如果添加对象失败,则应销毁该对象以避免内存泄漏 — 删除对象,并返回 false。
否则,返回 true — 该对象已成功创建,并添加到集合中。
将新描述符对象添加到集合列表的方法:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
方法接收描述符对象,和按文件名排序设置标志的集合列表。 如果列表中已经存在同名的对象,则返回 false。
否则,返回将对象添加到列表方法的操作结果。
该方法按文件描述返回集合列表中的描述符对象索引:
//+----------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法接收文件描述,据其查找描述符对象。 创建临时描述符对象实例,并在其描述字段中设置传递给方法的文件描述。
设置按文件描述排序集合列表标志(1),并获取描述符对象索引,其说明字段包含必要的文本。
确保删除临时对象,并返回所得到的索引(如果在集合列表中没有带有此描述的对象,则为 -1)。
文件描述符对象集合类已准备就绪。
现在,我们需要在 CEngine 函数库基准对象中添加一些方法。
打开文件 \MQL5\Include\DoEasy\ Engine.mqh,然后将描述符对象集合文件包含到其内:
//+------------------------------------------------------------------+ //| Engine.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/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" //+------------------------------------------------------------------+
为应用程序资源创建新的集合对象(文件描述符的集合):
//+------------------------------------------------------------------+ //| 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
并添加三种用于程序资源集合的新方法:
//--- 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(); }; //+------------------------------------------------------------------+
这些方法与描述符对象的集合类中的方法具有相同的名称,并自该类中调用同名方法。
为了测试所创建的类,根据 DataSND.mqh 和 DataIMG.mqh 包含文件中二进制数组里的有效数据创建所有的文件。 在“智能系统”日志中,显示自二进制数组创建文件的结果,并显示文件描述符对象集合列表结果的内容。 另外,我们播放音频之一,并在屏幕的右下角显示所创建的由两个图像文件构成的图像,这些图像文件描绘的是红色和绿色的 LED。
自动创建文件的访问测试
我们利用来自上一篇文章中的 TestDoEasyPart19.mq5 EA ,并将其保存到 \MQL5\Experts\TestDoEasy\Part20\ 之下,命名为 TestDoEasyPart20.mq5。
由于要在程序首次启动期间创建文件,因此应在 OnInit() 中调用创建程序资源的类。 获得的结果也应该在那里进行测试。
将以下代码区块添加到 OnInit() 响应程序的末尾:
//--- 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); } //+------------------------------------------------------------------+
在此,我们根据函数库源代码里二进制数组中的数据来创建文件。 依次创建每个文件后,检查其存在,并将结果显示在日志中。 创建所有文件及其描述符之后,创建文件并将所有文件描述符的完整列表加入集合。
清单列明了所有动作。 您可以自行分析它们。
编译 EA 之后,它将在日志中显示文件创建结果,播放硬币坠落的声音,并在屏幕的右下角显示由两个图像构成的 LED 图片。 您可以通过单击 LED 来切换图像。 实际上,这是一个拥有两种状态(打开/关闭)的按钮。
如我们所见,一切都按预期进行。 有关成功生成文件的消息出现在日志中,单击 LED 时会更改其颜色,如果我们打开终端数据文件夹(文件->打开数据文件夹)并进入 MQL5\Files\DoEasy\Resource\,我们 可以看到所有新创建文件所在的 Images 和 Sounds 子文件夹。
下一步是什么?
从下一篇文章开始,我们将开创新的函数库领域 — 交易类以及与它们相关的所有内容。
文后附有当前版本含糊库的所有文件,以及测试 EA 文件,供您测试和下载。
请在评论中留下您的问题、意见和建议。
系列中的前几篇文章:
第一部分 概念,数据管理
第二部分 历史订单和成交集合
第三部分 在场订单和持仓集合,安排搜索
第四部分 交易事件, 概念
第五部分 交易事件类和集合。 将事件发送至程序
第六部分 净持帐户事件
第七部分 StopLimit 挂单激活事件,为订单和持仓修改事件准备功能
第八部分 订单和持仓修改事件
第九部分 与 MQL4 的兼容性 - 准备数据
第十部分 与 MQL4 的兼容性 - 开仓和激活挂单事件
第十一部分 与 MQL4 的兼容性 - 平仓事件
第十二部分 帐户对象类和帐户对象集合
第十三部分 账户对象事件
第十四部分 品种对象
第十五部份 品种对象集合
第十六部分 品种集合事件
第十七部分 函数库对象之间的交互
第十八部分 帐户与任意其他函数库对象的交互
第十九部分 函数库消息类
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/7195
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




1 个字节编码为 5 个字符("0xNN")。
Base64 用于将二进制数据密集打包成文本。在我的测试中,每个字节有 1.36 个字符。
下面是一个例子
len1=50, len2=68, result=VGhlIHF1aWNrIGJyb3duICBmb3ggIGp1bXBzICBvdmVyICB0aGUgIGxhenkgIGRvZwA=
您有 1 个字节,编码为 5 个字符("0xNNN,")。
Base64 用于将二进制数据密集打包成文本。在我的测试中,每个字节有 1.36 个字符。
下面是一个例子
len1=50, len2=68, result=VGhlIHF1aWNrIGJyb3duICBmb3ggIGp1bXBzICBvdmVyICB0aGUgIGxhenkgIGRvZwA=
我们说的不是这个...
啊,你是说你有用于编译的数据,并且在 ex5 中将以 1:1 的比例占用。是的,这里不需要打包。
啊,您是说您有用于编译的数据,在 ex5 中将占用 1:1。是的,没必要在这里打包。