English 中文 Español Deutsch 日本語 Português
preview
Графика в библиотеке DoEasy (Часть 97): Независимая обработка перемещения объектов-форм

Графика в библиотеке DoEasy (Часть 97): Независимая обработка перемещения объектов-форм

MetaTrader 5Примеры | 25 февраля 2022, 12:44
1 601 2
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

Разрабатывая расширенные графические объекты, мы столкнулись с необходимостью вернуться к объектам-формам — графическим объектам на канвасе, которые будут присутствовать у расширенных графических объектов в качестве узлов управления точками привязки графического объекта. В прошлой статье начали создавать обработку событий мышки для объектов-форм. Сегодня завершим обработку перемещения объекта-формы. Причём сделаем так, чтобы мы могли перемещать любую форму по графику, и при этом форма будет выбираться именно та, которая захвачена курсором, и все инструменты графика будут правильно и в нужное время включаться или выключаться в зависимости от события.

Организуем отслеживание событий мышки таким образом, чтобы впоследствии мы смогли воспользоваться заготовленными сегодня обработчиками для реализации всех остальных взаимодействий объекта-формы с мышкой. Помимо реализации перемещения форм курсором мышки и подготовки "заглушек" для реализации иных событий взаимодействия формы с мышкой, сегодня мы дополним тексты кодов возврата торгового сервера и коды ошибок выполнения, которые появились в 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);
  }
//+------------------------------------------------------------------+

Скомпилируем советник и запустим его на графике:


Вот теперь мы избавились от всех недостатков, озвученных после тестирования советника из прошлой статьи. Кроме того, перемещение форм ограничено пределами графика, а при наложении одной формы на другую всегда выбирается нужная форма и всегда верно рассчитываются смещения координат курсора относительно координат перемещаемой формы.

Что дальше

В следующей статье продолжим развитие графических объектов библиотеки.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для MQL5. Их можно скачать и протестировать всё самостоятельно. При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

Графика в библиотеке DoEasy (Часть 93): Готовим функционал для создания составных графических объектов
Графика в библиотеке DoEasy (Часть 94): Составные графические объекты, перемещение и удаление
Графика в библиотеке DoEasy (Часть 95): Элементы управления составными графическими объектами
Графика в библиотеке DoEasy (Часть 96): Работа с событиями мышки и графика в объектах-формах

Прикрепленные файлы |
MQL5.zip (4294.47 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Alexey Viktorov
Alexey Viktorov | 28 февр. 2022 в 12:17
Vladimir Baskakov #:
Кроме автора, штампующего эти статьи, как на принтере, кому они нужны?

Мне нужны.

Vladimir Gulakov
Vladimir Gulakov | 1 мар. 2022 в 20:54
Файл тестового Советника. Чего то не отображается на графике. Компилировать пробовал, показывает 100 ошибок. Не будет ли для вас сильно затруднительно выложить это файл напрямую здесь, а еще лучше в КодоБазе? А то может он у меня неправильно распаковывается. Вообще по теме вещь полезная, эти прямоугольники перетягивать так по графику. Нужно только дополнить, чтоб в настройках можно было регулировать высоту пунктами, а если бы эти пункты еще и отображались на этом прямоугольнике графически, так было бы вообще замечательно. Ещё нужно, чтоб он был прозрачным, график собой не заслонял.
Уроки по DirectX (Часть I): Рисуем первый треугольник Уроки по DirectX (Часть I): Рисуем первый треугольник
Это вводная статья по DirectX, которая описывает особенности работы с API. Помогает разобраться с порядком инициализации его компонентов. Приводит пример написания скрипта на MQL, выводящего треугольник с помощью DirectX.
Веб-проекты (Часть III): Система авторизации Laravel/MetaTrader 5 Веб-проекты (Часть III): Система авторизации Laravel/MetaTrader 5
В этот раз создадим систему авторизации в торговом терминале MetaTrader 5 на чистом MQL5. Пользователи приложения смогут зарегистрироваться в системе, предоставив свои учётные данные, чтобы впоследствии можно было авторизоваться и получить доступ, к каким-нибудь данным, которые хранятся в серверной части приложения.
Советы профессионального программиста (Часть III): Логирование. Подключение к системе сбора и анализа логов Seq Советы профессионального программиста (Часть III): Логирование. Подключение к системе сбора и анализа логов Seq
Реализация класса Logger для унификации (структурирования) сообщений, выводимых в журнал эксперта. Подключение к системе сбора и анализа логов Seq. Наблюдение за сообщениями в онлайн режиме.
Шаблон проектирования MVC и возможность его использования (Часть 2): Схема взаимодействия между тремя компонентами Шаблон проектирования MVC и возможность его использования (Часть 2): Схема взаимодействия между тремя компонентами
Данная статья продолжает и завершает тему, поднятую в прошлой статье — шаблон MVC в программах на MQL. В этой статье мы рассмотрим возможную схему взаимодействия между этими тремя компонентами.