
Gráficos en la biblioteca DoEasy (Parte 97): Procesamiento independiente del desplazamiento de los objetos de formulario
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Procesamiento independiente del desplazamiento de los objetos de formulario
- Simulación
- ¿Qué es lo próximo?
Concepto
Al desarrollar los objetos gráficos extendidos, nos hemos visto en la necesidad de regresar a los objetos de formulario: los objetos gráficos en el lienzo que estarán presentes en los objetos gráficos extendidos como nodos para controlar los puntos de anclaje de un objeto gráfico. En el último artículo, comenzamos a crear la gestión de eventos del ratón para los objetos de formulario. Hoy terminaremos de procesar el desplazamiento del objeto de formulario. Además, lo implementaremos de tal forma que podamos mover cualquier formulario por el gráfico, seleccionando, en este caso, el formulario capturado por el cursor: todas las herramientas del gráfico se activarán o desactivarán correctamente y en el momento adecuado, dependiendo del evento.
Vamos a organizar el monitoreo de los eventos del ratón de tal forma que luego podamos usar los manejadores preparados hoy para implementar las demás interacciones del objeto de formulario con el ratón. Además de la implementación del desplazamiento de los formularios con el cursor del ratón y la preparación de "stubs" para la implementación de otros eventos de interacción con el ratón, hoy añadiremos los códigos de retorno del servidor comercial y los códigos de error de ejecución que han aparecido en MQL5 después del desarrollo de la biblioteca, pero que aún no han sido añadidos a la misma, así como nuevas propiedades para los objetos comerciales: los niveles de StopLoss y TakeProfit, presentes desde hace cierto tiempo en las propiedades de los objetos comerciales.
Mejorando las clases de la biblioteca
En el archivo MQL5\Include\DoEasy\Data.mqh, añadimos el índice del nuevo mensaje:
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
y el mensaje de texto que se corresponde con el índice nuevamente añadido:
{"Цена 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
Aquí, en el mismo archivo, añadimos una matriz de mensajes de error:
//+---------------------------------------------------------------------+ //| 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" }, }; //+------------------------------------------------------------------+
e insertamos las nuevas matrices; estas contienen los nuevos mensajes de error de tiempo de ejecución, que no se encontraban antes en la biblioteca, pero que ya se han añadido en 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__
Son los nuevos mensajes de error que han aparecido en MQL5 después de ponerse en marcha la biblioteca, y que no han podido añadirse inicialmente porque no estaban en el lenguaje. Ahora que ha transcurrido algún tiempo después de introducir estos códigos de error, y tras varias versiones del terminal, podemos añadir con seguridad estos códigos a la biblioteca, sin temor a la incompatibilidad de versiones.
Bien, ahora tenemos que hacer que la clase de mensajes de la biblioteca sea capaz de referirse a estas matrices al procesar el código de error.
Para ello, en el archivo \MQL5\Include\DoEasy\Services\Message.mqh, añadimos la comprobación de la correspondencia entre el código y los rangos de valores y asignamos al texto del error de la clase en la variable m_text la línea correspondiente de la matriz necesaria:
//+------------------------------------------------------------------+ //| 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] ); } //+------------------------------------------------------------------+
Al procesar los códigos de retorno del servidor comercial, aumentaremos el límite superior del rango de códigos en 1, ya que hemos obtenido un nuevo mensaje de error con el código 10046, y también deberá incluirse en el rango de códigos de error que se están procesando.
Ahora nuestra biblioteca puede manejar correctamente el nuevo servidor comercial y los códigos de error de tiempo de ejecución. Vamos a añadir nuevas propiedades al objeto de transacción. Tenemos una nueva propiedad "Pago por la transacción" y dos propiedades StopLoss y TakeProfit, que ahora son inherentes a las transacciones; también las añadiremos a las propiedades de la transacción.
En el archivo \MQL5\Include\DoEasy\Defines.mqh, añadimos una nueva propiedad a la enumeración de las propiedades de tipo real (en la biblioteca tenemos la orden abstracta, y las otras entidades se heredan de ella: tanto las transacciones como las posiciones):
//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+
Vamos a aumentar el número total de propiedades de tipo real de 11 a 12.
Asimismo, añadimos la clasificación según la nueva propiedad a la enumeración de posibles criterios de clasificación:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
Ahora podemos ordenar, filtrar y seleccionar transacciones según esta nueva propiedad.
En la lista de posibles estados del ratón en relación con el formulario, corregimos ligeramente los nombres de las constantes de enumeración ENUM_MOUSE_FORM_STATE para que sus nombres indiquen con mayor precisión el estado del ratón en relación con el formulario:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
Vamos a mejorar la clase de orden abstracta en el archivo \MQL5\Include\DoEasy\Objects\Orders\Order.mqh.
En la sección protegida de la clase, declaramos un método que lee de las propiedades de la transacción el valor de su propiedad DEAL_FEE y retorna el valor leído:
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:
En el bloque de métodos de acceso simplificado a las propiedades del objeto de orden, en la parte pública de la clase, añadimos un método que retorna el valor de la propiedad DEAL_FEE registrada en las propiedades del objeto:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
En el constructor paramétrico privado de la clase, leemos y escribimos el valor de la nueva propiedad en las propiedades del objeto:
//+------------------------------------------------------------------+ //| 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)] = ""; } //+------------------------------------------------------------------+
En el método que retorna el precio de StopLoss, escribimos el valor de StopLoss para la transacción:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
Si el estado de la orden es una transacción, leeremos el valor StopLoss de la transacción según su ticket y retornaremos el resultado.
Haremos exactamente lo mismo en el método que retorna el precio TakeProfit:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
Método que retorna el pago de las transacciones realizadas:
//+------------------------------------------------------------------+ //| Return the deal fee | //+------------------------------------------------------------------+ double COrder::DealFee(void) const { #ifdef __MQL4__ return 0; #else return ::HistoryDealGetDouble(m_ticket,DEAL_FEE); #endif } //+------------------------------------------------------------------+
Si se trata de MQL4, entonces la orden no tendrá tal propiedad: retornaremos cero.
Para MQL5, leeremos el valor requerido de las propiedades de la transacción según su ticket y lo retornaremos.
Vamos a añadir el procesamiento de la nueva propiedad al método que retorna la descripción de la propiedad de tipo real de la transacción:
//+------------------------------------------------------------------+ //| 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) ) : "" ); } //+------------------------------------------------------------------+
Las demás entidades de la orden (operaciones de balance, transacciones y posiciones) se heredan de la clase de orden abstracta. Vamos a mejorar la clase del objeto de transacción en el archivo \MQL5\Include\DoEasy\Objects\Orders\HistoryDeal.mqh.
Aquí, en el método que retorna la bandera de soporte de una propiedad de tipo real por parte del objeto, debemos eliminar las propiedades StopLoss y TakeProfit de la lista de propiedades sin soporte. Ahora, estas dos propiedades se encontarán en la transacción, y deberán tener soporte por parte del objeto de clase:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Ahora el método tiene el aspecto siguiente:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Como en el método se enumeran las propiedades sin soporte, la nueva propiedad de transacción añadida DEAL_FEE no se encontraba de inicio en esta lista, por lo que no será necesario registrarla aquí; será compatible por defecto.
Todas las nuevas propiedades y códigos de error han sido añadidos a la biblioteca, y deberán manejarse correctamente.
Finalmente, vamos a mover los objetos de formulario con el ratón.
Procesamiento independiente del desplazamiento de los objetos de formulario
En primer lugar, vamos a decidir cómo gestionar el evento de movimiento del ratón si presionamos y mantenemos presionado el botón en el formulario. La experiencia en el último artículo nos ha demostrado que es necesario desarrollar un concepto de gestión ligeramente distinto para los eventos del ratón en relación con el formulario. Al probar el asesor experto del artículo anterior, hallamos muchas deficiencias y comportamientos incorrectos cuando el ratón interactuaba con varios objetos de formulario, por lo que aquí necesitamos un enfoque diferente, en lugar del implementado anteriormente.
Numerosos experimentos nos han llevado a concluir que necesitamos crear un sistema de banderas en el manejador de eventos de la clase de colección de elementos gráficos, indicando en cuál de los numerosos objetos de formulario se encuentra actualmente el cursor del ratón, si se ha presionado el botón del mismo y si ya estaba previamente pulsado fuera de todos los formularios presentes en el gráfico. En el objeto de formulario, tenemos una propiedad: la bandera de interacción con el entorno. La usaremos para indicar exactamente el formulario con el que necesitamos trabajar y los eventos que necesitamos procesar.
Es decir, el concepto debería ser así:
- Si el cursor se encuentra fuera de todos los formularios presentes en el gráfico, entonces para este se permitirá el menú contextual, su desplazamiento con el ratón y la herramienta "Retícula", y no deberá ocurrir reacción alguna al cruce del cursor con los formularios (cuando el botón del ratón se mantiene fuera de los formularios): moveremos el gráfico con el ratón.
- Tan pronto como el cursor del ratón se coloque sobre cualquier objeto de formulario (al soltar el botón), desactivaremos todas las herramientas de los gráficos y esperaremos que el botón del ratón se mantenga presionado en el formulario, o bien otra interacción del ratón con dicho formulario (el giro de la ruleta del ratón, por ejemplo, o la visualización del desplazamiento del cursor sobre el formulario en algunos efectos visuales).
- Si presionamos el botón del ratón en el formulario, se establecerá la bandera de interacción y movimiento para el formulario. Usaremos el indicador de interacción más adelante para determinar cuál de los dos formularios elegir, siempre que estos dos formularios estén superpuestos, el cursor del ratón se encuentre sobre ellos y luego se mantenga presionado el botón del ratón. Deberemos seleccionar el formulario que tenga el indicador de interacción activo.
Si, después de mover el formulario con el ratón, soltamos el botón, se activará el permiso de uso de su instrumental para el gráfico y se dejará marcada la bandera de interacción para el formulario. Por lo tanto, es este formulario el que se seleccionará de los dos si se superponen nuevamente y se suelta el botón del ratón. O bien, si seleccionamos un formulario actualmente inactivo con el cursor (no el que se estaba moviendo actualmente) y comenzamos a moverlo, la bandera de interacción se eliminará del primer formulario y se configurará para el formulario captado por el ratón.
Este sistema de banderas nos permitirá saber siempre qué formulario ha sido el último activo, sobre cuál de ellos simplemente se ha pasado el cursor y si podemos seleccionarlo visualmente de alguna forma (crearemos estos manejadores más adelante, no en la clase de colección de elementos gráficos, sino en la clase de objeto de formulario). Siempre podremos interactuar con el formulario sobre el que se encuentra el cursor en cualquier momento, y desplazar el último formulario seleccionado con el ratón.
Todo lo que hemos creado en el manejador de eventos de la clase de objeto de formulario, deberá ser eliminado (salvo para ajustar las coordenadas del formulario en el evento de cambio de gráfico). Abrimos el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh y eliminamos el código innecesario del manejador, dejando solo el ajuste del desplazamiento de las coordenadas verticales:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
También hemos corregido los nombres de las constantes enum, corregidas anteriormente para que tengan una mayor carga semántica:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Eso es todo. Ahora necesitamos crear un sistema de banderas en el manejador de eventos de la clase de colección de elementos gráficos.
Abrimos el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh y hacemos las modificaciones pertinentes.
En la sección privada de la clase, declaramos el objeto de clase "Mouse States" y dos métodos: uno para retornar el puntero al formulario debajo del cursor y otro para restablecer las banderas de interacción para todos los formularios salvo el especificado:
//+------------------------------------------------------------------+ //| 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:
Método que retorna el puntero al formulario debajo del cursor:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
El método se comenta con todo detalle directamente en el código. En resumen, necesitamos saber en qué objeto de formulario se encuentra el cursor del ratón. Si simplemente miramos las coordenadas del cursor y las comparamos con las coordenadas y dimensiones del formulario, entonces, por supuesto, encontraremos el formulario sobre el que se halla el cursor. Pero aquí surge un momento desagradable: si dos formularios se superponen, entonces se seleccionará el formulario que esté primero en la lista, y no el que se encuentre sobre todos en el gráfico. Y eso no es correcto. Por consiguiente, al presionar el botón del ratón en el formulario, estableceremos el indicador de interacción para el formulario y lo usaremos para buscar este formulario activo. Si no existe tal formulario, solo en este caso, comenzaremos a buscar cualquier formulario en la lista sobre el que se encuentre el cursor del ratón. Este enfoque nos ofrecerá el comportamiento correcto al trabajar con el ratón: siempre se seleccionará el formulario que ha estado activo por última vez, y este formulario será el que se encuentre sobre el resto en el gráfico, es decir, en primer plano, porque cuando se seleccione, pasará inmediatamente al primer plano.
Método que restablece las banderas de interacción para todos los formularios salvo el especificado:
//+-----------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+
El método es necesario para que siempre tengamos un solo formulario con la bandera de interacción alzada. Al seleccionar un formulario con el ratón, estableceremos inmediatamente el indicador de interacción para él, mientras que para los otros formularios, este indicador deberá borrarse, lo cual sucederá al llamar a este método.
En el manejador de eventos de la clase de colección de elementos gráficos, debemos llamar a los manejadores de eventos para las interacciones entre el ratón y los formularios para cualquier evento, salvo el evento de cambio de gráfico. Para ello es muy adecuada la sección de código que sigue inmediatamente a la verificación del evento de cambio de gráfico: solo necesitaremos reaccionar a todos los eventos excepto a este. Ahora, vamos a colocar en el lugar correcto el bloque de manejadores para la interacción entre el ratón y los formularios:
//+------------------------------------------------------------------+ //| 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) { } } } } } //+------------------------------------------------------------------+
Aquí, comentamos con detalle el bloque completo del manejador de eventos de interacción entre el ratón y el formulario. Para algunos eventos, de momento solo hay "stubs": en estos bloques registraremos en lo sucesivo la llamada de estos manejadores de eventos en el objeto de formulario.
Ya tenemos todo listo para poner a prueba este nuevo concepto.
Simulación
Para la simulación, vamos a tomar el asesor del artículo anterior y a guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part97\ con el nuevo nombre TestDoEasyPart97.mq5.
No vamos a realizar prácticamente ningún cambio. Solo corregiremos las coordenadas de los formularios elaborados; además, para crear estos, usaremos la macrosustitución creada anteriormente indicando el número de objetos de formulario producidos:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Compilamos el asesor y lo ejecutamos en el gráfico:
Ahora nos hemos librado de todas las deficiencias mencionadas tras probar al asesor del último artículo. Además, el desplazamiento de los formularios está limitado por los bordes del gráfico. Cuando un formulario se superpone a otro, siempre se selecciona el formulario deseado y siempre se calculan correctamente los desplazamientos de las coordenadas del cursor en relación con las coordenadas del formulario desplazado.
¿Qué es lo próximo?
En el próximo artículo, continuaremos desarrollando los objetos gráficos de la biblioteca.
*Artículos de esta serie:
Gráficos en la biblioteca DoEasy (Parte 93): Preparando la funcionalidad para crear objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 94): Objetos gráficos compuestos, desplazamiento y eliminación
Gráficos en la biblioteca DoEasy (Parte 95): Elementos de control de los objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 96): Trabajando con los eventos del ratón y los gráficos en los objetos de formulario
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10482





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Aparte del autor que estampa estos artículos como una impresora, ¿quién los necesita?
Lo necesito.