
DoEasy 函数库中的图形(第九十七部分):独立处理窗体对象移动
内容
概述
在开发扩展图形对象时,我面临着必须重新回到窗体对象 —画布上的图形对象应按照扩展图形对象来实现,并作为管理图形对象定位点的控制连接。 在上一篇文章中,我启动了为窗体对象开发鼠标事件。 在此,我将完成关于处理窗体运动的工作。 我们应该能够移动图表上的任何窗体。 要被择的窗体应该是用鼠标光标拖动的窗体,所有图表工具都应该在相应的时候根据对象正确启用和禁用。
我们安排鼠标事件的跟踪,从而能够利用新开发的处理程序来实现窗体对象与鼠标的所有其余交互。 除了实现鼠标拖动窗体和为其它交互事件准备“凭证”之外,我还将为交易服务器的返回代码、和执行错误代码添加一些文本。 除此之外,我将为交成交对象添加新的属性 — 成交属性中的止损和止盈价位,现在有些时间了。
改进库类
在 \MQL5\Include\DoEasy\Data.mqh 里,添加新的消息索引:
MSG_LIB_PROP_BID, // Bid price MSG_LIB_PROP_ASK, // Ask price MSG_LIB_PROP_LAST, // Last deal price MSG_LIB_PROP_PRICE_SL, // StopLoss price MSG_LIB_PROP_PRICE_TP, // TakeProfit price MSG_LIB_PROP_DEAL_FEE, // Deal fee MSG_LIB_PROP_PROFIT, // Profit MSG_LIB_PROP_SYMBOL, // Symbol MSG_LIB_PROP_BALANCE, // Balance operation MSG_LIB_PROP_CREDIT, // Credit operation MSG_LIB_PROP_CLOSE_BY_SL, // Closing by StopLoss MSG_LIB_PROP_CLOSE_BY_TP, // Closing by TakeProfit MSG_LIB_PROP_ACCOUNT, // Account //--- COrder
以及与新添加的索引对应的文本消息:
{"Цена Bid","Bid price"}, {"Цена Ask","Ask price"}, {"Цена Last","Last price"}, {"Цена StopLoss","StopLoss price"}, {"Цена TakeProfit","TakeProfit price"}, {"Оплата за проведение сделки","Fee for making a deal"}, {"Прибыль","Profit"}, {"Символ","Symbol"}, {"Балансовая операция","Balance operation"}, {"Кредитная операция","Credit operation"}, {"Закрытие по StopLoss","Close by StopLoss"}, {"Закрытие по TakeProfit","Close by TakeProfit"}, {"Счёт","Account"}, //--- COrder
在同一文件中,补充错误消息数组:
//+---------------------------------------------------------------------+ //| Array of messages for trade server return codes (10004 - 10045) | //| (1) in user's country language | //| (2) in the international language | //+---------------------------------------------------------------------+ string messages_ts_ret_code[][TOTAL_LANG]= { {"Реквота","Requote"}, // 10004 {"Неизвестный код возврата торгового сервера","Unknown trading server return code"}, // 10005 {"Запрос отклонен","Request rejected"}, // 10006 {"Запрос отменен трейдером","Request canceled by trader"}, // 10007 {"Ордер размещен","Order placed"}, // 10008 {"Заявка выполнена","Request completed"}, // 10009 {"Заявка выполнена частично","Only part of request completed"}, // 10010 {"Ошибка обработки запроса","Request processing error"}, // 10011 {"Запрос отменен по истечению времени","Request canceled by timeout"}, // 10012 {"Неправильный запрос","Invalid request"}, // 10013 {"Неправильный объем в запросе","Invalid volume in request"}, // 10014 {"Неправильная цена в запросе","Invalid price in request"}, // 10015 {"Неправильные стопы в запросе","Invalid stops in request"}, // 10016 {"Торговля запрещена","Trading disabled"}, // 10017 {"Рынок закрыт","Market closed"}, // 10018 {"Нет достаточных денежных средств для выполнения запроса","Not enough money to complete request"}, // 10019 {"Цены изменились","Prices changed"}, // 10020 {"Отсутствуют котировки для обработки запроса","No quotes to process request"}, // 10021 {"Неверная дата истечения ордера в запросе","Invalid order expiration date in request"}, // 10022 {"Состояние ордера изменилось","Order state changed"}, // 10023 {"Слишком частые запросы","Too frequent requests"}, // 10024 {"В запросе нет изменений","No changes in request"}, // 10025 {"Автотрейдинг запрещен сервером","Autotrading disabled by server"}, // 10026 {"Автотрейдинг запрещен клиентским терминалом","Autotrading disabled by client terminal"}, // 10027 {"Запрос заблокирован для обработки","Request locked for processing"}, // 10028 {"Ордер или позиция заморожены","Order or position frozen"}, // 10029 {"Указан неподдерживаемый тип исполнения ордера по остатку","Invalid order filling type"}, // 10030 {"Нет соединения с торговым сервером","No connection with trade server"}, // 10031 {"Операция разрешена только для реальных счетов","Operation allowed only for live accounts"}, // 10032 {"Достигнут лимит на количество отложенных ордеров","Number of pending orders reached limit"}, // 10033 {"Достигнут лимит на объем ордеров и позиций для данного символа","Volume of orders and positions for symbol reached limit"}, // 10034 {"Неверный или запрещённый тип ордера","Incorrect or prohibited order type"}, // 10035 {"Позиция с указанным идентификатором уже закрыта","Position with specified identifier already closed"}, // 10036 {"Неизвестный код возврата торгового сервера","Unknown trading server return code"}, // 10037 {"Закрываемый объем превышает текущий объем позиции","Close volume exceeds the current position volume"}, // 10038 {"Для указанной позиции уже есть ордер на закрытие","Close order already exists for specified position"}, // 10039 {"Достигнут лимит на количество открытых позиций","Number of positions reached limit"}, // 10040 { "Запрос на активацию отложенного ордера отклонен, а сам ордер отменен", // 10041 "The pending order activation request is rejected, the order is canceled" }, { "Запрос отклонен, так как на символе установлено правило \"Разрешены только длинные позиции\"", // 10042 "The request is rejected, because the \"Only long positions are allowed\" rule is set for the symbol" }, { "Запрос отклонен, так как на символе установлено правило \"Разрешены только короткие позиции\"", // 10043 "The request is rejected, because the \"Only short positions are allowed\" rule is set for the symbol" }, { "Запрос отклонен, так как на символе установлено правило \"Разрешено только закрывать существующие позиции\"", // 10044 "The request is rejected, because the \"Only position closing is allowed\" rule is set for the symbol" }, { "Запрос отклонен, так как для торгового счета установлено правило \"Разрешено закрывать существующие позиции только по правилу FIFO\"", // 10045 "The request is rejected, because \"Position closing is allowed only by FIFO rule\" flag is set for the trading account" }, { "Запрос отклонен, так как для торгового счета установлено правило \"Запрещено открывать встречные позиции по одному символу\"", // 10046 "The request is rejected, because the \"Opposite positions on a single symbol are disabled\" rule is set for the trading account" }, }; //+------------------------------------------------------------------+
并添加新的数组 — 其中包含以前在函数库中不存在,但已添加到 MQL5 的新执行错误消息:
//+------------------------------------------------------------------+ //| Array of execution time error messages (5100 - 5114) | //| (Working with OpenCL) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_opencl[][TOTAL_LANG]= { {"Функции OpenCL на данном компьютере не поддерживаются","OpenCL functions not supported on this computer"}, // 5100 {"Внутренняя ошибка при выполнении OpenCL","Internal error occurred when running OpenCL"}, // 5101 {"Неправильный хэндл OpenCL","Invalid OpenCL handle"}, // 5102 {"Ошибка при создании контекста OpenCL","Error creating the OpenCL context"}, // 5103 {"Ошибка создания очереди выполнения в OpenCL","Failed to create run queue in OpenCL"}, // 5104 {"Ошибка при компиляции программы OpenCL","Error occurred when compiling OpenCL program"}, // 5105 {"Слишком длинное имя точки входа (кернел OpenCL)","Too long kernel name (OpenCL kernel)"}, // 5106 {"Ошибка создания кернел - точки входа OpenCL","Error creating OpenCL kernel"}, // 5107 { "Ошибка при установке параметров для кернел OpenCL (точки входа в программу OpenCL)", // 5108 "Error occurred when setting parameters for the OpenCL kernel" }, {"Ошибка выполнения программы OpenCL","OpenCL program runtime error"}, // 5109 {"Неверный размер буфера OpenCL","Invalid size of OpenCL buffer"}, // 5110 {"Неверное смещение в буфере OpenCL","Invalid offset in OpenCL buffer"}, // 5111 {"Ошибка создания буфера OpenCL","Failed to create OpenCL buffer"}, // 5112 {"Превышено максимальное число OpenCL объектов","Too many OpenCL objects"}, // 5113 {"Ошибка выбора OpenCL устройства","OpenCL device selection error"}, // 5114 }; //+------------------------------------------------------------------+ //| Array of execution time error messages (5120 - 5130) | //| (Working with databases) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_database[][TOTAL_LANG]= { {"Внутренняя ошибка базы данных","Internal database error"}, // 5120 {"Невалидный хендл базы данных","Invalid database handle"}, // 5121 {"Превышено максимально допустимое количество объектов Database","Exceeded the maximum acceptable number of Database objects"}, // 5122 {"Ошибка подключения к базе данных","Database connection error"}, // 5123 {"Ошибка выполнения запроса","Request execution error"}, // 5124 {"Ошибка создания запроса","Request generation error"}, // 5125 {"Данных для чтения больше нет","No more data to read"}, // 5126 {"Ошибка перехода к следующей записи запроса","Failed to move to the next request entry"}, // 5127 {"Данные для чтения результатов запроса еще не готовы","Data for reading request results are not ready yet"}, // 5128 {"Ошибка автоподстановки параметров в SQL-запрос","Failed to auto substitute parameters to an SQL request"}, // 5129 {"Запрос базы данных не только для чтения","Database query not read only"}, // 5130 }; //+------------------------------------------------------------------+ //| Array of execution time error messages (5200 - 5203) | //| (Working with WebRequest()) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_webrequest[][TOTAL_LANG]= { {"URL не прошел проверку","Invalid URL"}, // 5200 {"Не удалось подключиться к указанному URL","Failed to connect to specified URL"}, // 5201 {"Превышен таймаут получения данных","Timeout exceeded"}, // 5202 {"Ошибка в результате выполнения HTTP запроса","HTTP request failed"}, // 5203 }; //+------------------------------------------------------------------+ //| Array of execution time error messages (5270 - 5275) | //| (Working with network (sockets)) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_netsocket[][TOTAL_LANG]= { {"В функцию передан неверный хэндл сокета","Invalid socket handle passed to function"}, // 5270 {"Открыто слишком много сокетов (максимум 128)","Too many open sockets (max 128)"}, // 5271 {"Ошибка соединения с удаленным хостом","Failed to connect to remote host"}, // 5272 {"Ошибка отправки/получения данных из сокета","Failed to send/receive data from socket"}, // 5273 {"Ошибка установления защищенного соединения (TLS Handshake)","Failed to establish secure connection (TLS Handshake)"}, // 5274 {"Отсутствуют данные о сертификате, которым защищено подключение","No data on certificate protecting connection"}, // 5275 }; //+------------------------------------------------------------------+ //| Array of execution time error messages (5300 - 5310) | //| (Custom symbols) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_custom_symbol[][TOTAL_LANG]= { {"Должен быть указан пользовательский символ","Custom symbol must be specified"}, // 5300 {"Некорректное имя пользовательского символа","Name of custom symbol invalid"}, // 5301 {"Слишком длинное имя для пользовательского символа","Name of custom symbol too long"}, // 5302 {"Слишком длинный путь для пользовательского символа","Path of custom symbol too long"}, // 5303 {"Пользовательский символ с таким именем уже существует","Custom symbol with the same name already exists"}, // 5304 { "Ошибка при создании, удалении или изменении пользовательского символа", // 5305 "Error occurred while creating, deleting or changing the custom symbol" }, {"Попытка удалить пользовательский символ, выбранный в обзоре рынка","You are trying to delete custom symbol selected in Market Watch"}, // 5306 {"Неправильное свойство пользовательского символа","Invalid custom symbol property"}, // 5307 {"Ошибочный параметр при установке свойства пользовательского символа","Wrong parameter while setting property of custom symbol"}, // 5308 { "Слишком длинный строковый параметр при установке свойства пользовательского символа", // 5309 "A too long string parameter while setting the property of a custom symbol" }, {"Не упорядоченный по времени массив тиков","Ticks in array not arranged in order of time"}, // 5310 }; //+------------------------------------------------------------------+ //| Array of execution time error messages (5400 - 5402) | //| (Economic calendar) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_calendar[][TOTAL_LANG]= { {"Размер массива недостаточен для получения описаний всех значений","Array size insufficient for receiving descriptions of all values"}, // 5400 {"Превышен лимит запроса по времени","Request time limit exceeded"}, // 5401 {"Страна не найдена","Country not found"}, // 5402 }; //+------------------------------------------------------------------+ //| Array of execution time error messages (5601 - 5626) | //| (Working with databases) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_sqlite[][TOTAL_LANG]= { {"Общая ошибка","Generic error"}, // 5601 {"Внутренняя логическая ошибка в SQLite","SQLite internal logic error"}, // 5602 {"Отказано в доступе","Access denied"}, // 5603 {"Процедура обратного вызова запросила прерывание","Callback routine requested abort"}, // 5604 {"Файл базы данных заблокирован","Database file locked"}, // 5605 {"Таблица в базе данных заблокирована ","Database table locked"}, // 5606 {"Сбой malloc()","Insufficient memory for completing operation"}, // 5607 {"Попытка записи в базу данных, доступной только для чтения ","Attempt to write to readonly database"}, // 5608 {"Операция прекращена с помощью sqlite3_interrupt() ","Operation terminated by sqlite3_interrupt()"}, // 5609 {"Ошибка дискового ввода-вывода","Disk I/O error"}, // 5610 {"Образ диска базы данных испорчен","Database disk image corrupted"}, // 5611 {"Неизвестный код операции в sqlite3_file_control()","Unknown operation code in sqlite3_file_control()"}, // 5612 {"Ошибка вставки, так как база данных заполнена ","Insertion failed because database is full"}, // 5613 {"Невозможно открыть файл базы данных","Unable to open the database file"}, // 5614 {"Ошибка протокола блокировки базы данных ","Database lock protocol error"}, // 5615 {"Только для внутреннего использования","Internal use only"}, // 5616 {"Схема базы данных изменена","Database schema changed"}, // 5617 {"Строка или BLOB превышает ограничение по размеру","String or BLOB exceeds size limit"}, // 5618 {"Прервано из-за нарушения ограничения","Abort due to constraint violation"}, // 5619 {"Несоответствие типов данных","Data type mismatch"}, // 5620 {"Ошибка неправильного использования библиотеки","Library used incorrectly"}, // 5621 {"Использование функций операционной системы, не поддерживаемых на хосте","Uses OS features not supported on host"}, // 5622 {"Отказано в авторизации","Authorization denied"}, // 5623 {"Не используется ","Not used "}, // 5624 {"2-й параметр для sqlite3_bind находится вне диапазона","Bind parameter error, incorrect index"}, // 5625 {"Открытый файл не является файлом базы данных","File opened that is not database file"}, // 5626 }; //+------------------------------------------------------------------+ #ifdef __MQL4__
这些是在函数库初创后引入 MQL5 的新错误消息。 以前无法将它们添加到函数库当中,因为它们不存在于该语言中。 现在已经实现了这些错误代码,并已经发布了几个终端版本,我终于可以将这些错误代码添加到函数库之中了,且不存在版本不兼容的风险。
现在是时候令函数库消息类在处理错误代码时能够引用这些类了。
为了达此目的,请在 \MQL5\Include\DoEasy\Services\Message.mqh 里,根据数值范围添加代码验证,并从必要数组提取与错误代码对应的文本,赋值到 m_text 变量:
//+------------------------------------------------------------------+ //| Get messages from the text array by an ID | //+------------------------------------------------------------------+ void CMessage::GetTextByID(const int msg_id) { CMessage::m_text= ( //--- Runtime errors (0, 4001 - 4019) msg_id==0 ? messages_runtime[msg_id][m_lang_num] : #ifdef __MQL5__ msg_id>4000 && msg_id<4020 ? messages_runtime[msg_id-4000][m_lang_num] : //--- Runtime errors (Charts 4101 - 4116) msg_id>4100 && msg_id<4117 ? messages_runtime_charts[msg_id-4101][m_lang_num] : //--- Runtime errors (Graphical objects 4201 - 4205) msg_id>4200 && msg_id<4206 ? messages_runtime_graph_obj[msg_id-4201][m_lang_num] : //--- Runtime errors (MarketInfo 4301 - 4305) msg_id>4300 && msg_id<4306 ? messages_runtime_market[msg_id-4301][m_lang_num] : //--- Runtime errors (Access to history 4401 - 4407) msg_id>4400 && msg_id<4408 ? messages_runtime_history[msg_id-4401][m_lang_num] : //--- Runtime errors (Global Variables 4501 - 4524) msg_id>4500 && msg_id<4525 ? messages_runtime_global[msg_id-4501][m_lang_num] : //--- Runtime errors (Custom indicators 4601 - 4603) msg_id>4600 && msg_id<4604 ? messages_runtime_custom_indicator[msg_id-4601][m_lang_num] : //--- Runtime errors (Account 4701 - 4758) msg_id>4700 && msg_id<4759 ? messages_runtime_account[msg_id-4701][m_lang_num] : //--- Runtime errors (Indicators 4801 - 4812) msg_id>4800 && msg_id<4813 ? messages_runtime_indicator[msg_id-4801][m_lang_num] : //--- Runtime errors (Market depth 4901 - 4904) msg_id>4900 && msg_id<4905 ? messages_runtime_books[msg_id-4901][m_lang_num] : //--- Runtime errors (File operations 5001 - 5027) msg_id>5000 && msg_id<5028 ? messages_runtime_files[msg_id-5001][m_lang_num] : //--- Runtime errors (Converting strings 5030 - 5044) msg_id>5029 && msg_id<5045 ? messages_runtime_string[msg_id-5030][m_lang_num] : //--- Runtime errors (Working with arrays 5050 - 5063) msg_id>5049 && msg_id<5064 ? messages_runtime_array[msg_id-5050][m_lang_num] : //--- Runtime errors (Working with OpenCL 5100 - 5114) msg_id>5099 && msg_id<5115 ? messages_runtime_opencl[msg_id-5100][m_lang_num] : //--- Runtime errors (Working with databases 5120 - 5130) msg_id>5119 && msg_id<5131 ? messages_runtime_database[msg_id-5120][m_lang_num] : //--- Runtime errors (Working with WebRequest() 5200 - 5203) msg_id>5199 && msg_id<5204 ? messages_runtime_webrequest[msg_id-5200][m_lang_num] : //--- Runtime errors (Working with network (sockets) 5270 - 5275) msg_id>5269 && msg_id<5276 ? messages_runtime_netsocket[msg_id-5270][m_lang_num] : //--- Runtime errors (Custom symbols 5300 - 5310) msg_id>5299 && msg_id<5311 ? messages_runtime_custom_symbol[msg_id-5300][m_lang_num] : //--- Runtime errors (Economic calendar 5400 - 5402) msg_id>5399 && msg_id<5403 ? messages_runtime_calendar[msg_id-5400][m_lang_num] : //--- Runtime errors (Working with databases 5601 - 5626) msg_id>5600 && msg_id<5627 ? messages_runtime_sqlite[msg_id-5601][m_lang_num] : //--- Trade server return codes (10004 - 10045) msg_id>10003 && msg_id<10047 ? messages_ts_ret_code[msg_id-10004][m_lang_num] : #else // MQL4 msg_id>0 && msg_id<10 ? messages_ts_ret_code_mql4[msg_id][m_lang_num] : msg_id>63 && msg_id<66 ? messages_ts_ret_code_mql4[msg_id-54][m_lang_num] : msg_id>127 && msg_id<151 ? messages_ts_ret_code_mql4[msg_id-116][m_lang_num] : msg_id<4000 ? messages_ts_ret_code_mql4[26][m_lang_num] : //--- MQL4 runtime errors (4000 - 4030) msg_id<4031 ? messages_runtime_4000_4030[msg_id-4000][m_lang_num] : //--- MQL4 runtime errors (4050 - 4075) msg_id>4049 && msg_id<4076 ? messages_runtime_4050_4075[msg_id-4050][m_lang_num] : //--- MQL4 runtime errors (4099 - 4112) msg_id>4098 && msg_id<4113 ? messages_runtime_4099_4112[msg_id-4099][m_lang_num] : //--- MQL4 runtime errors (4200 - 4220) msg_id>4199 && msg_id<4221 ? messages_runtime_4200_4220[msg_id-4200][m_lang_num] : //--- MQL4 runtime errors (4250 - 4266) msg_id>4249 && msg_id<4267 ? messages_runtime_4250_4266[msg_id-4250][m_lang_num] : //--- MQL4 runtime errors (5001 - 5029) msg_id>5000 && msg_id<5030 ? messages_runtime_5001_5029[msg_id-5001][m_lang_num] : //--- MQL4 runtime errors (5200 - 5203) msg_id>5199 && msg_id<5204 ? messages_runtime_5200_5203[msg_id-5200][m_lang_num] : #endif //--- Library messages (ERR_USER_ERROR_FIRST) msg_id>ERR_USER_ERROR_FIRST-1 ? messages_library[msg_id-ERR_USER_ERROR_FIRST][m_lang_num] : messages_library[MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE-ERR_USER_ERROR_FIRST][m_lang_num] ); } //+------------------------------------------------------------------+
在处理交易服务器的返回代码时,将代码范围的上限加 1,因为我们现在有一条代码为 10046 的新消息,它也应该包括在可被处理的错误代码范围内。
现在,该函数库能够正确处理新的交易服务器和运行时错误代码了。 我们往成交对象里添加新属性。 现在我们有了新的 “Deal fee” 属性,而止损和止盈属性现在也是成交中固有的。 我们也要把它们添加到成交属性当中。
在 \MQL5\Include\DoEasy\Defines.mqh 中,将新属性添加到实数型属性枚举中(该函数库包含抽象订单,而所有其它内容,即成交和头寸,都是从中派生而来的):
//+------------------------------------------------------------------+ //| Order, deal, position real properties | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_DOUBLE { ORDER_PROP_PRICE_OPEN = ORDER_PROP_INTEGER_TOTAL, // Open price (MQL5 deal price) ORDER_PROP_PRICE_CLOSE, // Close price ORDER_PROP_SL, // StopLoss price ORDER_PROP_TP, // TaleProfit price ORDER_PROP_FEE, // Deal fee (DEAL_FEE from ENUM_DEAL_PROPERTY_DOUBLE) ORDER_PROP_PROFIT, // Profit ORDER_PROP_COMMISSION, // Commission ORDER_PROP_SWAP, // Swap ORDER_PROP_VOLUME, // Volume ORDER_PROP_VOLUME_CURRENT, // Unexecuted volume ORDER_PROP_PROFIT_FULL, // Profit+commission+swap ORDER_PROP_PRICE_STOP_LIMIT, // Limit order price when StopLimit order is activated }; #define ORDER_PROP_DOUBLE_TOTAL (12) // Total number of real properties //+------------------------------------------------------------------+
将实数型属性总数从 11 个增加到 12。
将新属性的可能的排序添加到订单和成交排序准则的枚举当中:
//+------------------------------------------------------------------+ //| Possible criteria of sorting orders and deals | //+------------------------------------------------------------------+ #define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) #define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { //--- Sort by integer properties SORT_BY_ORDER_TICKET = 0, // Sort by an order ticket SORT_BY_ORDER_MAGIC, // Sort by an order magic number SORT_BY_ORDER_TIME_OPEN, // Sort by an order open time in milliseconds SORT_BY_ORDER_TIME_CLOSE, // Sort by an order close time in milliseconds SORT_BY_ORDER_TIME_EXP, // Sort by an order expiration date SORT_BY_ORDER_TYPE_FILLING, // Sort by execution type by remainder SORT_BY_ORDER_TYPE_TIME, // Sort by order lifetime SORT_BY_ORDER_STATUS, // Sort by an order status (market order/pending order/deal/balance and credit operation) SORT_BY_ORDER_TYPE, // Sort by an order type SORT_BY_ORDER_REASON, // Sort by a deal/order/position reason/source SORT_BY_ORDER_STATE, // Sort by an order status SORT_BY_ORDER_POSITION_ID, // Sort by a position ID SORT_BY_ORDER_POSITION_BY_ID, // Sort by an opposite position ID SORT_BY_ORDER_DEAL_ORDER, // Sort by the order a deal is based on SORT_BY_ORDER_DEAL_ENTRY, // Sort by a deal direction – IN, OUT or IN/OUT SORT_BY_ORDER_TIME_UPDATE, // Sort by position change time in seconds SORT_BY_ORDER_TICKET_FROM, // Sort by a parent order ticket SORT_BY_ORDER_TICKET_TO, // Sort by a derived order ticket SORT_BY_ORDER_PROFIT_PT, // Sort by order profit in points SORT_BY_ORDER_CLOSE_BY_SL, // Sort by the flag of closing an order by StopLoss SORT_BY_ORDER_CLOSE_BY_TP, // Sort by the flag of closing an order by TakeProfit SORT_BY_ORDER_MAGIC_ID, // Sort by an order/position "magic number" ID SORT_BY_ORDER_GROUP_ID1, // Sort by the first order/position group ID SORT_BY_ORDER_GROUP_ID2, // Sort by the second order/position group ID SORT_BY_ORDER_PEND_REQ_ID, // Sort by a pending request ID SORT_BY_ORDER_DIRECTION, // Sort by direction (Buy, Sell) //--- Sort by real properties SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Sort by open price SORT_BY_ORDER_PRICE_CLOSE, // Sort by close price SORT_BY_ORDER_SL, // Sort by StopLoss price SORT_BY_ORDER_TP, // Sort by TakeProfit price SORT_BY_ORDER_FEE, // Sort by deal fee SORT_BY_ORDER_PROFIT, // Sort by profit SORT_BY_ORDER_COMMISSION, // Sort by commission SORT_BY_ORDER_SWAP, // Sort by swap SORT_BY_ORDER_VOLUME, // Sort by volume SORT_BY_ORDER_VOLUME_CURRENT, // Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL, // Sort by profit+commission+swap SORT_BY_ORDER_PRICE_STOP_LIMIT, // Sort by Limit order when StopLimit order is activated //--- Sort by string properties SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Sort by symbol SORT_BY_ORDER_COMMENT, // Sort by comment SORT_BY_ORDER_COMMENT_EXT, // Sort by custom comment SORT_BY_ORDER_EXT_ID // Sort by order ID in an external trading system }; //+------------------------------------------------------------------+
现在我们就可以按新的实数型属性针对成交进行排序。
在与窗体相关的鼠标可能状态列表中,稍微调整 ENUM_MOUSE_FORM_STATE 枚举的常量名称,从而它们的名称能更准确地指示与窗体相关的鼠标状态:
//+------------------------------------------------------------------+ //| The list of possible mouse states relative to the form | //+------------------------------------------------------------------+ enum ENUM_MOUSE_FORM_STATE { MOUSE_FORM_STATE_NONE = 0, // Undefined state //--- Outside the form MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED, // The cursor is outside the form, the mouse buttons are not clicked MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED, // The cursor is outside the form, the mouse button (any) is clicked MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL, // The cursor is outside the form, the mouse wheel is being scrolled //--- Within the form MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED, // The cursor is inside the form, no mouse buttons are clicked MOUSE_FORM_STATE_INSIDE_FORM_PRESSED, // The cursor is inside the form, the mouse button (any) is clicked MOUSE_FORM_STATE_INSIDE_FORM_WHEEL, // The cursor is inside the form, the mouse wheel is being scrolled //--- Within the window header area MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED, // The cursor is inside the active area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED, // The cursor is inside the active area, any mouse button is clicked MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL, // The cursor is inside the active area, the mouse wheel is being scrolled //--- Within the window scrolling area MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED, // The cursor is within the window scrolling area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED, // The cursor is within the window scrolling area, the mouse button (any) is clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL, // The cursor is within the window scrolling area, the mouse wheel is being scrolled }; //+------------------------------------------------------------------+
改进 \MQL5\Include\DoEasy\Objects\Orders\Order.mqh 中的抽象订单类。
在类的受保护部分中,声明从成交属性中读取成交费用属性值、并返回该属性值的方法:
protected: //--- Protected parametric constructor COrder(ENUM_ORDER_STATUS order_status,const ulong ticket); //--- Get and return integer properties of a selected order from its parameters long OrderMagicNumber(void) const; long OrderTicket(void) const; long OrderTicketFrom(void) const; long OrderTicketTo(void) const; long OrderPositionID(void) const; long OrderPositionByID(void) const; long OrderOpenTimeMSC(void) const; long OrderCloseTimeMSC(void) const; long OrderType(void) const; long OrderState(void) const; long OrderTypeByDirection(void) const; long OrderTypeFilling(void) const; long OrderTypeTime(void) const; long OrderReason(void) const; long DealOrderTicket(void) const; long DealEntry(void) const; bool OrderCloseByStopLoss(void) const; bool OrderCloseByTakeProfit(void) const; datetime OrderExpiration(void) const; long PositionTimeUpdateMSC(void) const; //--- Get and return real properties of a selected order from its parameters: (1) open price, (2) close price, (3) profit, //--- (4) commission, (5) swap, (6) volume, (7) unexecuted volume (8) StopLoss price, (9) TakeProfit price (10) StopLimit order price double OrderOpenPrice(void) const; double OrderClosePrice(void) const; double OrderProfit(void) const; double OrderCommission(void) const; double OrderSwap(void) const; double OrderVolume(void) const; double OrderVolumeCurrent(void) const; double OrderStopLoss(void) const; double OrderTakeProfit(void) const; double DealFee(void) const; double OrderPriceStopLimit(void) const; //--- Get and return string properties of a selected order from its parameters: (1) symbol, (2) comment, (3) ID at an exchange string OrderSymbol(void) const; string OrderComment(void) const; string OrderExternalID(void) const; //--- Return (1) reason, (2) direction, (3) deal type string GetReasonDescription(const long reason) const; string GetEntryDescription(const long deal_entry) const; string GetTypeDealDescription(const long type_deal) const; public:
在类的公开部分中,用于简化访问订单对象属性的方法模块里,添加返回对象属性中设置的成交费用属性值的方法:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the order object properties | //+------------------------------------------------------------------+ //--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason, //--- (6) position ID, (7) opposite position ID, (8) first group ID, (9) second group ID, //--- (10) pending request ID, (11) magic number ID, (12) type, (13) flag of closing by StopLoss, //--- (14) flag of closing by TakeProfit (15) open time, (16) close time, //--- (17) order expiration date, (18) state, (19) status, (20) type by direction, (21) execution type by remainder, (22) order lifetime long Ticket(void) const { return this.GetProperty(ORDER_PROP_TICKET); } long TicketFrom(void) const { return this.GetProperty(ORDER_PROP_TICKET_FROM); } long TicketTo(void) const { return this.GetProperty(ORDER_PROP_TICKET_TO); } long Magic(void) const { return this.GetProperty(ORDER_PROP_MAGIC); } long Reason(void) const { return this.GetProperty(ORDER_PROP_REASON); } long PositionID(void) const { return this.GetProperty(ORDER_PROP_POSITION_ID); } long PositionByID(void) const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID); } long MagicID(void) const { return this.GetProperty(ORDER_PROP_MAGIC_ID); } long GroupID1(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID1); } long GroupID2(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID2); } long PendReqID(void) const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID); } long TypeOrder(void) const { return this.GetProperty(ORDER_PROP_TYPE); } bool IsCloseByStopLoss(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); } bool IsCloseByTakeProfit(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); } long TimeOpen(void) const { return this.GetProperty(ORDER_PROP_TIME_OPEN); } long TimeClose(void) const { return this.GetProperty(ORDER_PROP_TIME_CLOSE); } datetime TimeExpiration(void) const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP); } ENUM_ORDER_STATE State(void) const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE); } ENUM_ORDER_STATUS Status(void) const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS); } ENUM_ORDER_TYPE TypeByDirection(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); } ENUM_ORDER_TYPE_FILLING TypeFilling(void) const { return (ENUM_ORDER_TYPE_FILLING)this.GetProperty(ORDER_PROP_TYPE_FILLING); } ENUM_ORDER_TYPE_TIME TypeTime(void) const { return (ENUM_ORDER_TYPE_TIME)this.GetProperty(ORDER_PROP_TYPE_TIME); } //--- Return (1) open price, (2) close price, (3) profit, (4) commission, (5) swap, (6) volume, //--- (7) unexecuted volume (8) StopLoss and (9) TakeProfit, (10) deal fee and (11) StopLimit order price double PriceOpen(void) const { return this.GetProperty(ORDER_PROP_PRICE_OPEN); } double PriceClose(void) const { return this.GetProperty(ORDER_PROP_PRICE_CLOSE); } double Profit(void) const { return this.GetProperty(ORDER_PROP_PROFIT); } double Comission(void) const { return this.GetProperty(ORDER_PROP_COMMISSION); } double Swap(void) const { return this.GetProperty(ORDER_PROP_SWAP); } double Volume(void) const { return this.GetProperty(ORDER_PROP_VOLUME); } double VolumeCurrent(void) const { return this.GetProperty(ORDER_PROP_VOLUME_CURRENT); } double StopLoss(void) const { return this.GetProperty(ORDER_PROP_SL); } double TakeProfit(void) const { return this.GetProperty(ORDER_PROP_TP); } double Fee(void) const { return this.GetProperty(ORDER_PROP_FEE); } double PriceStopLimit(void) const { return this.GetProperty(ORDER_PROP_PRICE_STOP_LIMIT); } //--- Return (1) symbol, (2) comment, (3) ID at an exchange string Symbol(void) const { return this.GetProperty(ORDER_PROP_SYMBOL); } string Comment(void) const { return this.GetProperty(ORDER_PROP_COMMENT); } string CommentExt(void) const { return this.GetProperty(ORDER_PROP_COMMENT_EXT); } string ExternalID(void) const { return this.GetProperty(ORDER_PROP_EXT_ID); } //--- Get the full order profit double ProfitFull(void) const { return this.Profit()+this.Comission()+this.Swap(); } //--- Get order profit in points int ProfitInPoints(void) const; //--- Set (1) the first group ID, (2) the second group ID, (3) the pending request ID, (4) custom comment void SetGroupID1(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id); } void SetGroupID2(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id); } void SetPendReqID(const long req_id) { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id); } void SetCommentExt(const string comment_ext) { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext); } //+------------------------------------------------------------------+ //| Descriptions of the order object properties | //+------------------------------------------------------------------+
在封闭的参数化类构造函数中读取和设置对象新属性值:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Set the object type this.m_type=OBJECT_DE_TYPE_ORDER_DEAL_POSITION; //--- Save integer properties this.m_ticket=ticket; this.m_long_prop[ORDER_PROP_STATUS] = order_status; this.m_long_prop[ORDER_PROP_MAGIC] = this.OrderMagicNumber(); this.m_long_prop[ORDER_PROP_TICKET] = this.OrderTicket(); this.m_long_prop[ORDER_PROP_TIME_EXP] = this.OrderExpiration(); this.m_long_prop[ORDER_PROP_TYPE_FILLING] = this.OrderTypeFilling(); this.m_long_prop[ORDER_PROP_TYPE_TIME] = this.OrderTypeTime(); this.m_long_prop[ORDER_PROP_TYPE] = this.OrderType(); this.m_long_prop[ORDER_PROP_STATE] = this.OrderState(); this.m_long_prop[ORDER_PROP_DIRECTION] = this.OrderTypeByDirection(); this.m_long_prop[ORDER_PROP_POSITION_ID] = this.OrderPositionID(); this.m_long_prop[ORDER_PROP_REASON] = this.OrderReason(); this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET] = this.DealOrderTicket(); this.m_long_prop[ORDER_PROP_DEAL_ENTRY] = this.DealEntry(); this.m_long_prop[ORDER_PROP_POSITION_BY_ID] = this.OrderPositionByID(); this.m_long_prop[ORDER_PROP_TIME_OPEN] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = this.PositionTimeUpdateMSC(); //--- Save real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)] = this.OrderOpenPrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)] = this.OrderClosePrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)] = this.OrderProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)] = this.OrderCommission(); this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)] = this.OrderSwap(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)] = this.OrderVolume(); this.m_double_prop[this.IndexProp(ORDER_PROP_SL)] = this.OrderStopLoss(); this.m_double_prop[this.IndexProp(ORDER_PROP_TP)] = this.OrderTakeProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_FEE)] = this.DealFee(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)] = this.OrderVolumeCurrent(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)] = this.OrderPriceStopLimit(); //--- Save string properties this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)] = this.OrderSymbol(); this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)] = this.OrderComment(); this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)] = this.OrderExternalID(); //--- Save additional integer properties this.m_long_prop[ORDER_PROP_PROFIT_PT] = this.ProfitInPoints(); this.m_long_prop[ORDER_PROP_TICKET_FROM] = this.OrderTicketFrom(); this.m_long_prop[ORDER_PROP_TICKET_TO] = this.OrderTicketTo(); this.m_long_prop[ORDER_PROP_CLOSE_BY_SL] = this.OrderCloseByStopLoss(); this.m_long_prop[ORDER_PROP_CLOSE_BY_TP] = this.OrderCloseByTakeProfit(); this.m_long_prop[ORDER_PROP_MAGIC_ID] = this.GetMagicID((uint)this.GetProperty(ORDER_PROP_MAGIC)); this.m_long_prop[ORDER_PROP_GROUP_ID1] = this.GetGroupID1((uint)this.GetProperty(ORDER_PROP_MAGIC)); this.m_long_prop[ORDER_PROP_GROUP_ID2] = this.GetGroupID2((uint)this.GetProperty(ORDER_PROP_MAGIC)); this.m_long_prop[ORDER_PROP_PEND_REQ_ID] = this.GetPendReqID((uint)this.GetProperty(ORDER_PROP_MAGIC)); //--- Save additional real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); //--- Save additional string properties this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)] = ""; } //+------------------------------------------------------------------+
在返回止损价格的方法中,写入成交的止损值:
//+------------------------------------------------------------------+ //| Return StopLoss price | //+------------------------------------------------------------------+ double COrder::OrderStopLoss(void) const { #ifdef __MQL4__ return ::OrderStopLoss(); #else double res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetDouble(POSITION_SL); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetDouble(ORDER_SL); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetDouble(m_ticket,ORDER_SL); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetDouble(m_ticket,DEAL_SL); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
如果订单状态为成交,则依据票据读取成交的止损值,并返回结果。
在返回止盈价格的方法中执行相同的操作:
//+------------------------------------------------------------------+ //| Return TakeProfit price | //+------------------------------------------------------------------+ double COrder::OrderTakeProfit(void) const { #ifdef __MQL4__ return ::OrderTakeProfit(); #else double res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetDouble(POSITION_TP); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetDouble(ORDER_TP); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetDouble(m_ticket,ORDER_TP); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetDouble(m_ticket,DEAL_TP); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
返回成交费用的方法:
//+------------------------------------------------------------------+ //| Return the deal fee | //+------------------------------------------------------------------+ double COrder::DealFee(void) const { #ifdef __MQL4__ return 0; #else return ::HistoryDealGetDouble(m_ticket,DEAL_FEE); #endif } //+------------------------------------------------------------------+
如果是 MQL4 平台,则订单没有此类属性 — 返回零。
对于 MQL5,依据其票证从成交属性中读取所需的数值并返回。
在返回订单实数型属性描述的方法中添加新属性的处理:
//+------------------------------------------------------------------+ //| Return description of order's real property | //+------------------------------------------------------------------+ string COrder::GetPropertyDescription(ENUM_ORDER_PROP_DOUBLE property) { int dg=(int)::SymbolInfoInteger(this.GetProperty(ORDER_PROP_SYMBOL),SYMBOL_DIGITS); int dgl=(int)DigitsLots(this.GetProperty(ORDER_PROP_SYMBOL)); return ( //--- General properties property==ORDER_PROP_PRICE_CLOSE ? CMessage::Text(MSG_ORD_PRICE_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==ORDER_PROP_PRICE_OPEN ? CMessage::Text(MSG_ORD_PRICE_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==ORDER_PROP_SL ? CMessage::Text(MSG_LIB_PROP_PRICE_SL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(property)==0 ? CMessage::Text(MSG_LIB_PROP_EMPTY) : ": "+::DoubleToString(this.GetProperty(property),dg)) ) : property==ORDER_PROP_TP ? CMessage::Text(MSG_LIB_PROP_PRICE_TP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(property)==0 ? CMessage::Text(MSG_LIB_PROP_EMPTY) : ": "+::DoubleToString(this.GetProperty(property),dg)) ) : property==ORDER_PROP_FEE ? CMessage::Text(MSG_LIB_PROP_DEAL_FEE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(property)==0 ? CMessage::Text(MSG_LIB_PROP_EMPTY) : ": "+::DoubleToString(this.GetProperty(property),dg)) ) : property==ORDER_PROP_PROFIT ? CMessage::Text(MSG_LIB_PROP_PROFIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==ORDER_PROP_COMMISSION ? CMessage::Text(MSG_ORD_COMMISSION)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==ORDER_PROP_SWAP ? CMessage::Text(MSG_ORD_SWAP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==ORDER_PROP_VOLUME ? CMessage::Text(MSG_ORD_VOLUME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dgl) ) : property==ORDER_PROP_VOLUME_CURRENT ? CMessage::Text(MSG_ORD_VOLUME_CURRENT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dgl) ) : property==ORDER_PROP_PRICE_STOP_LIMIT ? CMessage::Text(MSG_ORD_PRICE_STOP_LIMIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : //--- Additional property property==ORDER_PROP_PROFIT_FULL ? CMessage::Text(MSG_ORD_PROFIT_FULL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : "" ); } //+------------------------------------------------------------------+
所有其它订单元素,包括余额操作、成交和仓位,都是从抽象订单类派生而来的。 我们来改进 \MQL5\Include\DoEasy\Objects\Orders\HistoryDeal.mqh 中的成交对象类。
在此,我们需要从方法中删除止损和止盈属性,因为它们在属性列表中不受支持,该方法返回指示对象的实数型属性不受支持的标志 — 现在成交提供了这两个属性,故它们应由类对象支持:
//+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CHistoryDeal::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { if(property==ORDER_PROP_TP || property==ORDER_PROP_SL || property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_VOLUME_CURRENT || property==ORDER_PROP_PRICE_STOP_LIMIT || ( this.OrderType()==DEAL_TYPE_BALANCE && ( property==ORDER_PROP_PRICE_OPEN || property==ORDER_PROP_COMMISSION || property==ORDER_PROP_SWAP || property==ORDER_PROP_VOLUME ) ) ) return false; return true; } //+------------------------------------------------------------------+
现在,方法如下所示:
//+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CHistoryDeal::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { if(property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_VOLUME_CURRENT || property==ORDER_PROP_PRICE_STOP_LIMIT || ( this.OrderType()==DEAL_TYPE_BALANCE && ( property==ORDER_PROP_PRICE_OPEN || property==ORDER_PROP_COMMISSION || property==ORDER_PROP_SWAP || property==ORDER_PROP_VOLUME ) ) ) return false; return true; } //+------------------------------------------------------------------+
由于该方法列举了不受支持的属性,因此新添加的 DEAL_FEE 属性最初不在列表当中,这意味着我们不需要在此处设置它,因为默认情况下它会受支持。
所有新属性和错误代码都会被加到函数库之中,并应正确处理。
现在我们继续处理鼠标拖动窗体对象。
独立处理窗体对象移动
首先,如果在窗体上单击并按住按钮,我们应该决定如何处理鼠标移动事件。 考虑到从前一篇文章里获取的经验,我们需要开发一个稍微不同的概念来处理与窗体相关的鼠标事件。 在测试前一篇文章中的 EA 时,我在鼠标与多个窗体对象的交互中发现了多处缺点和错误行为。 因此,我们需要另一种方式。
多次实验证明,应该在图形元素集合类事件的处理程序中创建标志系统。 这些标志用于指示鼠标光标当前所在的窗体对象、此时是否按下鼠标按钮、以及是否于图表上所有窗体之外按下了鼠标按钮。 窗体对象拥有与环境交互的标志。 利用它可以指定要使用的窗体,以及要处理的事件。
换句话说,这个概念应该是:
- 如果光标位于图表上所有窗体之外,则允许图表使用上下文菜单,也允许用鼠标和十字线工具拖动图表,而当我们用鼠标拖动图表时,光标触摸窗体(按下窗体外部的鼠标按钮)时,应该不会响应。
- 一旦鼠标光标悬停在任何窗体对象上(松开按钮),则禁用所有图表工具,并等待在窗体上按下鼠标按钮,或等待鼠标与窗体的任何其它交互(例如,鼠标滚轮滚动、或将光标悬停在窗体上方时,视觉效果显示为多个视觉窗体效果)。
- 如果在窗体上按下鼠标按钮,则会为窗体设置交互和移动标志。 然后,交互标志会用于选择两个窗体中的哪一种,前提是这两个窗体重叠,鼠标光标悬停在它们上面,然后按下鼠标按钮。 应选择带有激活交互标志的窗体。
如果我们在用鼠标拖动窗体后释放按钮,图表工具将被启用,而窗体交互标志仍处于激活状态。 因此,如果再次叠加并释放鼠标按钮,则从两个窗体中选择该窗体。 如果光标选择了当前未激活的窗体(而不是刚刚拖动的窗体),并开始拖动它,则会从第一个窗体中删除交互标志,并为所选窗体激活交互标志。
这样的标记系统将始终能令我们知道哪个窗体最后处于激活状态,哪个窗体的光标悬停在其上方,以及它是否可以被直观地高亮显示(稍后在窗体对象类中会实现这些处理程序,而非在图形元素集合类中实现)。 我们始终能够与光标悬停其上的窗体进行交互,并拖动最后被鼠标选中的窗体。
我们需要删除窗体对象类事件处理程序中创建的所有内容(除了调整窗体坐标所对应的图表变更事件)。 打开 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh,并从处理程序中删除冗余代码,只保留垂直坐标偏移的调整:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
此外,我已经更正了枚举常量名,令它们更容易理解:
//+------------------------------------------------------------------+ //| Return the mouse status relative to the form | //+------------------------------------------------------------------+ ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam) { //--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys ENUM_MOUSE_FORM_STATE form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED; ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam); //--- Get the mouse status flags from the CMouseState class object and save them in the variable this.m_mouse_state_flags=this.m_mouse.GetMouseFlags(); //--- If the cursor is inside the form if(CGCnvElement::CursorInsideElement(m_mouse.CoordX(),m_mouse.CoordY())) { //--- Set bit 8 responsible for the "cursor inside the form" flag this.m_mouse_state_flags |= (0x0001<<8); //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area" if(CGCnvElement::CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY())) this.m_mouse_state_flags |= (0x0001<<9); //--- otherwise, release the bit "cursor inside the active area" else this.m_mouse_state_flags &=0xFDFF; //--- If one of the mouse buttons is clicked, check the cursor location in the active area and //--- return the appropriate value of the pressed key (in the active area or the form area) if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_PRESSED); //--- otherwise, if not a single mouse button is pressed else { //--- if the mouse wheel is scrolled, return the appropriate wheel scrolling value (in the active area or the form area) if((this.m_mouse_state_flags & 0x0080)!=0) form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : MOUSE_FORM_STATE_INSIDE_FORM_WHEEL); //--- otherwise, return the appropriate value of the unpressed key (in the active area or the form area) else form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED); } } //--- If the cursor is outside the form else { //--- return the appropriate button value in an inactive area form_state= ( ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED ); } return form_state; } //+------------------------------------------------------------------+
一切就绪。 现在我们需要在图形元素集合类事件处理程序中创建标志系统。
打开 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh,并进行必要的改进。
在类的私密部分中,声明“鼠标状态”类对象,以及两个方法 — 返回光标所指窗体的指针,和重置除指定窗体之外的所有窗体的所有交互标志:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Indicator for controlling graphical object events packed into the program resources class CGraphElementsCollection : public CBaseObj { private: CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects CArrayObj m_list_deleted_obj; // List of removed graphical objects CMouseState m_mouse; // "Mouse status" class object bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements bool IsPresentCanvElmInList(const long chart_id,const string name); //--- Return the flag indicating the presence of the graphical object class in the graphical object collection list bool IsPresentGraphObjInList(const long chart_id,const string name); //--- Return the flag indicating the presence of a graphical object on a chart by name bool IsPresentGraphObjOnChart(const long chart_id,const string name); //--- Return the pointer to the object of managing objects of the specified chart CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id); //--- Create a new object of managing graphical objects of a specified chart and add it to the list CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id); //--- Update the list of graphical objects by chart ID CChartObjectsControl *RefreshByChartID(const long chart_id); //--- Check if the chart window is present bool IsPresentChartWindow(const long chart_id); //--- Handle removing the chart window void RefreshForExtraObjects(void); //--- Return the first free ID of the graphical (1) object and (2) element on canvas long GetFreeGraphObjID(bool program_object); long GetFreeCanvElmID(void); //--- Add (1) the standard graphical object and (2) the graphical element on canvas to the collection bool AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control); //--- Return the pointer to the form located under the cursor CForm *GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam,ENUM_MOUSE_FORM_STATE &mouse_state); //--- Reset all interaction flags for all forms except the specified one void ResetAllInteractionExeptOne(CForm *form); public: bool AddCanvElmToCollection(CGCnvElement *element); private: //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); CGStdGraphObj *FindMissingObj(const long chart_id,int &index); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID bool DeleteGraphObjFromList(CGStdGraphObj *obj); void DeleteGraphObjectsFromList(const long chart_id); //--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index bool MoveGraphObjToDeletedObjList(CGStdGraphObj *obj); bool MoveGraphObjToDeletedObjList(const int index); //--- Move all objects by chart ID to the list of removed graphical objects void MoveGraphObjectsToDeletedObjList(const long chart_id); //--- Remove the object of managing charts from the list bool DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj); //--- Set the flags of scrolling the chart with the mouse, context menu and crosshairs tool for the specified chart void SetChartTools(const long chart_id,const bool flag); public:
该方法返回光标所指处的窗体指针:
//+------------------------------------------------------------------+ //| Return the pointer to the form located under the cursor | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam,ENUM_MOUSE_FORM_STATE &mouse_state) { //--- Initialize the mouse status relative to the form mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare the pointers to graphical element collection class objects CGCnvElement *elm=NULL; CForm *form=NULL; //--- Get the list of objects the interaction flag is set for (there should be only one object) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); //--- If managed to obtain the list and it is not empty if(list!=NULL && list.Total()>0) { //--- Get the only graphical element there elm=list.At(0); //--- If it is a form object if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- If there is no a single form object with a specified interaction flag, //--- in the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the next element elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL) continue; //--- if the obtained element is a form object if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- Nothing is found - return NULL return NULL; } //+------------------------------------------------------------------+
该方法已在代码注释中详述。 简而言之,我们需要知道鼠标光标所在的窗体对象。 如果我们只简单地看一下光标坐标,并将其与窗体坐标和尺寸进行比较,我们肯定会找到光标所处的窗体。 然而,我们在此会面临一个问题:如果两个窗体重叠,则会选择列表中的第一个窗体,而不是位于图表上最前面的那个窗体,故这是错误的。 因此,在窗体上单击鼠标按钮将设置查找激活窗体的相应交互标志。 只有当这样的窗体不存在时,我们才开始搜索所有列出的、鼠标光标位于其上的窗体。 这样的方式为我们在操作鼠标时提供了正确的行为。 始终选择最后激活的窗体。 这是位于图表上所有其它窗体之上的窗体,即在前景上,因为窗体在选择后会立即移到前景。
该方法重置除指定窗体之外的所有窗体的交互标志:
//+--------------------------------------------------------------------+ //| Reset all interaction flags for all forms except the specified one | //+--------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CForm *form_exept) { //--- In the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the pointer to a form object CForm *form=this.m_list_all_canv_elm_obj.At(i); //--- if failed to receive the pointer, or it is not a form, or it is not a form whose pointer has been passed to the method, move on if(form==NULL || form.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (form.Name()==form_exept.Name() && form.ChartID()==form_exept.ChartID())) continue; //--- Reset the interaction flag for the current form in the loop form.SetInteraction(false); } } //+------------------------------------------------------------------+
利用该方法,我们每次始终只有一个带有激活交互标志的窗体。 当用鼠标选择一个窗体时,我们会立即为它设置交互标志。 而所有其它窗体,应禁用该标志。 这是在调用方法时完成的。
如果发生除图表更改事件以外的任何事件,我们应该调用鼠标交互事件的处理程序、和图形元素集合类事件处理程序中的窗体。 紧跟在图表更改事件检查之后的代码片段正适合这种情况,因为我们需要响应除此之外的所有事件。 我们将鼠标与窗体交互的处理程序模块放置在必要的地方:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj_std=NULL; // Pointer to the standard graphical object CGCnvElement *obj_cnv=NULL; // Pointer to the graphical element object on canvas ushort idx=ushort(id-CHARTEVENT_CUSTOM); if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK) { //--- Calculate the chart ID //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID //--- If the event ID corresponds to a user event, the chart ID is received from lparam //--- Otherwise, the chart ID is assigned to -1 long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE); long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param); //--- Get the object, whose properties were changed or which was relocated, //--- from the collection list by its name set in sparam obj_std=this.GetStdGraphObject(sparam,chart_id); //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed if(obj_std==NULL) { //--- Let's search the list for the object that is not on the chart obj_std=this.FindMissingObj(chart_id); //--- If failed to find the object here as well, exit if(obj_std==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(chart_id); //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- and send an event with the new name of the object to the control program chart if(obj_std.SetNamePrev(obj_std.Name()) && obj_std.SetName(name_new)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj_std.ChartID(),obj_std.TimeCreate(),obj_std.Name()); } //--- Update the properties of the obtained object //--- and check their change obj_std.PropertiesRefresh(); obj_std.PropertiesCheckChanged(); } //--- Handle standard graphical object events in the collection list for(int i=0;i<this.m_list_all_graph_obj.Total();i++) { //--- Get the next graphical object and obj_std=this.m_list_all_graph_obj.At(i); if(obj_std==NULL) continue; //--- call its event handler obj_std.OnChartEvent((id<CHARTEVENT_CUSTOM ? id : idx),lparam,dparam,sparam); } //--- Handle chart changes for extended standard objects if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE) { CArrayObj *list=this.GetListStdGraphObjectExt(); if(list!=NULL) { for(int i=0;i<list.Total();i++) { obj_std=list.At(i); if(obj_std==NULL) continue; obj_std.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); } } } //--- Handling mouse events of graphical objects on canvas //--- If the event is not a chart change else { //--- Check whether the mouse button is pressed bool pressed=(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)==MOUSE_BUTT_KEY_STATE_LEFT ? true : false); ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare static variables for the active form and status flags static CForm *form=NULL; static bool pressed_chart=false; static bool pressed_form=false; static bool move=false; //--- If the button is not pressed on the chart and the movement flag is not set, get the form, above which the cursor is located if(!pressed_chart && !move) form=this.GetFormUnderCursor(id,lparam,dparam,sparam,mouse_state); //--- If the button is not pressed, reset all flags and enable the chart tools if(!pressed) { pressed_chart=false; pressed_form=false; move=false; SetChartTools(::ChartID(),true); } //--- If this is a mouse movement event and the movement flag is active, move the form, above which the cursor is located (if the pointer to it is valid) if(id==CHARTEVENT_MOUSE_MOVE && move) { if(form!=NULL) { //--- calculate the cursor movement relative to the form coordinate origin int x=this.m_mouse.CoordX()-form.OffsetX(); int y=this.m_mouse.CoordY()-form.OffsetY(); //--- get the width and height of the chart the form is located at int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow()); int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow()); //--- Adjust the calculated form coordinates if the form is out of the chart range if(x<0) x=0; if(x>chart_width-form.Width()) x=chart_width-form.Width(); if(y<0) y=0; if(y>chart_height-form.Height()) y=chart_height-form.Height(); //--- Move the form by the obtained coordinates form.Move(x,y,true); } } //--- Display debugging comments on the chart Comment ( (form!=NULL ? form.Name()+":" : ""),"\n", EnumToString((ENUM_CHART_EVENT)id),"\n", EnumToString(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)), "\n",EnumToString(mouse_state), "\npressed=",pressed,", move=",move,(form!=NULL ? ", Interaction="+(string)form.Interaction() : ""), "\npressed_chart=",pressed_chart,", pressed_form=",pressed_form ); //--- If the cursor is not above the form if(form==NULL) { //--- If the mouse button is pressed if(pressed) { //--- If the button is still pressed and held on the form, exit if(pressed_form) { return; } //--- If the button hold flag is not enabled yet, set the flags and enable chart tools if(!pressed_chart) { pressed_chart=true; // Button is held on the chart pressed_form=false; // Cursor is not above the form move=false; // movement disabled SetChartTools(::ChartID(),true); } } } //--- If the cursor is above the form else { //--- If the button is still pressed and held on the chart, exit if(pressed_chart) { return; } //--- If the flag of holding the button on the form is not set yet if(!pressed_form) { pressed_chart=false; // The button is not pressed on the chart SetChartTools(::ChartID(),false); //--- 'The cursor is inside the form, no mouse buttons are clicked' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED) { } //--- 'The cursor is inside the form, a mouse button is clicked (any)' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED) { } //--- 'The cursor is inside the form, the mouse wheel is being scrolled' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL) { } //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED) { //--- Set the cursor shift relative to the form initial coordinates form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); } //--- 'The cursor is inside the active area, any mouse button is clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // the flag of holding the mouse button on the form //--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { //--- Set flags and form parameters move=true; // movement flag form.SetInteraction(true); // flag of the form interaction with the environment form.BringToTop(); // form on the background - above all others this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate } } //--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL) { } //--- 'The cursor is inside the window scrolling area, no mouse buttons are clicked' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED) { } //--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED) { } //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler workpiece if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL) { } } } } } //+------------------------------------------------------------------+
“鼠标与窗体交互”事件处理程序的整个代码模块都附带详细注释。 目前,一些事件只有“存根” — 这些模块将在窗体对象中包含调用事件处理程序。
在此刻,测试新概念的一切都已准备就绪。
测试
为了执行测试,我们取用来自上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part97\ 之下,命名为 TestDoEasyPart97.mq5。
几乎不会有任何变化。 我只是调整了已创建窗体的坐标。 我将使用之前创建的宏替换指定已创建窗体对象的数量:
//+------------------------------------------------------------------+ //| TestDoEasyPart97.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (2) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (188) // Left #define KEY_RIGHT (190) // Right #define KEY_ORIGIN (191) // Initial properties //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpUseColorBG = INPUT_YES; // Use chart background color to calculate shadow color sinput color InpColorForm3 = clrCadetBlue; // Third form shadow color (if not background color) //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create form objects for(int i=0;i<FORMS_TOTAL;i++) { //--- When creating an object, pass all the required parameters to it CForm *form=new CForm("Form_0"+string(i+1),30,(i==0 ? 100 : 160),100,30); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(true); //--- Set the form ID and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the opacity of 200 form.SetOpacity(245); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(false); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity(),true); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.TextOnBG(0,TextByLanguage("Тест 0","Test 0")+string(i+1),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Add the form to the list if(!engine.GraphAddCanvElmToCollection(form)) delete form; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
编译 EA,并在图表上启动它:
在测试来自前一篇文章中的 EA 之后,我消灭了所有提到的缺陷。 此外,移动窗体受到图表边框的限制。 当一个窗体叠加在另一个窗体之上时,始终会选择必要的窗体,且其坐标会始终相对于所移动窗体的光标坐标进行正确计算。
下一步是什么?
在下一篇文章中,我将继续开发函数库图形对象。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第九十三部分):准备创建复合图形对象的功能
DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象
DoEasy 函数库中的图形(第九十五部分):复合图形对象控件
DoEasy 函数库中的图形(第九十六部分):窗体对象中的图形和鼠标事件的处理
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10482


除了作者像打印机一样冲压出这些文章之外,谁还需要它们呢?
我需要。