Графика в библиотеке DoEasy (Часть 97): Независимая обработка перемещения объектов-форм
Содержание
- Концепция
- Доработка классов библиотеки
- Независимая обработка перемещения объектов-форм
- Тестирование
- Что дальше
Концепция
Разрабатывая расширенные графические объекты, мы столкнулись с необходимостью вернуться к объектам-формам — графическим объектам на канвасе, которые будут присутствовать у расширенных графических объектов в качестве узлов управления точками привязки графического объекта. В прошлой статье начали создавать обработку событий мышки для объектов-форм. Сегодня завершим обработку перемещения объекта-формы. Причём сделаем так, чтобы мы могли перемещать любую форму по графику, и при этом форма будет выбираться именно та, которая захвачена курсором, и все инструменты графика будут правильно и в нужное время включаться или выключаться в зависимости от события.
Организуем отслеживание событий мышки таким образом, чтобы впоследствии мы смогли воспользоваться заготовленными сегодня обработчиками для реализации всех остальных взаимодействий объекта-формы с мышкой. Помимо реализации перемещения форм курсором мышки и подготовки "заглушек" для реализации иных событий взаимодействия формы с мышкой, сегодня мы дополним тексты кодов возврата торгового сервера и коды ошибок выполнения, которые появились в MQL5 после начала разработки библиотеки, но ещё не были добавлены в неё, а также добавим новые свойства для объектов-сделок — уровни StopLoss и TakeProfit, которые с некоторых пор присутствуют в свойствах сделки.
Доработка классов библиотеки
В файле \MQL5\Include\DoEasy\Data.mqh впишем индекс нового сообщения:
MSG_LIB_PROP_BID, // Цена Bid MSG_LIB_PROP_ASK, // Цена Ask MSG_LIB_PROP_LAST, // Цена последней сделки MSG_LIB_PROP_PRICE_SL, // Цена StopLoss MSG_LIB_PROP_PRICE_TP, // Цена TakeProfit MSG_LIB_PROP_DEAL_FEE, // Оплата за проведение сделки MSG_LIB_PROP_PROFIT, // Профит MSG_LIB_PROP_SYMBOL, // Символ MSG_LIB_PROP_BALANCE, // Балансовая операция MSG_LIB_PROP_CREDIT, // Кредитная операция MSG_LIB_PROP_CLOSE_BY_SL, // Закрытие по StopLoss MSG_LIB_PROP_CLOSE_BY_TP, // Закрытие по TakeProfit MSG_LIB_PROP_ACCOUNT, // Счёт //--- COrder
и текстовое сообщение, соответствующее вновь добавленному индексу:
{"Цена Bid","Price Bid"}, {"Цена Ask","Price Ask"}, {"Цена Last","Price Last"}, {"Цена StopLoss","Price StopLoss"}, {"Цена TakeProfit","Price TakeProfit"}, {"Оплата за проведение сделки","Fee for making a deal"}, {"Прибыль","Profit"}, {"Символ","Symbol"}, {"Балансовая операция","Balance operation"}, {"Кредитная операция","Credit operation"}, {"Закрытие по StopLoss","Close by StopLoss"}, {"Закрытие по TakeProfit","Close by TakeProfit"}, {"Счёт","Account"}, //--- COrder
Здесь же, в этом же файле, дополним массив сообщений об ошибках:
//+---------------------------------------------------------------------+ //| Массив сообщений кодов возвратов торгового сервера (10004 - 10045) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+---------------------------------------------------------------------+ 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 the request was completed"}, // 10010 {"Ошибка обработки запроса","Request processing error"}, // 10011 {"Запрос отменен по истечению времени","Request canceled by timeout"}, // 10012 {"Неправильный запрос","Invalid request"}, // 10013 {"Неправильный объем в запросе","Invalid volume in the request"}, // 10014 {"Неправильная цена в запросе","Invalid price in the request"}, // 10015 {"Неправильные стопы в запросе","Invalid stops in the request"}, // 10016 {"Торговля запрещена","Trade is disabled"}, // 10017 {"Рынок закрыт","Market is closed"}, // 10018 {"Нет достаточных денежных средств для выполнения запроса","There is not enough money to complete the request"}, // 10019 {"Цены изменились","Prices changed"}, // 10020 {"Отсутствуют котировки для обработки запроса","There are no quotes to process the request"}, // 10021 {"Неверная дата истечения ордера в запросе","Invalid order expiration date in the 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 the trade server"}, // 10031 {"Операция разрешена только для реальных счетов","Operation is allowed only for live accounts"}, // 10032 {"Достигнут лимит на количество отложенных ордеров","The number of pending orders has reached the limit"}, // 10033 {"Достигнут лимит на объем ордеров и позиций для данного символа","The volume of orders and positions for the symbol has reached the limit"}, // 10034 {"Неверный или запрещённый тип ордера","Incorrect or prohibited order type"}, // 10035 {"Позиция с указанным идентификатором уже закрыта","Position with the specified identifier has already been closed"}, // 10036 {"Неизвестный код возврата торгового сервера","Unknown trading server return code"}, // 10037 {"Закрываемый объем превышает текущий объем позиции","A close volume exceeds the current position volume"}, // 10038 {"Для указанной позиции уже есть ордер на закрытие","A close order already exists for a specified position"}, // 10039 {"Достигнут лимит на количество открытых позиций","The number of positions has reached the limit"}, // 10040 { "Запрос на активацию отложенного ордера отклонен, а сам ордер отменен", // 10041 "The pending order activation request is rejected, the order is canceled" }, { "Запрос отклонен, так как на символе установлено правило \"Разрешены только длинные позиции\"", // 10042 "The request is rejected, because the \"Only long positions are allowed\" rule is set for the symbol" }, { "Запрос отклонен, так как на символе установлено правило \"Разрешены только короткие позиции\"", // 10043 "The request is rejected, because the \"Only short positions are allowed\" rule is set for the symbol" }, { "Запрос отклонен, так как на символе установлено правило \"Разрешено только закрывать существующие позиции\"", // 10044 "The request is rejected, because the \"Only position closing is allowed\" rule is set for the symbol" }, { "Запрос отклонен, так как для торгового счета установлено правило \"Разрешено закрывать существующие позиции только по правилу FIFO\"", // 10045 "The request is rejected, because \"Position closing is allowed only by FIFO rule\" flag is set for the trading account" }, { "Запрос отклонен, так как для торгового счета установлено правило \"Запрещено открывать встречные позиции по одному символу\"", // 10046 "The request is rejected, because the \"Opposite positions on a single symbol are disabled\" rule is set for the trading account" }, }; //+------------------------------------------------------------------+
и впишем новые массивы — с новыми сообщениями об ошибках выполнения, которые ранее отсутствовали в библиотеке, но уже добавлены в MQL5:
//+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5100 - 5114) | //| (Работа с OpenCL) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ string messages_runtime_opencl[][TOTAL_LANG]= { {"Функции OpenCL на данном компьютере не поддерживаются","OpenCL functions are 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 a run queue in OpenCL"}, // 5104 {"Ошибка при компиляции программы OpenCL","Error occurred when compiling an OpenCL program"}, // 5105 {"Слишком длинное имя точки входа (кернел OpenCL)","Too long kernel name (OpenCL kernel)"}, // 5106 {"Ошибка создания кернел - точки входа OpenCL","Error creating an 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 the OpenCL buffer"}, // 5110 {"Неверное смещение в буфере OpenCL","Invalid offset in the OpenCL buffer"}, // 5111 {"Ошибка создания буфера OpenCL","Failed to create an OpenCL buffer"}, // 5112 {"Превышено максимальное число OpenCL объектов","Too many OpenCL objects"}, // 5113 {"Ошибка выбора OpenCL устройства","OpenCL device selection error"}, // 5114 }; //+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5120 - 5130) | //| (Работа с базами данных) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ 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 }; //+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5200 - 5203) | //| (Работа с WebRequest()) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ 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 }; //+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5270 - 5275) | //| (Работа с сетью (сокетами)) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ 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 the connection"}, // 5275 }; //+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5300 - 5310) | //| (Пользовательские символы) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ string messages_runtime_custom_symbol[][TOTAL_LANG]= { {"Должен быть указан пользовательский символ","A custom symbol must be specified"}, // 5300 {"Некорректное имя пользовательского символа","The name of the custom symbol is invalid"}, // 5301 {"Слишком длинное имя для пользовательского символа","The name of the custom symbol is too long"}, // 5302 {"Слишком длинный путь для пользовательского символа","The path of the custom symbol is too long"}, // 5303 {"Пользовательский символ с таким именем уже существует","A 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 a custom symbol selected in Market Watch"}, // 5306 {"Неправильное свойство пользовательского символа","An invalid custom symbol property"}, // 5307 {"Ошибочный параметр при установке свойства пользовательского символа","A wrong parameter while setting the property of a custom symbol"}, // 5308 { "Слишком длинный строковый параметр при установке свойства пользовательского символа", // 5309 "A too long string parameter while setting the property of a custom symbol" }, {"Не упорядоченный по времени массив тиков","Ticks in the array are not arranged in the order of time"}, // 5310 }; //+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5400 - 5402) | //| (Экономический календарь) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ string messages_runtime_calendar[][TOTAL_LANG]= { {"Размер массива недостаточен для получения описаний всех значений","Array size is insufficient for receiving descriptions of all values"}, // 5400 {"Превышен лимит запроса по времени","Request time limit exceeded"}, // 5401 {"Страна не найдена","Country is not found"}, // 5402 }; //+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (5601 - 5626) | //| (Работа с базами данных) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ string messages_runtime_sqlite[][TOTAL_LANG]= { {"Общая ошибка","Generic error"}, // 5601 {"Внутренняя логическая ошибка в SQLite","SQLite internal logic error"}, // 5602 {"Отказано в доступе","Access denied"}, // 5603 {"Процедура обратного вызова запросила прерывание","Callback routine requested abort"}, // 5604 {"Файл базы данных заблокирован","Database file locked"}, // 5605 {"Таблица в базе данных заблокирована ","Database table locked"}, // 5606 {"Сбой malloc()","Insufficient memory for completing operation"}, // 5607 {"Попытка записи в базу данных, доступной только для чтения ","Attempt to write to readonly database"}, // 5608 {"Операция прекращена с помощью sqlite3_interrupt() ","Operation terminated by sqlite3_interrupt()"}, // 5609 {"Ошибка дискового ввода-вывода","Disk I/O error"}, // 5610 {"Образ диска базы данных испорчен","Database disk image corrupted"}, // 5611 {"Неизвестный код операции в sqlite3_file_control()","Unknown operation code in sqlite3_file_control()"}, // 5612 {"Ошибка вставки, так как база данных заполнена ","Insertion failed because database is full"}, // 5613 {"Невозможно открыть файл базы данных","Unable to open the database file"}, // 5614 {"Ошибка протокола блокировки базы данных ","Database lock protocol error"}, // 5615 {"Только для внутреннего использования","Internal use only"}, // 5616 {"Схема базы данных изменена","Database schema changed"}, // 5617 {"Строка или BLOB превышает ограничение по размеру","String or BLOB exceeds size limit"}, // 5618 {"Прервано из-за нарушения ограничения","Abort due to constraint violation"}, // 5619 {"Несоответствие типов данных","Data type mismatch"}, // 5620 {"Ошибка неправильного использования библиотеки","Library used incorrectly"}, // 5621 {"Использование функций операционной системы, не поддерживаемых на хосте","Uses OS features not supported on host"}, // 5622 {"Отказано в авторизации","Authorization denied"}, // 5623 {"Не используется ","Not used "}, // 5624 {"2-й параметр для sqlite3_bind находится вне диапазона","Bind parameter error, incorrect index"}, // 5625 {"Открытый файл не является файлом базы данных","File opened that is not database file"}, // 5626 }; //+------------------------------------------------------------------+ #ifdef __MQL4__
Это новые сообщения об ошибках, которые появились в MQL5 уже после начала создания библиотеки, и которые изначально не могли быть в неё добавлены по причине их отсутствия в языке. Теперь же, спустя некоторое количество времени после введения этих кодов ошибок, и после выхода нескольких релизных версий терминала, мы можем спокойно добавить эти коды ошибок в библиотеку, не боясь несовместимости версий.
Теперь нам нужно сделать так, чтобы класс сообщений библиотеки мог ссылаться на эти массивы при обработке кода ошибки.
Для этого в файле \MQL5\Include\DoEasy\Services\Message.mqh добавим проверку на соответствие кода диапазонам значений и присвоим тексту ошибки класса в переменной m_text соответствующую коду ошибки строку из нужного массива:
//+------------------------------------------------------------------+ //| Получение сообщения из массива текстов по идентификатору | //+------------------------------------------------------------------+ void CMessage::GetTextByID(const int msg_id) { CMessage::m_text= ( //--- Ошибки времени выполнения (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] : //--- Ошибки времени выполнения (Графики 4101 - 4116) msg_id>4100 && msg_id<4117 ? messages_runtime_charts[msg_id-4101][m_lang_num] : //--- Ошибки времени выполнения (Графические объекты 4201 - 4205) msg_id>4200 && msg_id<4206 ? messages_runtime_graph_obj[msg_id-4201][m_lang_num] : //--- Ошибки времени выполнения (MarketInfo 4301 - 4305) msg_id>4300 && msg_id<4306 ? messages_runtime_market[msg_id-4301][m_lang_num] : //--- Ошибки времени выполнения (Доступ к истории 4401 - 4407) msg_id>4400 && msg_id<4408 ? messages_runtime_history[msg_id-4401][m_lang_num] : //--- Ошибки времени выполнения (Global Variables 4501 - 4524) msg_id>4500 && msg_id<4525 ? messages_runtime_global[msg_id-4501][m_lang_num] : //--- Ошибки времени выполнения (Пользовательские индикаторы 4601 - 4603) msg_id>4600 && msg_id<4604 ? messages_runtime_custom_indicator[msg_id-4601][m_lang_num] : //--- Ошибки времени выполнения (Account 4701 - 4758) msg_id>4700 && msg_id<4759 ? messages_runtime_account[msg_id-4701][m_lang_num] : //--- Ошибки времени выполнения (Индикаторы 4801 - 4812) msg_id>4800 && msg_id<4813 ? messages_runtime_indicator[msg_id-4801][m_lang_num] : //--- Ошибки времени выполнения (Стакан цен 4901 - 4904) msg_id>4900 && msg_id<4905 ? messages_runtime_books[msg_id-4901][m_lang_num] : //--- Ошибки времени выполнения (Файловые операции 5001 - 5027) msg_id>5000 && msg_id<5028 ? messages_runtime_files[msg_id-5001][m_lang_num] : //--- Ошибки времени выполнения (Преобразование строк 5030 - 5044) msg_id>5029 && msg_id<5045 ? messages_runtime_string[msg_id-5030][m_lang_num] : //--- Ошибки времени выполнения (Работа с массивами 5050 - 5063) msg_id>5049 && msg_id<5064 ? messages_runtime_array[msg_id-5050][m_lang_num] : //--- Ошибки времени выполнения (Работа с OpenCL 5100 - 5114) msg_id>5099 && msg_id<5115 ? messages_runtime_opencl[msg_id-5100][m_lang_num] : //--- Ошибки времени выполнения (Работа с базами данных 5120 - 5130) msg_id>5119 && msg_id<5131 ? messages_runtime_database[msg_id-5120][m_lang_num] : //--- Ошибки времени выполнения (Работа с WebRequest() 5200 - 5203) msg_id>5199 && msg_id<5204 ? messages_runtime_webrequest[msg_id-5200][m_lang_num] : //--- Ошибки времени выполнения (Работа с сетью (сокетами) 5270 - 5275) msg_id>5269 && msg_id<5276 ? messages_runtime_netsocket[msg_id-5270][m_lang_num] : //--- Ошибки времени выполнения (Пользовательские символы 5300 - 5310) msg_id>5299 && msg_id<5311 ? messages_runtime_custom_symbol[msg_id-5300][m_lang_num] : //--- Ошибки времени выполнения (Экономический календарь 5400 - 5402) msg_id>5399 && msg_id<5403 ? messages_runtime_calendar[msg_id-5400][m_lang_num] : //--- Ошибки времени выполнения (Работа с базами данных 5601 - 5626) msg_id>5600 && msg_id<5627 ? messages_runtime_sqlite[msg_id-5601][m_lang_num] : //--- Коды возврата торгового сервера (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 (4000 - 4030) msg_id<4031 ? messages_runtime_4000_4030[msg_id-4000][m_lang_num] : //--- Ошибки времени выполнения MQL4 (4050 - 4075) msg_id>4049 && msg_id<4076 ? messages_runtime_4050_4075[msg_id-4050][m_lang_num] : //--- Ошибки времени выполнения MQL4 (4099 - 4112) msg_id>4098 && msg_id<4113 ? messages_runtime_4099_4112[msg_id-4099][m_lang_num] : //--- Ошибки времени выполнения MQL4 (4200 - 4220) msg_id>4199 && msg_id<4221 ? messages_runtime_4200_4220[msg_id-4200][m_lang_num] : //--- Ошибки времени выполнения MQL4 (4250 - 4266) msg_id>4249 && msg_id<4267 ? messages_runtime_4250_4266[msg_id-4250][m_lang_num] : //--- Ошибки времени выполнения MQL4 (5001 - 5029) msg_id>5000 && msg_id<5030 ? messages_runtime_5001_5029[msg_id-5001][m_lang_num] : //--- Ошибки времени выполнения MQL4 (5200 - 5203) msg_id>5199 && msg_id<5204 ? messages_runtime_5200_5203[msg_id-5200][m_lang_num] : #endif //--- Сообщения библиотеки (ERR_USER_ERROR_FIRST) msg_id>ERR_USER_ERROR_FIRST-1 ? messages_library[msg_id-ERR_USER_ERROR_FIRST][m_lang_num] : messages_library[MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE-ERR_USER_ERROR_FIRST][m_lang_num] ); } //+------------------------------------------------------------------+
При обработке кодов возврата торгового сервера увеличим верхнюю границу диапазона кодов на 1, так как у нас появилось одно новое сообщение об ошибке с кодом 10046, и его тоже необходимо включить в диапазон обрабатываемых кодов ошибок.
Теперь наша библиотека может правильно обрабатывать новые коды ошибок торгового сервера и времени выполнения. Добавим новые свойства объекту-сделке. У нас появилось новое свойство "Оплата за проведение сделки" и два свойства StopLoss и TakeProfit теперь присущи и сделкам — их тоже добавим в свойства сделки.
В файле \MQL5\Include\DoEasy\Defines.mqh впишем новое свойство в перечисление вещественных свойств ордера (в библиотеке у нас есть абстрактный ордер, а от него наследуются уже остальные сущности — сделки и позиции):
//+------------------------------------------------------------------+ //| Вещественные свойства ордера, сделки, позиции | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_DOUBLE { ORDER_PROP_PRICE_OPEN = ORDER_PROP_INTEGER_TOTAL, // Цена открытия (MQL5 цена сделки) ORDER_PROP_PRICE_CLOSE, // Цена закрытия ORDER_PROP_SL, // Цена StopLoss ORDER_PROP_TP, // Цена TaleProfit ORDER_PROP_FEE, // Оплата за проведение сделки (DEAL_FEE из ENUM_DEAL_PROPERTY_DOUBLE) ORDER_PROP_PROFIT, // Профит ORDER_PROP_COMMISSION, // Комиссия ORDER_PROP_SWAP, // Своп ORDER_PROP_VOLUME, // Объём ORDER_PROP_VOLUME_CURRENT, // Невыполненный объем ORDER_PROP_PROFIT_FULL, // Профит+комиссия+своп ORDER_PROP_PRICE_STOP_LIMIT, // Цена постановки Limit ордера при срабатывании StopLimit ордера }; #define ORDER_PROP_DOUBLE_TOTAL (12) // Общее количество вещественных свойств //+------------------------------------------------------------------+
Увеличим общее количество вещественных свойств с 11 до 12.
В перечисление возможных критериев сортировки ордеров и сделок добавим сортировку по новому свойству:
//+------------------------------------------------------------------+ //| Возможные критерии сортировки ордеров и сделок | //+------------------------------------------------------------------+ #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_ORDER_TICKET = 0, // Сортировать по тикету ордера SORT_BY_ORDER_MAGIC, // Сортировать по магику ордера SORT_BY_ORDER_TIME_OPEN, // Сортировать по времени открытия ордера в миллисекундах SORT_BY_ORDER_TIME_CLOSE, // Сортировать по времени закрытия ордера в миллисекундах SORT_BY_ORDER_TIME_EXP, // Сортировать по дате экспирации ордера SORT_BY_ORDER_TYPE_FILLING, // Сортировать по типу исполнения по остатку SORT_BY_ORDER_TYPE_TIME, // Сортировать по времени жизни ордера SORT_BY_ORDER_STATUS, // Сортировать по статусу ордера (маркет-ордер/отложенный ордер/сделка/балансовая,кредитная операция) SORT_BY_ORDER_TYPE, // Сортировать по типу ордера SORT_BY_ORDER_REASON, // Сортировать по причине/источнику сделки/ордера/позиции SORT_BY_ORDER_STATE, // Сортировать по состоянию ордера SORT_BY_ORDER_POSITION_ID, // Сортировать по идентификатору позиции SORT_BY_ORDER_POSITION_BY_ID, // Сортировать по идентификатору встречной позиции SORT_BY_ORDER_DEAL_ORDER, // Сортировать по ордеру, на основание которого выполнена сделка SORT_BY_ORDER_DEAL_ENTRY, // Сортировать по направлению сделки – IN, OUT или IN/OUT SORT_BY_ORDER_TIME_UPDATE, // Сортировать по времени изменения позиции в секундах SORT_BY_ORDER_TICKET_FROM, // Сортировать по тикету родительского ордера SORT_BY_ORDER_TICKET_TO, // Сортировать по тикету дочернего ордера SORT_BY_ORDER_PROFIT_PT, // Сортировать по профиту ордера в пунктах SORT_BY_ORDER_CLOSE_BY_SL, // Сортировать по признаку закрытия ордера по StopLoss SORT_BY_ORDER_CLOSE_BY_TP, // Сортировать по признаку закрытия ордера по TakeProfit SORT_BY_ORDER_MAGIC_ID, // Сортировать по идентификатору "магик" ордера/позиции SORT_BY_ORDER_GROUP_ID1, // Сортировать по идентификатору 1 группы ордеров/позиций SORT_BY_ORDER_GROUP_ID2, // Сортировать по идентификатору 2 группы ордеров/позиций SORT_BY_ORDER_PEND_REQ_ID, // Сортировать по идентификатору отложенного запроса SORT_BY_ORDER_DIRECTION, // Сортировать по направлению (Buy, Sell) //--- Сортировка по вещественным свойствам SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Сортировать по цене открытия SORT_BY_ORDER_PRICE_CLOSE, // Сортировать по цене закрытия SORT_BY_ORDER_SL, // Сортировать по цене StopLoss SORT_BY_ORDER_TP, // Сортировать по цене TaleProfit SORT_BY_ORDER_FEE, // Сортировать по оплате за проведение сделки SORT_BY_ORDER_PROFIT, // Сортировать по профиту SORT_BY_ORDER_COMMISSION, // Сортировать по комиссии SORT_BY_ORDER_SWAP, // Сортировать по свопу SORT_BY_ORDER_VOLUME, // Сортировать по объёму SORT_BY_ORDER_VOLUME_CURRENT, // Сортировать по невыполненному объему SORT_BY_ORDER_PROFIT_FULL, // Сортировать по критерию профит+комиссия+своп SORT_BY_ORDER_PRICE_STOP_LIMIT, // Сортировать по цене постановки Limit ордера при срабатывании StopLimit ордера //--- Сортировка по строковым свойствам SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Сортировать по символу SORT_BY_ORDER_COMMENT, // Сортировать по комментарию SORT_BY_ORDER_COMMENT_EXT, // Сортировать по пользовательскому комментарию SORT_BY_ORDER_EXT_ID // Сортировать по идентификатору ордера во внешней торговой системе }; //+------------------------------------------------------------------+
Теперь мы сможем сделки сортировать, фильтровать и выбирать по этому новому свойству.
В списке возможных состояний мышки относительно формы чуть поправим наименования констант перечисления ENUM_MOUSE_FORM_STATE — чтобы их названия более точно указывали на состояние мышки относительно формы:
//+------------------------------------------------------------------+ //| Список возможных состояний мышки относительно формы | //+------------------------------------------------------------------+ enum ENUM_MOUSE_FORM_STATE { MOUSE_FORM_STATE_NONE = 0, // Неопределённое состояние //--- За пределами формы MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED, // Курсор за пределами формы, кнопки мышки не нажаты MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED, // Курсор за пределами формы, нажата кнопка мышки (любая) MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL, // Курсор за пределами формы, прокручивается колёсико мышки //--- В пределах формы MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED, // Курсор в пределах формы, кнопки мышки не нажаты MOUSE_FORM_STATE_INSIDE_FORM_PRESSED, // Курсор в пределах формы, нажата кнопка мышки (любая) MOUSE_FORM_STATE_INSIDE_FORM_WHEEL, // Курсор в пределах формы, прокручивается колёсико мышки //--- В пределах области заголовка окна MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED, // Курсор в пределах активной области, кнопки мышки не нажаты MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED, // Курсор в пределах активной области, нажата кнопка мышки (любая) MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL, // Курсор в пределах активной области, прокручивается колёсико мышки //--- В пределах области прокрутки окна MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED, // Курсор в пределах области прокрутки окна, кнопки мышки не нажаты MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED, // Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая) MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL, // Курсор в пределах области прокрутки окна, прокручивается колёсико мышки }; //+------------------------------------------------------------------+
Доработаем класс абстрактного ордера в файле \MQL5\Include\DoEasy\Objects\Orders\Order.mqh.
В защищённой секции класса объявим метод, считывающий из свойств сделки значение её свойства DEAL_FEE, и возвращающий считанное значение:
protected: //--- Защищённый параметрический конструктор COrder(ENUM_ORDER_STATUS order_status,const ulong ticket); //--- Получает и возвращает целочисленные свойства выбранного ордера из его параметров 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; //--- Получает и возвращает вещественные свойства выбранного ордера из его параметров: (1) цену открытия, (2) цену закрытия, (3) профит, //--- (4) комиссию, (5) своп, (6) объём, (7) невыполненный объём (8) цену StopLoss, (9) цену TakeProfit (10) цену установки StopLimit-ордера 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; //--- Получает и возвращает строковые свойства выбранного ордера из его параметров: (1) символ, (2) комментарий, (3) идентификатор на бирже string OrderSymbol(void) const; string OrderComment(void) const; string OrderExternalID(void) const; //--- Возвращает описание (1) причины, (2) направления, (3) типа сделки string GetReasonDescription(const long reason) const; string GetEntryDescription(const long deal_entry) const; string GetTypeDealDescription(const long type_deal) const; public:
В блоке методов упрощённого доступа к свойствам объекта-ордера в публичной секции класса добавим метод, возвращающий записанное в свойствах объекта значение свойства DEAL_FEE:
//+------------------------------------------------------------------+ //| Методы упрощённого доступа к свойствам объекта-ордера | //+------------------------------------------------------------------+ //--- Возвращает (1) тикет, (2) тикет родительского ордера, (3) тикет дочернего ордера, (4) магик, (5) причину выставления ордера, //--- (6) идентификатор позиции, (7) идентификатор встречной позиции, (8) идентификатор первой группы, (9) идентификатор второй группы, //--- (10) идентификатор отложенного запроса, (11) идентификатор магического номера, (12) тип, (13) флаг закрытия по StopLoss, //--- (14) флаг закрытия по TakeProfit (15) время открытия, (16) время закрытия, //--- (17) дату экспирации, (18) состояние, (19) статус (20) тип ордера по направлению, (21) тип исполнения по остатку, (22) время жизни ордера 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); } //--- Возвращает (1) цену открытия, (2) цену закрытия, (3) профит, (4) комиссию, (5) своп, (6) объём, //--- (7) невыполненный объём (8) StopLoss и (9) TakeProfit, (10) оплату за проведение сделки, (11) цену установки StopLimit-ордера 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); } //--- Возвращает (1) символ, (2) комментарий, (3) идентификатор на бирже 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); } //--- Возвращает полный профит ордера double ProfitFull(void) const { return this.Profit()+this.Comission()+this.Swap(); } //--- Возвращает профит ордера в пунктах int ProfitInPoints(void) const; //--- Устанавливает (1) идентификатор первой группы, (2) идентификатор второй группы, (3) идентификатор отложенного запроса, (4) пользовательский комментарий 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); } //+------------------------------------------------------------------+ //| Описания свойств объекта-ордера | //+------------------------------------------------------------------+
В закрытом параметрическом конструкторе класса считаем и запишем в свойства объекта значение нового свойства:
//+------------------------------------------------------------------+ //| Закрытый параметрический конструктор | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Устанавливаем объекту его тип this.m_type=OBJECT_DE_TYPE_ORDER_DEAL_POSITION; //--- Сохранение целочисленных свойств 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(); //--- Сохранение вещественных свойств 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(); //--- Сохранение строковых свойств 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(); //--- Сохранение дополнительных целочисленных свойств 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)); //--- Сохранение дополнительных вещественных свойств this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); //--- Сохранение дополнительных строковых свойств this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)] = ""; } //+------------------------------------------------------------------+
В методе, возвращающем цену StopLoss запишем значение StopLoss для сделки:
//+------------------------------------------------------------------+ //| Возвращает цену StopLoss | //+------------------------------------------------------------------+ 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 } //+------------------------------------------------------------------+
Если статус ордера — сделка, то считываем значение StopLoss сделки по её тикету и возвращаем результат.
Точно так же сделаем и в методе, возвращающем цену TakeProfit:
//+------------------------------------------------------------------+ //| Возвращает цену TakeProfit | //+------------------------------------------------------------------+ 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 } //+------------------------------------------------------------------+
Метод, возвращающий оплату за проведение сделки:
//+------------------------------------------------------------------+ //| Возвращает оплату за проведение сделки | //+------------------------------------------------------------------+ double COrder::DealFee(void) const { #ifdef __MQL4__ return 0; #else return ::HistoryDealGetDouble(m_ticket,DEAL_FEE); #endif } //+------------------------------------------------------------------+
Если это MQL4, то там нет такого свойства у ордера — возвращаем ноль.
Для MQL5 считываем требуемое значение из свойств сделки по её тикету и возвращаем его.
Впишем обработку нового свойства в метод, возвращающий описание вещественного свойства ордера:
//+------------------------------------------------------------------+ //| Возвращает описание вещественного свойства ордера | //+------------------------------------------------------------------+ 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 ( //--- Общие свойства 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) ) : //--- Дополнительное свойство property==ORDER_PROP_PROFIT_FULL ? CMessage::Text(MSG_ORD_PROFIT_FULL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : "" ); } //+------------------------------------------------------------------+
От класса абстрактного ордера наследуются все остальные ордерные сущности — балансовые операции, сделки и позиции. Доработаем класс объекта-сделки в файле \MQL5\Include\DoEasy\Objects\Orders\HistoryDeal.mqh.
Здесь нам нужно в методе, возвращающем флаг поддержания объектом вещественного свойства убрать свойства StopLoss и TakeProfit из списка неподдерживаемых свойств — теперь эти два свойства есть у сделки, и они должны поддерживаться объектом класса:
//+------------------------------------------------------------------+ //| Возвращает истину, если ордер поддерживает переданное | //| вещественное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
Теперь метод выглядит так:
//+------------------------------------------------------------------+ //| Возвращает истину, если ордер поддерживает переданное | //| вещественное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CHistoryDeal::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { if(property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_VOLUME_CURRENT || property==ORDER_PROP_PRICE_STOP_LIMIT || ( this.OrderType()==DEAL_TYPE_BALANCE && ( property==ORDER_PROP_PRICE_OPEN || property==ORDER_PROP_COMMISSION || property==ORDER_PROP_SWAP || property==ORDER_PROP_VOLUME ) ) ) return false; return true; } //+------------------------------------------------------------------+
Так как в методе перечисляются неподдерживаемые свойства, то добавленное новое свойство сделки DEAL_FEE в этом списке не было изначально и, соответственно, его нам не нужно прописывать здесь — оно по умолчанию будет поддерживаться.
Все новые свойства и коды ошибок добавлены в библиотеку и должны правильно обрабатываться.
Займёмся наконец перемещением мышкой объектов-форм.
Независимая обработка перемещения объектов-форм
В первую очередь решим, как нам обрабатывать событие перемещения мышки, если кнопка зажата и удерживается на форме. Опыт прошлой статьи показал, что необходимо разработать немного иную концепцию обработки событий мышки относительно формы. При тестировании советника из прошлой статьи, было обнаружено много недочётов и неправильного поведения при взаимодействии мышки с множественными объектами-формами, поэтому здесь необходим иной подход вместо того, который был нами реализован ранее.
Множественные эксперименты привели к тому, что нам необходимо в обработчике событий класса-коллекции графических элементов создать систему флагов, указывающих на то, над какой из многочисленных объектов-форм находится в данный момент курсор мышки, зажата ли при этом кнопка мышки, и была ли она ранее уже зажата за пределами всех форм, присутствующих на графике. В объекте-форме у нас есть свойство — флаг взаимодействия с внешней средой. Используем его для указания именно той формы, с которой необходимо работать, и события которой нам необходимо обрабатывать.
Т.е., концепция должна быть такой:
- Если курсор находится за пределами всех форм, присутствующих на графике, то для графика разрешаются контекстное меню, перемещение его мышкой и инструмент "Перекрестие", а на пересечение курсора с формами (при зажатой кнопке мышки за пределами форм) не должно быть никакой реакции — мы мышкой двигаем график.
- Как только курсор мышки наводится на любой объект-форму (при отпущенной кнопке) — запрещаем весь инструментарий графика и ожидаем либо зажатия кнопки мышки на форме, либо иное взаимодействие мышки с этой формой (прокрутка колёсика мышки, например, либо визуализация наведения курсора на форму в виде некоторых визуальных эффектов формы).
- Если кнопка мышки стала нажатой на форме, то для формы устанавливается флаг взаимодействия и перемещения. Флаг взаимодействия будем использовать впоследствии для определения, какую из двух форм выбрать при условии, что эти две формы наложены друг на друга, и курсор мышки наведён на них и после этого зажата кнопка мышки. Должна выбираться именно та форма, у которой активен флаг взаимодействия.
Если же после перемещения формы мышкой отпустить кнопку, то для графика включится разрешение его инструментария, а для формы флаг взаимодействия будет оставлен взведённым. Таким образом именно эта форма будет выбрана из двух, если они опять наложены друг на друга, и кнопка мышки отпущена. Либо, если выбрать курсором неактивную в данный момент форму (не ту, которая сейчас перемещалась) и начать её перемещать, то с первой формы будет снят флаг взаимодействия, а для захваченной мышкой — установлен.
Такая система флагов позволит нам всегда знать, какая форма была активной последней, на какую из них просто наведён курсор и можно ли её как-то выделить визуально (эти обработчики будем делать позже, и не в классе-коллекции графических элементов, а в классе объекта-формы), и мы всегда в любой момент времени сможем взаимодействовать с формой, на которую наведён курсор, и перемещать ту форму, которая выбрана мышкой последней.
Всё, что мы создали в обработчике событий класса объекта-формы, нам нужно удалить (кроме корректировки координат формы при событии изменения графика). Откроем файл \MQL5\Include\DoEasy\Objects\Graph\Form.mqh и удалим из обработчика ненужный код, оставив только корректировку смещения вертикальной координаты:
//+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Корректируем смещение по Y для подокна CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Также были подправлены наименования констант перечислений, которые мы выше исправили для большей их смысловой нагрузки:
//+------------------------------------------------------------------+ //| Возвращает состояние мышки относительно формы | //+------------------------------------------------------------------+ ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем состояние мышки относительно формы и состояние кнопок мыши и клавиш Shift и Ctrl 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); //--- Получаем флаги состоянии мышки из объекта класса CMouseState и сохраняем их в переменной this.m_mouse_state_flags=this.m_mouse.GetMouseFlags(); //--- Если курсор внутри формы if(CGCnvElement::CursorInsideElement(m_mouse.CoordX(),m_mouse.CoordY())) { //--- Устанавливаем бит 8, отвечающий за флаг "курсор внутри формы" this.m_mouse_state_flags |= (0x0001<<8); //--- Если курсор внутри активной зоны - устанавливаем бит 9 "курсор внутри активной зоны" if(CGCnvElement::CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY())) this.m_mouse_state_flags |= (0x0001<<9); //--- иначе - снимаем бит "курсор внутри активной зоны" else this.m_mouse_state_flags &=0xFDFF; //--- Если нажата одна из трёх кнопок мыши - проверяем расположение курсора в активной области и //--- возвращаем соответствующее значение нажатой кнопки (в активной зоне или в области формы) 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); //--- иначе если ни одна кнопка мышки не нажата else { //--- если колесо мышки прокручивается, возвращаем соответствующее значение прокрутки колеса (в активной зоне или в области формы) 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); //--- иначе - возвращаем соответствующее значение ненажатой кнопки (в активной зоне или в области формы) 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); } } //--- Если курсор снаружи формы else { //--- возвращаем соответствующее значение кнопок в неактивной зоне form_state= ( ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED ); } return form_state; } //+------------------------------------------------------------------+
Всё. Теперь нам нужно создать систему флагов в обработчике событий класса-коллекции графических элементов.
Откроем файл \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh и внесём в него необходимые доработки.
В приватной секции класса объявим объект класса "Состояния мышки" и два метода — возвращающий указатель на форму, находящуюся под курсором, и сбрасывающий всем формам флаги взаимодействия кроме указанной:
//+------------------------------------------------------------------+ //| Коллекция графических объектов | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Индикатор контроля событий графических объектов, упакованный в ресурсы программы class CGraphElementsCollection : public CBaseObj { private: CArrayObj m_list_charts_control; // Список объектов управления чартами CListObj m_list_all_canv_elm_obj; // Список всех графических элементов на канвасе CListObj m_list_all_graph_obj; // Список всех графических объектов CArrayObj m_list_deleted_obj; // Список удалённых графических объектов CMouseState m_mouse; // Объект класса "Состояния мышки" bool m_is_graph_obj_event; // Флаг события в списке графических объектов int m_total_objects; // Количество графических объектов int m_delta_graph_obj; // Разница в количестве графических объектов по сравнению с прошлой проверкой //--- Возвращает флаг наличия объекта класса графического элемента в списке-коллекции графических элементов bool IsPresentCanvElmInList(const long chart_id,const string name); //--- Возвращает флаг наличия объекта класса графического объекта в списке-коллекции графических объектов bool IsPresentGraphObjInList(const long chart_id,const string name); //--- Возвращает флаг наличия графического объекта на графике по имени bool IsPresentGraphObjOnChart(const long chart_id,const string name); //--- Возвращает указатель на объект управления объектами указанного чарта CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id); //--- Создаёт новый объект управления графическими объектами указанного чарта и добавляет его в список CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id); //--- Обновляет список графических объектов по идентификатору чарта CChartObjectsControl *RefreshByChartID(const long chart_id); //--- Проверяет наличие окна графика bool IsPresentChartWindow(const long chart_id); //--- Обрабатывает удаление окна графика void RefreshForExtraObjects(void); //--- Возвращает первый свободный идентификатор графического (1) объекта, (2) элемента на канвасе long GetFreeGraphObjID(bool program_object); long GetFreeCanvElmID(void); //--- Добавляет (1) стандартный графический объект, (2) графический элемент на канвасе в коллекцию bool AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control); //--- Возвращает указатель на форму, находящуюся под курсором CForm *GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam,ENUM_MOUSE_FORM_STATE &mouse_state); //--- Сбрасывает всем формам флаги взаимодействия кроме указанной void ResetAllInteractionExeptOne(CForm *form); public: bool AddCanvElmToCollection(CGCnvElement *element); private: //--- Находит объект, имеющийся в коллекции, но отсутствующий на графике CGStdGraphObj *FindMissingObj(const long chart_id); CGStdGraphObj *FindMissingObj(const long chart_id,int &index); //--- Находит графический объект, имеющийся на графике, но отсутствующий в коллекции string FindExtraObj(const long chart_id); //--- Удаляет (1) указанный, (2) по идентификатору графика объект класса графического объекта из списка-коллекции графических объектов bool DeleteGraphObjFromList(CGStdGraphObj *obj); void DeleteGraphObjectsFromList(const long chart_id); //--- Перемещает (1) указанный, (2) по индексу объект класса графического объекта в список удалённых графических объектов bool MoveGraphObjToDeletedObjList(CGStdGraphObj *obj); bool MoveGraphObjToDeletedObjList(const int index); //--- Перемещает в список удалённых графических объектов все объекты по идентификатору графика void MoveGraphObjectsToDeletedObjList(const long chart_id); //--- Удаляет объект управления графиками из списка bool DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj); //--- Устанавливает для указанного графика флаги прокрутки чарта колёсиком мышки, контекстного меню и инструмента "перекрестие" void SetChartTools(const long chart_id,const bool flag); public:
Метод, возвращающий указатель на форму, находящуюся под курсором:
//+------------------------------------------------------------------+ //| Возвращает указатель на форму, находящуюся под курсором | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam,ENUM_MOUSE_FORM_STATE &mouse_state) { //--- Инициализируем состояние мышки относительно формы mouse_state=MOUSE_FORM_STATE_NONE; //--- Объявим указатели на объекты класса-коллекции графических элементов CGCnvElement *elm=NULL; CForm *form=NULL; //--- Получим список объектов, для которых установлен флаг взаимодействия (должен быть всего один объект) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); //--- Если список получить удалось и он не пустой if(list!=NULL && list.Total()>0) { //--- Получаем единственный в нём графический элемент elm=list.At(0); //--- Если этот элемент - объект-форма if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM) { //--- Присваиваем указатель на элемент указателю на объект-форму form=elm; //--- Получаем состояние мышки относительно формы mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- Если курсор в пределах формы - возвращаем указатель на форму if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- Если нет ни одного объекта-формы с установленным флагом взаимодействия - //--- в цикле по всем объектам класса-коллекции графических элементов int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- получаем очередной элемент elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL) continue; //--- если полученный элемент - объект-форма if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM) { //--- Присваиваем указатель на элемент указателю на объект-форму form=elm; //--- Получаем состояние мышки относительно формы mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- Если курсор в пределах формы - возвращаем указатель на форму if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return form; } } //--- Ничего не нашли - возвращаем NULL return NULL; } //+------------------------------------------------------------------+
Метод подробно прокомментирован прямо в коде. Вкратце: нам нужно знать, на каком объекте-форме находится курсор мышки. Если просто смотреть координаты курсора и сопоставлять их с координатами и размерами формы, то мы, конечно, найдём ту форму, над которой находится курсор. Но тут всплывает один неприятный момент: если две формы наложены друг на друга, то выберется та форма, которая находится в списке первой, а не та, которая на графике находится над всеми. Это неправильно. Поэтому мы, при нажатии кнопки мышки на форме, устанавливаем для формы флаг взаимодействия, и по нему ищем эту активную форму. Если такой формы нету — только в этом случае начинаем искать любую форму в списке, над которой находится курсор мышки. Такой подход даст нам верное поведение при работе с мышкой — всегда будет выбираться именно та форма, которая была активной последней, и именно она и находится выше остальных на графике, т.е. — на переднем плане, так как при её выборе она сразу же перемещается на передний план.
Метод, сбрасывающий всем формам флаги взаимодействия кроме указанной:
//+------------------------------------------------------------------+ //| Сбрасывает всем формам флаги взаимодействия кроме указанной | //+------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CForm *form_exept) { //--- В цикле по всем объектам класса-коллекции графических элементов int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- получаем указатель на объект-форму CForm *form=this.m_list_all_canv_elm_obj.At(i); //--- если указатель получить не удалось, или это не форма, или это та форма, указатель на которую передан в метод - идём далее if(form==NULL || form.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (form.Name()==form_exept.Name() && form.ChartID()==form_exept.ChartID())) continue; //--- Сбрасываем флаг взаимодействия текущей форме в цикле form.SetInteraction(false); } } //+------------------------------------------------------------------+
Метод нужен для того, чтобы у нас всегда только одна форма имела взведённым флаг взаимодействия. При выборе формы мышкой, мы сразу же устанавливаем для неё флаг взаимодействия, а для остальных форм этот флаг нужно снять, что и происходит при вызове этого метода.
В обработчике событий класса-коллекции графических элементов нам нужно при любых событиях, кроме события изменения графика, вызвать обработчики событий взаимодействий мышки с формами. Для этого очень хорошо подходит участок кода, следующий сразу за проверкой события изменения графика — нам как раз нужно реагировать на все события, кроме этого. Разместим блок обработчиков взаимодействия мышки с формами в нужном месте:
//+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj_std=NULL; // Указатель на стандартный графический объект CGCnvElement *obj_cnv=NULL; // Указатель на объект графического элемента на канвасе 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) { //--- Рассчитаем идентификатор графика //--- Если id события соответствует событию с текущего графика, то идентификатор графика получаем от ChartID //--- Если id события соответствует пользовательскому событию, то идентификатор графика получаем из lparam //--- Иначе - идентификатору графика присваиваем -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); //--- Получим объект из списка-коллекции по его имени, записанном в sparam, //--- свойства которого изменены, или который был перемещён obj_std=this.GetStdGraphObject(sparam,chart_id); //--- Если объект не удалось получить по имени - он отсутствует в списке, //--- а значит его имя было изменено if(obj_std==NULL) { //--- Найдём в списке объект, которого нет на графике obj_std=this.FindMissingObj(chart_id); //--- Если и тут не удалось найти объект - уходим if(obj_std==NULL) return; //--- Получим имя переименованного графического объекта на графике, которого нет в списке-коллекции string name_new=this.FindExtraObj(chart_id); //--- установим новое имя объекту в списке-коллекции, которому не соответствует ни один графический объект на графике, //--- и отправим событие с новым именем объекта на график управляющей программы 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()); } //--- Обновим свойства полученного объекта //--- и проверим их изменение obj_std.PropertiesRefresh(); obj_std.PropertiesCheckChanged(); } //--- Обработка событий стандартных графических объектов в списке-коллекции for(int i=0;i<this.m_list_all_graph_obj.Total();i++) { //--- Получаем очередной графический объект и obj_std=this.m_list_all_graph_obj.At(i); if(obj_std==NULL) continue; //--- вызываем его обработчик событий obj_std.OnChartEvent((id<CHARTEVENT_CUSTOM ? id : idx),lparam,dparam,sparam); } //--- Обработка изменения графика для расширенных стандартных объектов 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); } } } //--- Обработка событий мышки графических объектов на канвасе //--- Если событие - не изменение графика else { //--- Проверим нажата ли кнопка мышки 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; //--- Объявим статические переменные для активной формы и флагов состояний static CForm *form=NULL; static bool pressed_chart=false; static bool pressed_form=false; static bool move=false; //--- Если кнопка не зажата на графике и не стоит флаг перемещения - получаем форму, над которой находится курсор if(!pressed_chart && !move) form=this.GetFormUnderCursor(id,lparam,dparam,sparam,mouse_state); //--- Если не нажата кнопка - сбрасываем все флаги и разрешаем инструментарий графика if(!pressed) { pressed_chart=false; pressed_form=false; move=false; SetChartTools(::ChartID(),true); } //--- Если событие перемещения мышки и стоит флаг перемещения - смещаем форму за курсором (если указатель на неё валидный) if(id==CHARTEVENT_MOUSE_MOVE && move) { if(form!=NULL) { //--- рассчитываем смещение курсора относительно начала координат формы int x=this.m_mouse.CoordX()-form.OffsetX(); int y=this.m_mouse.CoordY()-form.OffsetY(); //--- получаем ширину и высоту графика, на котором находится форма 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()); //--- Корректируем рассчитанные координаты для формы в случае выхода формы за пределы графика 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(); //--- Смещаем форму на полученные координаты form.Move(x,y,true); } } //--- Выводим на график отладочные комментарии 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(form==NULL) { //--- Если нажата кнопка мышки if(pressed) { //--- Если кнопка всё ещё нажата и удерживается на форме - уходим if(pressed_form) { return; } //--- Если ещё не стоит флаг удержания кнопки на чарте - ставим флаги и разрешаем инструментарий графика if(!pressed_chart) { pressed_chart=true; // Кнопка удерживается на графике pressed_form=false; // Курсор не над формой move=false; // смешщение запрещено SetChartTools(::ChartID(),true); } } } //--- Если курсор над формой else { //--- Если кнопка всё ещё нажата и удерживается на графике - уходим if(pressed_chart) { return; } //--- Если ещё не установлен флаг удержания кнопки на форме if(!pressed_form) { pressed_chart=false; // Кнопка не удерживается на графике SetChartTools(::ChartID(),false); //--- Заготовка обработчика события Курсор в пределах формы, кнопки мышки не нажаты if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED) { } //--- Заготовка обработчика события Курсор в пределах формы, нажата кнопка мышки (любая) if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED) { } //--- Заготовка обработчика события Курсор в пределах формы, прокручивается колёсико мышки if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL) { } //--- Обработчик события Курсор в пределах активной области, кнопки мышки не нажаты if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED) { //--- Установим смещение курсора относительно начальных координат формы form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); } //--- Обработчик события Курсор в пределах активной области, нажата кнопка мышки (любая) if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // флаг удержания кнопки мышки на форме //--- Если зажата левая кнопка мышки if(this.m_mouse.IsPressedButtonLeft()) { //--- Установим флаги и параметры формы move=true; // флаг перемещения form.SetInteraction(true); // флаг взаимодействия формы со внешней средой form.BringToTop(); // форма на передний план - поверх всех остальных this.ResetAllInteractionExeptOne(form); // Сбросим флаги взаимодействия для всех форм, кроме текущей form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Смещение курсора относительно координаты X form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Смещение курсора относительно координаты Y } } //--- Заготовка обработчика события Курсор в пределах активной области, прокручивается колёсико мышки if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL) { } //--- Заготовка обработчика события Курсор в пределах области прокрутки окна, кнопки мышки не нажаты if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED) { } //--- Заготовка обработчика события Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая) if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED) { } //--- Заготовка обработчика события Курсор в пределах области прокрутки окна, прокручивается колёсико мышки if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL) { } } } } } //+------------------------------------------------------------------+
Здесь весь блок кода обработчика событий взаимодействия мышки с формой подробно прокомментирован. Для некоторых событий пока есть лишь "заглушки" — в этих блоках будем в дальнейшем прописывать вызов обработчиков этих событий в объекте-форме.
Сейчас у нас уже всё готово для тестирования этой новой концепции.
Тестирование
Для тестирования возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part97\ под новым именем TestDoEasyPart97.mq5.
Практически никаких изменений делать не будем. Лишь подправим координаты создаваемых форм и для их создания будем использовать ранее созданную макроподстановку, указывающую количество создаваемых объектов-форм:
//+------------------------------------------------------------------+ //| TestDoEasyPart97.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (2) // Количество создаваемых форм #define START_X (4) // Начальная координата X фигуры #define START_Y (4) // Начальная координата Y фигуры #define KEY_LEFT (188) // Влево #define KEY_RIGHT (190) // Вправо #define KEY_ORIGIN (191) // Изначальные свойства //--- 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() { //--- Установка глобальных переменных советника ArrayResize(array_clr,2); // Массив цветов градиентной заливки array_clr[0]=C'26,100,128'; // Исходный ≈Тёмно-лазурный цвет array_clr[1]=C'35,133,169'; // Осветлённый исходный цвет //--- Создадим массив с текущим символом и установим его для использования в библиотеке string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Создадим объект-таймсерию для текущего символа и периода и выведем его описание в журнал engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания //--- Создадим объекты-формы for(int i=0;i<FORMS_TOTAL;i++) { //--- При создании объекта передаём в него все требуемые параметры CForm *form=new CForm("Form_0"+string(i+1),30,(i==0 ? 100 : 160),100,30); if(form==NULL) continue; //--- Установим форме флаги активности, и перемещаемости form.SetActive(true); form.SetMovable(true); //--- Установим форме её идентификатор и номер в списке объектов form.SetID(i); form.SetNumber(0); // (0 - означает главный объект-форма) К главному объекту могут прикрепляться второстепенные, которыми он будет управлять //--- Установим непрозрачность 200 form.SetOpacity(245); //--- Цвет фона формы зададим как первый цвет из массива цветов form.SetColorBackground(array_clr[0]); //--- Цвет очерчивающей рамки формы form.SetColorFrame(clrDarkBlue); //--- Установим флаг рисования тени form.SetShadow(false); //--- Рассчитаем цвет тени как цвет фона графика, преобразованный в монохромный color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- Если в настройках задано использовать цвет фона графика, то затемним монохромный цвет на 20 единиц //--- Иначе - будем использовать для рисования тени заданный в настройках цвет color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Нарисуем тень формы со смещением от формы вправо-вниз на три пикселя по всем осям //--- Непрозрачность тени при этом установим равной 200, а радиус размытия равный 4 form.DrawShadow(3,3,clr,200,4); //--- Зальём фон формы вертикальным градиентом form.Erase(array_clr,form.Opacity(),true); //--- Нарисуем очерчивающий прямоугольник по краям формы form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Выведем текст с описанием типа градиента и обновим форму //--- Параметры текста: координаты текста в центре формы и точка привязки - тоже по центру //--- Создаём новый кадр текстовой анимации с идентификатором 0 и выводим текст на форму 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); //--- Добавим форму в список if(!engine.GraphAddCanvElmToCollection(form)) delete form; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Скомпилируем советник и запустим его на графике:
Вот теперь мы избавились от всех недостатков, озвученных после тестирования советника из прошлой статьи. Кроме того, перемещение форм ограничено пределами графика, а при наложении одной формы на другую всегда выбирается нужная форма и всегда верно рассчитываются смещения координат курсора относительно координат перемещаемой формы.
Что дальше
В следующей статье продолжим развитие графических объектов библиотеки.
*Статьи этой серии:
Графика в библиотеке DoEasy (Часть 93): Готовим функционал для создания составных графических объектов
Графика в библиотеке DoEasy (Часть 94): Составные графические объекты, перемещение и удаление
Графика в библиотеке DoEasy (Часть 95): Элементы управления составными графическими объектами
Графика в библиотеке DoEasy (Часть 96): Работа с событиями мышки и графика в объектах-формах
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Кроме автора, штампующего эти статьи, как на принтере, кому они нужны?
Мне нужны.