Graphics in DoEasy library (Part 97): Independent handling of form object movement
Contents
Concept
While developing extended graphical objects, I faced the necessity to get back to form objects — graphical objects on canvas to be implemented into extended graphical objects as control junctions for managing graphical object anchor points. In the previous article, I started the development of mouse events for form objects. Here I will finish my work on handling form object movement. We should be able to move any form on the chart. The form to be selected should be the one dragged by the mouse cursor and all chart tools should be correctly enabled and disabled depending on the object in due time.
Let's arrange tracking mouse events so that we are able to use the newly developed handlers for implementing all remaining interactions of a form object with the mouse. Apart from implementing dragging the forms by the mouse cursor and preparing "stubs" for other interaction events, I will add texts for the trade server return codes and execution error codes. Besides, I will add new properties for deal objects — StopLoss and TakeProfit levels present in the deal properties for some time now.
Improving library classes
In \MQL5\Include\DoEasy\Data.mqh, add the new message index:
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
and the text message corresponding to the newly added index:
{"Цена 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
In the same file, complement the error message array:
//+---------------------------------------------------------------------+ //| 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" }, }; //+------------------------------------------------------------------+
and add the new arrays — with the new execution error messages that were previously absent in the library but have already been added to 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__
These are new error messages introduced to MQL5 after the library creation started. It was not possible to add them to the library since they were not present in the language. Now that the error codes have been implemented and several terminal versions have already been released, I finally can add these error codes to the library with no risk of version incompatibility.
Now it is time to let the library message class to be able to refer to these classes when handling an error code.
To achieve this, in \MQL5\Include\DoEasy\Services\Message.mqh, add the code verification in terms of compliance with the value ranges and assign the string from the necessary array, corresponding to the error code, to the class error text in the m_text variable:
//+------------------------------------------------------------------+ //| 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] ); } //+------------------------------------------------------------------+
When handling trade server return codes, increase the upper border of the code range by 1, since we now have a new message with the code 10046, which should also be included into the range of handled error codes.
Now the library is able to correctly handle new trade server and runtime error codes. Let's add new properties to the deal object. Now we have the new "Deal fee" property, while StopLoss and TakeProfit properties are now inherent in deals as well. Let's add them to the deal properties as well.
In \MQL5\Include\DoEasy\Defines.mqh, add the new property to the enumeration of order real properties (the library contains the abstract order, while all other content, namely deals and positions, is derived from it):
//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+
Increase the total number of real properties from 11 to 12.
Add sorting by the new property to the enumeration of possible order and deal sorting criteria:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
Now we are able to sort deals by the new property.
In the list of possible mouse states relative to the form, slightly adjust the constant names of the ENUM_MOUSE_FORM_STATE enumeration so that their names indicate the mouse status relative to the form more accurately:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
Improve the abstract order class in \MQL5\Include\DoEasy\Objects\Orders\Order.mqh.
In the protected class section, declare the method reading the DEAL_FEE property value from the deal properties and returning that property value:
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:
In the block of methods for simplified access to the order object properties in the public section of the class, add the method returning the DEAL_FEE property value set in the object properties:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
Read and set the new property value to the object properties in the closed parametric class constructor:
//+------------------------------------------------------------------+ //| 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)] = ""; } //+------------------------------------------------------------------+
In the method returning the StopLoss price, write the StopLoss value for the deal:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
If the order status is deal, read the StopLoss value of the deal by its ticket and return the result.
Do the same in the method returning TakeProfit price:
//+------------------------------------------------------------------+ //| 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 } //+------------------------------------------------------------------+
The method returning a deal fee:
//+------------------------------------------------------------------+ //| Return the deal fee | //+------------------------------------------------------------------+ double COrder::DealFee(void) const { #ifdef __MQL4__ return 0; #else return ::HistoryDealGetDouble(m_ticket,DEAL_FEE); #endif } //+------------------------------------------------------------------+
If this is MQL4, the order has no such property — return zero.
In case of MQL5, read the required value from the deal properties by its ticket and return it.
Add handling a new property to the method returning the description of the order real property:
//+------------------------------------------------------------------+ //| 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) ) : "" ); } //+------------------------------------------------------------------+
All other order elements, including balance operations, deals and positions, are derived from the abstract order class. Let's improve the deal object class in \MQL5\Include\DoEasy\Objects\Orders\HistoryDeal.mqh.
Here we need to remove the StopLoss and TakeProfit properties from the list of unsupported properties in the method returning the flag indicating the object support for the real property — now the deal features these two properties and they should be supported by the class object:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Now the method looks as follows:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Since the method enumerates unsupported properties, the newly added DEAL_FEE property is initially absent from the list meaning we do not need to set it here as it will be supported by default.
All new properties and error codes are added to the library and should be handled correctly.
Now let's move on to dragging form object with the mouse.
Independent handling of form object movement
First of all, we should decide on how to handle the mouse movement event if the button is clicked and held on the form. Considering the previous article experience, we need to develop a slightly different concept of handling mouse events relative to the form. When testing the EA from the previous article I detected multiple shortcomings and incorrect behavior in the mouse interaction with multiple form objects. Therefore, we need another approach.
Multiple experiments proved that the system of flags should be created in the handler of graphical element collection class events. These flags are to indicate a form object the mouse cursor is currently located at, whether the mouse button is pressed at the moment and whether it was pressed outside all forms present on the chart. The form object features the flag of interaction with the environment. Use it to specify the form you want to work with and whose events you want to handle.
In other words, the concept should be as follows:
- If the cursor is outside all forms present on the chart, the context menu is allowed for the chart, as well as dragging it with the mouse and using the Crosshair tool, while there should be no response when the cursor touches the forms (with the mouse button pressed outside the forms) as we drag the chart with the mouse.
- As soon as the mouse cursor hovers over any form object (with the button released), disable all chart tools and wait for the mouse button being pressed on the form or for any other interaction of the mouse with the form (for example, mouse wheel scrolling or visualization of the cursor hovering above the form as several visual form effects).
- If the mouse button is pressed on the form, the interaction and movement flag is set for the form. The interaction flag is then to be used for defining which of the two forms should be selected provided that these two forms are superimposed, the mouse cursor hovers over them and the mouse button is pressed afterwards. The form with the active interaction flag should be selected.
If we release the button after dragging the form with the mouse, the chart tools are enabled while the form interaction flag remains active. Thus, this form is selected out of the two if they are superimposed again and the mouse button is released. If the cursor selects a currently inactive form (not the one that has just been dragged) and starts dragging it, the interaction flag is removed from the first form and activated for the selected one.
Such flag system will always let us know which form was last active, which form has the cursor hovering above it and whether it can be visually highlighted (these handlers will be implemented later in the form object class rather than in the graphical element collection class). We will always be able to interact with a form featuring the cursor above it and drag a form which was last selected by the mouse.
We need to remove everything that was created in the form object class events handler (except for adjusting form coordinates in case of a chart change event). Open \MQL5\Include\DoEasy\Objects\Graph\Form.mqh and remove the redundant code from the handler leaving the adjustment of the vertical coordinate shift:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Besides, I have corrected the enumeration constant names to make them more comprehensible:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
All is set. Now we need to create the flag system in the graphical element collection class event handler.
Open \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh and make the necessary improvements.
In the private section of the class, declare the "Mouse status" class object and two methods — the one returning the pointer to the form located under the cursor and the one resetting all interaction flags for all forms except the specified one:
//+------------------------------------------------------------------+ //| 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:
The method returning the pointer to the form located under the 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; } //+------------------------------------------------------------------+
The method is commented in detail in the code. In brief, we need to know which form object the mouse cursor is located at. If we simply have a look at the cursor coordinates and compare them with the form coordinates and size, we will surely find the form, above which the cursor is located. However, here we face an issue: if two forms are superimposed, the form coming first in the list is selected, rather than the one located above all others on the chart, which is wrong. Therefore, clicking the mouse button on the form sets the appropriate interaction flag used to find the active form. Only if such form is absent, we start searching any listed form, above which the mouse cursor is located. Such an approach provides us with the correct behavior when handling the mouse. The form that was last active is always selected. This is the form located above all others on the chart — i.e. on the foreground, since the form is moved to the foreground immediately after its selection.
The method resetting interaction flags for all forms except the specified one:
//+--------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+
With this method, we will always have only one form with the active interaction flag at a time. When selecting a form with the mouse, we immediately set the interaction flag for it. For all other forms, the flag should be disabled. This is done when calling the method.
We should call the handlers of events of mouse interaction with the forms in the handler of graphical element collection class events in case of any events except for the chart change event. The code segment immediately following the chart change event check is the most suitable for that as we need to respond to all events except for this one. Let's place the block of handlers of mouse interaction with the forms where it is necessary:
//+------------------------------------------------------------------+ //| 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) { } } } } } //+------------------------------------------------------------------+
The entire code block of the 'mouse interacting with the form' event handler is accompanied by detailed comments. Currently, some events have only "stubs" — these blocks are to contain calling event handlers in a form object.
At the moment, all is ready for testing the new concept.
Test
To perform the test, let's use the EA from the previous article and save it to \MQL5\Experts\TestDoEasy\Part97\ as TestDoEasyPart97.mq5.
There will be almost no changes. I will only adjust the coordinates of created forms. I will use the previously created macro substitution specifying the number of created form objects:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Compile the EA and launch it on the chart:
I have eliminated all drawbacks mentioned after testing the EA from the previous article. Besides, moving forms is limited by the chart borders. When one form is superimposed on another, the necessary form is always selected and the cursor coordinate shifts relative to the moved form coordinates are always calculated correctly.
What's next?
In the next article, I will continue the development of library graphical objects.
*Previous articles within the series:
Graphics in DoEasy library (Part 93): Preparing functionality for creating composite graphical objects
Graphics in DoEasy library (Part 94): Moving and deleting composite graphical objects
Graphics in DoEasy library (Part 95): Composite graphical object controls
Graphics in DoEasy library (Part 96): Graphics in form objects and handling mouse events
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/10482
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Apart from the author stamping out these articles like a printer, who needs them?
I need.
Thanks for this useful article. Very informative.