English 中文 Español Deutsch 日本語 Português
DoEasy. Элементы управления (Часть 32):  горизонтальный "ScrollBar", прокрутка колесиком мышки

DoEasy. Элементы управления (Часть 32): горизонтальный "ScrollBar", прокрутка колесиком мышки

MetaTrader 5Примеры | 27 июня 2023, 08:52
664 4
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

С момента написания последней статьи по библиотеке прошло немало времени, за которое в торговом терминале MetaTrader 5 появилась поддержка новой политики исполнения ордеровПассивная / Book or Cancel (BOC), а в языке MQL5 появились новые коды ошибок времени выполнения для методов матриц и векторов и ONNX моделей. Все эти дополнения сегодня добавим в библиотеку.

В прошлой статье мы остановились на том, что создали функционал WinForms объекта-полосы прокрутки, позволяющий прокручивать содержимое контейнера кнопками со стрелками, расположенными по краям полосы прокрутки. Этого, естественно, недостаточно для полноценной работы с этим объектом, и сегодня мы сделаем возможность прокрутки содержимого контейнера перетаскиванием ползунка прокрутки мышкой. А созданные для этого методы позволят подключить к объекту-полосе прокрутки функционал, позволяющий смещать содержимое контейнера прокруткой колёсика мышки.

Если навести курсор на область с горизонтальной полосой прокрутки и прокрутить колесо мышки, то это вызовет смещение содержимого контейнера вправо при прокрутке колеса вверх (от себя), и влево — при прокрутке колеса вниз (на себя). По крайней мере так происходит горизонтальная прокрутка в Adobe PhotoShop, и здесь мы сделаем точно так же. В последующем добавим возможность указать в библиотеке направление горизонтальной (и вертикальной тоже) прокрутки содержимого контейнера колёсиком мышки.


Доработка классов библиотеки

Некоторое время назад, после обновления терминала, всплыла досадная ошибка — перестала компилироваться библиотека и файлы примеров к статьям. Причиной послужила моя невнимательность при указании приватной секции для некоторых методов класса CTrading в файле Trading.mqh. Соответственно, класс CTradingControl, являющийся производным классом от CTrading, не мог получить доступ к таким методам. Ранее компилятор пропускал эту мою ошибку, но после обновления она стала обнаруживаться. Исправление ситуации простое — нужно указать для недоступных из производных классов приватных методов защищённую секцию и они станут доступны в наследуемом классе.

Откроем файл \MQL5\Include\DoEasy\Trading.mqh и укажем защищённую секцию для метода SetPrices(), находящегося в приватной секции:

//--- Устанавливает торговому объекту нужный звук
   void                 SetSoundByMode(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,CTradeObj *trade_obj);

protected:
//--- Устанавливает цены торгового запроса
   template <typename PR,typename SL,typename TP,typename PL> 
   bool                 SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj);

private:
//--- Возвращает флаг проверки разрешённости по дистанции (1) StopLoss, (2) TakeProfit, (3) цены установки ордера от цены по уровню StopLevel
   bool                 CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj);
   bool                 CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj);
   bool                 CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0);

После объявления метода вернём приватную секцию на место — чтобы остальные методы не попали в защищённую.

К слову, ещё немного ранее я уже проделывал то же самое с двумя другими методами в этом же классе:

//--- Возвращает метод обработки ошибки
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(const uint result_code);
//--- Корректировка ошибок
   ENUM_ERROR_CODE_PROCESSING_METHOD   RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj);
   
protected:
//--- (1) Открывает позицию, (2) устанавливает отложенный ордер

   template<typename SL,typename TP> 
   bool                 OpenPosition(const ENUM_POSITION_TYPE type,
                                    const double volume,
                                    const string symbol,
                                    const ulong magic=ULONG_MAX,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const string comment=NULL,
                                    const ulong deviation=ULONG_MAX,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceOrder( const ENUM_ORDER_TYPE order_type,
                                    const double volume,
                                    const string symbol,
                                    const PR price,
                                    const PL price_limit=0,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const ulong magic=ULONG_MAX,
                                    const string comment=NULL,
                                    const datetime expiration=0,
                                    const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
                                    
private                                   
//--- Возвращает индекс объекта-запроса в списке по (1) идентификатору,
//--- (2) тикету ордера, (3) тикету позиции в запросе
   int                  GetIndexPendingRequestByID(const uchar id);
   int                  GetIndexPendingRequestByOrder(const ulong ticket);
   int                  GetIndexPendingRequestByPosition(const ulong ticket);

public:

Если вдруг при компиляции файлов примеров к предыдущим статьям встретится ошибка запрета доступа у приватным методам класса CTrading, то исправить её можно будет самостоятельно, проделав указанные выше доработки.


Так как теперь появилась новая политика исполнения ордеров и новые коды ошибок времени выполнения, то нам необходимо добавить в массивы текстовых сообщений библиотеки описание этих нововведений.

Откроем файл \MQL5\Include\DoEasy\Data.mqh и впишем новый индекс сообщения о новой политике исполнения:

   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_FOK,            // Ордер исполняется исключительно в указанном объеме, иначе отменяется
   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_IOK,            // Ордер исполняется на доступный объем, неисполненный отменяется
   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK,            // Ордер выставляется в стакан цен и не может быть исполнен немедленно. Если ордер может быть исполнен немедленно при выставлении, то он снимается
   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_RETURN,         // Ордер исполняется на доступный объем, неисполненный остаётся

и текстовые сообщения (русское и английское), соответствующие вновь добавленному индексу:

   {"Ордер исполняется исключительно в указанном объеме, иначе отменяется (FOK)","The order is executed exclusively in the specified volume, otherwise it is canceled (FOK)"},
   {"Ордер исполняется на доступный объем, неисполненный отменяется (IOK)","The order is executed on the available volume, the unfulfilled is canceled (IOK)"},
   {
    "Ордер выставляется в стакан цен и не может быть исполнен немедленно. Если ордер может быть исполнен немедленно при выставлении, то он снимается (BOK)",
    "The order is placed in the Depth of Market and cannot be executed immediately. If the order can be executed immediately when placed, then it is canceled"
   },
   {"Ордер исполняется на доступный объем, неисполненный остаётся (Return)","The order is executed at an available volume, unfulfilled remains in the market (Return)"},


В массив сообщений ошибок времени выполнения (коды ошибок 0, 4001 — 4019) добавим новые коды ошибок, появившиеся в языке MQL5 — от 4020 до 4025:

//+------------------------------------------------------------------+
//| Массив сообщений ошибок времени выполнения (0, 4001 - 4025)      |
//| (1) на языке страны пользователя                                 |
//| (2) на международном языке                                       |
//+------------------------------------------------------------------+
string messages_runtime[][TOTAL_LANG]=
  {
   {"Операция выполнена успешно","The operation completed successfully"},                                                                          // 0
   {"Неожиданная внутренняя ошибка","Unexpected internal error"},                                                                                  // 4001
   {"Ошибочный параметр при внутреннем вызове функции клиентского терминала","Wrong parameter in the inner call of the client terminal function"}, // 4002
   {"Ошибочный параметр при вызове системной функции","Wrong parameter when calling the system function"},                                         // 4003
   {"Недостаточно памяти для выполнения системной функции","Not enough memory to perform the system function"},                                    // 4004
   {
    "Структура содержит объекты строк и/или динамических массивов и/или структуры с такими объектами и/или классы",                                // 4005
    "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes"
   },                                                                                                                                                    
   {
    "Массив неподходящего типа, неподходящего размера или испорченный объект динамического массива",                                               // 4006
    "Array of a wrong type, wrong size, or a damaged object of a dynamic array"
   },
   {
    "Недостаточно памяти для перераспределения массива либо попытка изменения размера статического массива",                                       // 4007
    "Not enough memory for the relocation of an array, or an attempt to change the size of a static array"
   },
   {"Недостаточно памяти для перераспределения строки","Not enough memory for the relocation of string"},                                          // 4008
   {"Неинициализированная строка","Not initialized string"},                                                                                       // 4009
   {"Неправильное значение даты и/или времени","Invalid date and/or time"},                                                                        // 4010
   {"Общее число элементов в массиве не может превышать 2147483647","Total amount of elements in the array cannot exceed 2147483647"},             // 4011
   {"Ошибочный указатель","Wrong pointer"},                                                                                                        // 4012
   {"Ошибочный тип указателя","Wrong type of pointer"},                                                                                            // 4013
   {"Системная функция не разрешена для вызова","Function is not allowed for call"},                                                               // 4014
   {"Совпадение имени динамического и статического ресурсов","The names of the dynamic and the static resource match"},                            // 4015
   {"Ресурс с таким именем в EX5 не найден","Resource with this name has not been found in EX5"},                                                  // 4016
   {"Неподдерживаемый тип ресурса или размер более 16 MB","Unsupported resource type or its size exceeds 16 Mb"},                                  // 4017
   {"Имя ресурса превышает 63 символа","The resource name exceeds 63 characters"},                                                                 // 4018
   {"При вычислении математической функции произошло переполнение ","Overflow occurred when calculating math function "},                          // 4019
   {"Выход за дату окончания тестирования после вызова Sleep()","Out of test end date after calling Sleep()"},                                     // 4020
   {"Неизвестный код ошибки (4021)","Unknown error code (4021)"},                                                                         // 4021
   {
    "Тестирование было прекращено принудительно извне. Например, прервана оптимизацию, или закрыто окно визуального тестирования, или остановлен агент тестирования",
    "Test forcibly stopped from the outside. For example, optimization interrupted, visual testing window closed or testing agent stopped"},       // 4022
   {"Неподходящий тип","Invalid type"},                                                                                                            // 4023
   {"Невалидный хендл","Invalid handle"},                                                                                                          // 4024
   {"Пул объектов заполнен","Object pool filled out"},                                                                                             // 4025
  };
//+------------------------------------------------------------------+

Некоторые коды ошибок не задействованы (пока?), но их значения находятся непосредственно между задействованными кодами ошибок. Чтобы быстро понять, что этот код ошибки стал использоваться в MQL5, просто к его сообщению о неизвестном коде ошибки добавим сам код.

В случае, если библиотека выдаёт в журнал запись "Неизвестный код ошибки" (как это выглядит сейчас, до внесения изменений), сложно понять какой код из ранее неиспользуемых теперь стал задействован. Добавив к сообщению код ошибки, мы в такой ситуации сразу же получим сообщение о неизвестном коде ошибки с указанием номера этой ошибки. Нам останется лишь открыть обновлённую справку и дополнить библиотеку описанием этой ошибки так же, как мы это делаем сейчас. Такие дополнения в этом файле уже дописаны ко всем сообщениям о неизвестном коде ошибки.

Для кодов ошибок методов матриц и векторов и ONNX моделей создадим два новых массива, так как начальные значения кодов начинаются с 5700 и 5800 и "пустое пространство" с неиспользуемыми кодами заполнять почти тысячей одинаковых сообщений о неизвестном коде — не практично и не оптимально. Тем более, что в библиотеке по этой же причине используются разные массивы для различных групп кодов ошибок, и мы добавим ещё два массива после массива сообщений ошибок времени выполнения с кодами 5601 — 5626:

//+------------------------------------------------------------------+
//| Массив сообщений ошибок времени выполнения  (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
  };
//+------------------------------------------------------------------+
//| Массив сообщений ошибок времени выполнения  (5700 - 5706)        |
//| (Методы матриц и векторов)                                       |
//| (1) на языке страны пользователя                                 |
//| (2) на международном языке                                       |
//+------------------------------------------------------------------+
string messages_runtime_matrix_vector[][TOTAL_LANG]=
  {
   {"Внутренняя ошибка исполняющей подсистемы матриц/векторов","Internal error of the matrix/vector executing subsystem"},                         // 5700
   {"Матрица/вектор не инициализирован","Matrix/vector not initialized"},                                                                          // 5701
   {"Несогласованный размер матриц/векторов в операции","Inconsistent size of matrices/vectors in operation"},                                     // 5702
   {"Некорректный размер матрицы/вектора","Invalid matrix/vector size"},                                                                           // 5703
   {"Некорректный тип матрицы/вектора","Invalid matrix/vector type"},                                                                              // 5704
   {"Функция недоступна для данной матрицы/вектора","Function not available for this matrix/vector"},                                              // 5705
   {"Матрица/вектор содержит нечисла (Nan/Inf)","Matrix/vector contains non-numbers (Nan/Inf)"},                                                   // 5706
  };  
//+------------------------------------------------------------------+
//| Массив сообщений ошибок времени выполнения  (5800 - 5808)        |
//| (ONNX модели)                                                    |
//| (1) на языке страны пользователя                                 |
//| (2) на международном языке                                       |
//+------------------------------------------------------------------+
string messages_runtime_onnx[][TOTAL_LANG]=
  {
   {"Внутренняя ошибка ONNX стандарта","ONNX internal error"},                                                                                     // 5800
   {"Ошибка инициализации ONNX Runtime API","ONNX Runtime API initialization error"},                                                              // 5801
   {"Свойство или значение неподдерживаются языком MQL5","Property or value not supported by MQL5"},                                               // 5802
   {"Ошибка запуска ONNX runtime API","ONNX runtime API run error"},                                                                               // 5803
   {"В OnnxRun передано неверное количество параметров ","Invalid number of parameters passed to OnnxRun"},                                        // 5804
   {"Некорректное значение параметра","Invalid parameter value"},                                                                                  // 5805
   {"Некорректный тип параметра","Invalid parameter type"},                                                                                        // 5806
   {"Некорректный размер параметра","Invalid parameter size"},                                                                                     // 5807
   {"Размерность тензора не задана или указана неверно","Tensor dimension not set or invalid"},                                                    // 5808
  };  
//+------------------------------------------------------------------+
#ifdef __MQL4__


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

Откроем файл \MQL5\Include\DoEasy\Services\Message.mqh и впишем в метод GetTextByID() обработку вновь добавленных массивов. Метод записывает в переменную m_text, текстовое сообщение, хранящееся по индексу сообщения, передаваемого в метод.

Для ошибок времени выполнения (0, 4001 — 4019) расширим количество обрабатываемых кодов с 4019 до 4025, а для кодов ошибок методов матриц и векторов и ONNX-моделей будем записывать текстовые сообщения из новых массивов:

//+------------------------------------------------------------------+
//| Получение сообщения из массива текстов по идентификатору         |
//+------------------------------------------------------------------+
void CMessage::GetTextByID(const int msg_id)
  {
   CMessage::m_text=
     (
      //--- Ошибки времени выполнения (0, 4001 - 4025)
      msg_id==0                     ?  messages_runtime[msg_id][m_lang_num]                        :
     #ifdef __MQL5__
      msg_id>4000 && msg_id<4026    ?  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]            :
      //--- Ошибки времени выполнения (Методы матриц и векторов 5700 - 5706)
      msg_id>5699 && msg_id<5707    ?  messages_runtime_matrix_vector[msg_id-5700][m_lang_num]     :
      //--- Ошибки времени выполнения (ONNX модели 5800 - 5808)
      msg_id>5799 && msg_id<5809    ?  messages_runtime_onnx[msg_id-5800][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]
     );
  }
//+------------------------------------------------------------------+


Некоторые общие функции, используемые в библиотеке, записаны в файле \MQL5\Include\DoEasy\Services\DELib.mqh.

В функцию, возвращающую описание режима заливки ордера, добавим обработку типа заливки BoC:

//+------------------------------------------------------------------+
//| Возвращает описание режима заливки ордера                        |
//+------------------------------------------------------------------+
string OrderTypeFillingDescription(const ENUM_ORDER_TYPE_FILLING type)
  {
   return
     (
      type==ORDER_FILLING_FOK    ?  CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_FOK)   :
      type==ORDER_FILLING_IOC    ?  CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_IOK)   :
      type==ORDER_FILLING_BOC    ?  CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK)   :
      type==ORDER_FILLING_RETURN ?  CMessage::Text(MSG_LIB_TEXT_REQUEST_ORDER_FILLING_RETURN): 
      type==WRONG_VALUE          ? "WRONG_VALUE"   :  EnumToString(type)
     );
  }
//+------------------------------------------------------------------+

Теперь, если политика исполнения Book or Cancel, то функция возвратит текст, соответствующий индексу MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK в массиве сообщений, добавленный нами сегодня.


При снятии советника с графика в журнал выводилось сообщение об ошибке закрытия стакана цен. При этом не было ни описания ошибки, ни её кода. Считаю это неправильным поведением — просто совсем не понятно что происходит. Для исправления ситуации доработаем метод, осуществляющий закрытие стакана цен, в классе объекта-символа CSymbol в файле \MT5\MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh.

В блоке кода, обрабатывающем ошибку закрытия стакана цен, получим код ошибки и выведем его в журнал после описания этой ошибки:

//+------------------------------------------------------------------+
//| Осуществляет закрытие стакана цен                                |
//+------------------------------------------------------------------+
bool CSymbol::BookClose(void)
  {
//--- Если флаг подписки на стакан снят - значит уже (или ещё) нет подписки - возвращаем true
   if(!this.m_book_subscribed)
      return true;
//--- Сохраняем результат отписки от стакана цен
   bool res=( #ifdef __MQL5__ ::MarketBookRelease(this.m_name) #else true #endif );
//--- Если успешно отписались - сбрасываем флаг подписки на стакан и записываем состояние в свойство объекта
   if(res)
     {
      this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]=this.m_book_subscribed=false;
      ::Print(CMessage::Text(MSG_SYM_SYMBOLS_BOOK_DEL)+" "+this.m_name);
     }
   else
     {
      this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]=this.m_book_subscribed=true;
      int err=::GetLastError();
      ::Print(CMessage::Text(MSG_SYM_SYMBOLS_ERR_BOOK_DEL)+": "+CMessage::Text(err)+" (",(string)err,")");
     }
//--- Возвращаем результат отписки от стакана цен
   return res;
  }
//+------------------------------------------------------------------+

Теперь будет понятна причина возникновения ошибки при попытке закрытия стакана цен.


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

Если при смещении ползунка прокрутки нам не нужно иметь заранее определённого значения, на которое будет смещаться содержимое контейнера (величина смещения будет задаваться величиной, на которую был смещён ползунок), то для прокрутки колёсиком мышки нам нужно определить на сколько пикселей будет смещено содержимое контейнера при единоразовом срабатывании счётчика колёсика мышки. Счётчик дискретный и посылает событие при достижении значения Delta равным 120 или -120 (в зависимости от направления прокрутки колёсика). Для прокрутки колёсиком зададим значение в четыре пикселя.

Откроем файл \MT5\MQL5\Include\DoEasy\Defines.mqh и исправим наименование макроподстановки, отвечающей за величину смещения кликом по кнопке со стрелкой (DEF_CONTROL_SCROLL_BAR_SCROLL_STEP), и добавим новую макроподстановку, отвечающую за величину смещения при прокрутке колёсиком мышки:

#define DEF_CONTROL_SCROLL_BAR_WIDTH                  (11)                 // Ширина элемента управления ScrollBar по умолчанию
#define DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN         (8)                  // Минимальный размер области захвата (ползунка)
#define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK      (2)                  // Шаг сдвига в пикселях содержимого контейнера при прокрутке кликом по кнопке
#define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL      (4)                  // Шаг сдвига в пикселях содержимого контейнера при прокрутке колесом мышки
#define DEF_CONTROL_CORNER_AREA                       (4)                  // Количество пикселей, определяющих область угла для изменения размеров
#define DEF_CONTROL_LIST_MARGIN_X                     (1)                  // Зазор между столбцами в элементах управления ListBox
#define DEF_CONTROL_LIST_MARGIN_Y                     (0)                  // Зазор между строками в элементах управления ListBox


Все предварительные исправления теперь внесены, займёмся доработкой функционала полосы прокрутки.

Сначала сделаем возможность смещения содержимого контейнера перемещением мышкой полосы прокрутки. Логика будет такой: полоса прокрутки схематически, в уменьшенном виде, отображает контейнер и его содержимое. Ползунок, его размер, отображает размер видимой части контейнера, а полоса прокрутки (между краями кнопок со стрелками) отображает содержимое контейнера, выходящее за его пределы. У нас есть одна отправная точка, от которой мы будем отталкиваться в своих расчётах, — это величина, на которую ползунок сдвинут от правого края левой кнопки со стрелкой. И у нас есть размер ползунка и размер видимой части контейнера. Зная, на сколько видимая часть контейнера больше размера ползунка и на сколько был смещён ползунок, мы можем рассчитать на сколько нужно сместить содержимое контейнера:

  1. Ширина видимой части контейнера (W1)
  2. Ширина ползунка (W2)
  3. На сколько видимая часть контейнера больше ползунка (X = W1 / W2)
  4. На сколько смещён ползунок (S1)
  5. На сколько нужно сместить содержимое контейнера (S1 * X)

Таким образом, зная отношение размеров ползунка и видимой части контейнера, и то, на сколько был смещён ползунок, мы рассчитываем величину, на которую нужно сместить содержимое контейнера. При этом, если ползунок смещён влево, то содержимое контейнера нужно двигать вправо, и наоборот.

Откроем файл класса горизонтальной полосы прокрутки \MT5\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh и в его обработчике событий напишем блок кода, выполняющий данные расчёты и сдвиг содержимого контейнера:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CScrollBarHorisontal::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Корректируем смещение по Y для подокна
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Получаем указатели на управляющие объекты полосы прокрутки
   CArrowLeftButton  *buttl=this.GetArrowButtonLeft();
   CArrowRightButton *buttr=this.GetArrowButtonRight();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttl==NULL || buttr==NULL || thumb==NULL)
      return;
//--- Если идентификатор события - перемещение объекта
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Переносим полосу прокрутки на передний план
      this.BringToTop();
      //--- Объявляем переменные для координат области захвата
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Устанавливаем координату Y равной координате Y элемента управления
      y=this.CoordY()+this.BorderSizeTop();
      //--- Корректируем координату X так, чтобы область захвата не выходила за пределы элемента управления с учётом кнопок со стрелками
      if(x<buttl.RightEdge())
        x=buttl.RightEdge();
      if(x>buttr.CoordX()-thumb.Width())
        x=buttr.CoordX()-thumb.Width();
      //--- Если объект-область захвата смещён на рассчитанные координаты
      if(thumb.Move(x,y,true))
        {
         //--- устанавливаем объекту его относительные координаты
         thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
         thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
        }
      //--- Получаем указатель на базовый объект
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Проверяем выход содержимого за пределы контейнера
         base.CheckForOversize();

         //--- Рассчитываем дистанцию, на которую ползунок отстоит от левой границы полосы прокрутки (от правой грани левой кнопки со стрелкой)
         int distance=thumb.CoordX()-buttl.RightEdge();
         
         //--- Объявляем переменную, хранящую величину дистанции, бывшую перед смещением ползунка
         static int distance_last=distance;
         //--- Объявляем переменную, хранящую величину в пикселях экрана, на которую был смещён ползунок
         int shift_value=0;
         
         //--- Если значения прошлой и текущей дистанции не равны (ползунок сдвинут),
         if(distance!=distance_last)
           {
            //--- рассчитаем значение, на которое сдвинут ползунок,
            shift_value=distance_last-distance;
            //--- и впишем новую дистанцию в значение прошлой дистанции для следующего рассчёта
            distance_last=distance;
           }
         
         //--- Получаем наибольшую и наименьшую координаты правой и левой сторон содержимого базового объекта
         int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
         int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);

         //--- Получаем смещение координаты левой стороны содержимого базового объекта
         //--- относительно начальной координаты рабочей области базового объекта
         int extl=base.CoordXWorkspace()-cntt_l;
         
         //--- Рассчитываем относительную величину нужной координаты,
         //--- где должно располагаться содержимое базового объекта, смещаемое ползунком
         double x=(double)this.WidthWorkspace()*(double)distance/double(thumb.Width()!=0 ? thumb.Width() : DBL_MIN);
         
         //--- Рассчитываем необходимую величину сдвига содержимого базового объекта по вышерассчитанной координате
         int shift_need=extl-(int)::round(x);
         
         //--- Если ползунок смещён влево (положительное значение смещения)
         if(shift_value>0)
           {
            if(cntt_l+shift_need<=base.CoordXWorkspace())
               base.ShiftDependentObj(shift_need,0);
           }
         //--- Если ползунок смещён вправо (отрицательное значение смещения)
         if(shift_value<0)
           {
            if(cntt_r-shift_need>=base.RightEdgeWorkspace())
               base.ShiftDependentObj(shift_need,0);
           }
         ::ChartRedraw(this.ChartID());
        }
     }
//--- Если щелчок по любой кнопке прокрутки
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- Переносим полосу прокрутки на передний план
      this.BringToTop();
      //--- Получаем базовый объект
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Рассчитываем на сколько каждая сторона содержимого базового объекта выходит за его пределы
      base.CheckForOversize();
      //--- Получаем наибольшую и наименьшую координаты правой и левой сторон содержимого базового объекта
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Задаём количество пикселей, на которые нужно сместить содержимое базового объекта
      int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL);
      //--- Если щелчок по кнопке влево
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- Если щелчок по кнопке вправо
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Рассчитываем ширину и координаты ползунка
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

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

В методе, рассчитывающем и устанавливающем параметры области захвата, немного изменим рассчёт относительного размера окна видимой части — уйдём от рассчётов в процентах, сделав рассчёт отношения размера рабочей области контейнера к размеру ползунка (насколько один размер больше другого):

//+------------------------------------------------------------------+
//| Рассчитывает и устанавливает параметры области захвата (ползунка)|
//+------------------------------------------------------------------+
int CScrollBarHorisontal::SetThumbParams(void)
  {
//--- Получаем базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
//--- Получаем объект-область захвата (ползунок)
   CScrollBarThumb *thumb=this.GetThumb();
   if(thumb==NULL)
      return 0;
//--- Получаем размер видимой части по ширине внутри контейнера
   int base_w=base.WidthWorkspace();
//--- Рассчитываем полную ширину всех привязанных объектов
   int objs_w=base_w+base.OversizeLeft()+base.OversizeRight();
//--- Рассчитываем относительный размер окна видимой части
   double px=(double)base_w/double(objs_w!=0 ? objs_w : 1);
//--- Рассчитываем и корректируем размер ползунка относительно ширины его рабочей области (не меньше минимального размера)
   int thumb_size=(int)::floor(this.BarWorkAreaSize()*px);
   if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN)
      thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
   if(thumb_size>this.BarWorkAreaSize())
      thumb_size=this.BarWorkAreaSize();
//--- Рассчитываем координату ползунка и изменяем его размер под ранее рассчитанный
   int thumb_x=this.CalculateThumbAreaDistance(thumb_size);
   if(!thumb.Resize(thumb_size,thumb.Height(),true))
      return 0;
//--- Смещаем ползунок на рассчитанную координату X
   if(thumb.Move(this.BarWorkAreaCoord()+thumb_x,thumb.CoordY()))
     {
      thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
      thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
     }
//--- Возвращаем рассчитанный размер ползунка
   return thumb_size;
  }
//+------------------------------------------------------------------+

Помимо корректировки минимального размера ползунка, сделаем корректировку по его максимальному размеру — рассчитанное значение ширины ползунка не должно быть больше фактического размера рабочей области полосы прокрутки (дистанцией между левой и правой кнопок со стрелками).

Если теперь скомпилировать советник из прошлой статьи с текущими доработками библиотеки, то, смещая ползунок полосы прокрутки, мы будем соответственно смещать в противоположном направлении содержимое контейнера:



Теперь нужно сделать возможность прокрутки содержимого контейнера колёсиком мышки — это удобно и привычно. Во многих программах прокрутка колёсика мышки вызывает лишь вертикальное смещение содержимого контейнера (вспомним страницы браузера или текстовые редакторы). Но у нас будет немного иначе — при наведении курсора на горизонтальную полосу прокрутки и при вращении колёсика мышки, мы будем смещать горизонтально содержимое контейнера. Мне кажется это логичным. Если требуется вертикальное смещение, то колесо мышки нужно крутить в области вертикальной полосы прокрутки или содержимого контейнера. Но если курсор находится на горизонтальной полосе прокрутки, то от вращения колеса мышки логично ожидать горизонтального смещения содержимого контейнера. По крайней мере так сделано в Adobe PhotoShop, и это логично и удобно.

Для реализации этого функционала у нас, как ни странно, практически всё уже готово. У нас есть обработчик, смещающий содержимое контейнера при нажатии на кнопки со стрелками полосы прокрутки. При событии вращения колеса мышки в области полосы прокрутки мы будем отсылать событие нажатия на кнопки смещения. В зависимости от направления вращения колёсика будем отсылать либо событие щелчка по левой, либо по правой кнопке со стрелкой.

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

В защищённой секции класса объявим виртуальный обработчик события "Курсор в пределах активной области, прокручивается колёсико мышки":

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CScrollBarHorisontal(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          CGCnvElement *main_obj,CGCnvElement *base_obj,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);

//--- Обработчик события  Курсор в пределах активной области, прокручивается колёсико мышки
   virtual void      MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
public:

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

За пределами тела класса напишем его реализацию:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| прокручивается колёсико мышки                                    |
//+------------------------------------------------------------------+
void CScrollBarHorisontal::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   ENUM_WF_CONTROL_EVENT evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT);
   this.OnChartEvent(evn,lparam,dparam,sparam);
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Здесь всё просто: при прокрутке колёсика мышки в вещественном параметре dparam передаётся значение Delta счётчика вращения колеса.
Значения могут быть 120 и -120 — в зависимости от направления вращения.
Если Delta (переданное в dparam) имеет положительную величину, то событием будет щелчок по кнопке со стрелкой влево.
При отрицательном значении Delta событием будет щелчок по кнопке со стрелкой вправо
.
И это событие отправляем в обработчик событий данного объекта (полосы прокрутки).

Так как мы одним и тем же блоком кода в обработчике OnChartEvent() объекта-полосы прокрутки обрабатываем два разных события (щелчок по кнопке и вращение колёсика мышки), то определить какое было изначально событие мы сможем по значению строкового параметра sparam — при прокрутке колеса в этом параметре будет пустая строка, а при щелчке по кнопке — её наименование. В обработчике события определим какое именно было событие и зададим размер смещения содержимого контейнера в пикселях в зависимости от идентифицированного события. Для щелчка по кнопке — два пикселя смещения, для прокрутки колеса мышки — четыре:

//--- Если щелчок по любой кнопке прокрутки
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- Переносим полосу прокрутки на передний план
      this.BringToTop();
      //--- Получаем базовый объект
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Рассчитываем на сколько каждая сторона содержимого базового объекта выходит за его пределы
      base.CheckForOversize();
      //--- Получаем наибольшую и наименьшую координаты правой и левой сторон содержимого базового объекта
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Задаём количество пикселей, на которые нужно сместить содержимое базового объекта
      int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL);
      //--- Если щелчок по кнопке влево
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- Если щелчок по кнопке вправо
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Рассчитываем ширину и координаты ползунка
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

Теперь, если опять скомпилировать советник из прошлой статьи и попытаться вращать колесо мышки при нахождении курсора на полосе прокрутки, то смещение содержимого будет происходить только, если курсор не находится на ползунке:


Если же курсор попадает на ползунок, то прокрутки не будет. Почему? Да просто потому, что активным становится объект-ползунок полосы прокрутки, а у него ещё нет обработчика такого события. Добавим.

Откроем файл класса объекта-ползунка полосы прокрутки \MT5\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh и объявим в защищённой секции два обработчика событий мышки:

//+------------------------------------------------------------------+
//| Класс объекта ScrollBarThumb элементов управления WForms         |
//+------------------------------------------------------------------+
class CScrollBarThumb : public CButton
  {
private:

protected:
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах активной области, нажата кнопка мышки (любая)
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах активной области, отжата кнопка мышки (левая)
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах активной области, прокручивается колёсико мышки
   virtual void      MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах формы, прокручивается колёсико мышки
   virtual void      MouseInsideWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CScrollBarThumb(const ENUM_GRAPH_ELEMENT_TYPE type,
                                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
                             
public:

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

За пределами тела класса напишем реализацию этих двух обработчиков.

Реализация обработчика события "Курсор в пределах активной области, прокручивается колёсико мышки":

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| прокручивается колёсико мышки                                    |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
   base.BringToTop();
   ENUM_WF_CONTROL_EVENT evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT);
   base.OnChartEvent(evn,lparam,dparam,sparam);
   ::ChartRedraw(base.ChartID());
  }
//+------------------------------------------------------------------+

Логика обработчика идентична логике обработчика объекта-полосы прокрутки, рассмотренного нами выше. Но здесь мы сначала получаем указатель на базовый объект для ползунка, которым является объект-полоса прокрутки. Затем выводим полосу прокрутки на передний план (её ползунок при этом тоже будет выведен на передний план). Далее определяем нужное событие по направлению вращения колёсика мышки и отправляем соответствующее событие в обработчик событий базового объекта — полосы прокрутки. По итогу перерисовываем график для немедленного отображения изменений.


Реализация обработчика события "Курсор в пределах формы, прокручивается колёсико мышки":

Так как этот обработчик должен быть полностью идентичным вышерассмотренному, то просто вызываем именно первый обработчик, с параметрами, переданными в этот:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах формы,                      |
//| прокручивается колёсико мышки                                    |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseInsideWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+


Теперь у нас всё готово по функционалу горизонтальной полосы прокрутки. Протестируем что получилось.


Тестирование

Для теста возьмём советник из прошлой статьи без каких либо его изменений. Скомпилируем его и запустим на графике, задав в настройках при запуске "No" для режима автоматического изменения размеров контейнера под его содержимое:



Проверим работу всех составляющих созданного функционала горизонтальной полосы прокрутки:


Всё работает так, как и планировалось.



Что дальше

В следующей статье перенесём созданный функционал в элемент управления "Вертикальная полоса прокрутки".


К содержанию

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

 
DoEasy. Элементы управления (Часть 26): Дорабатываем WinForms-объект "ToolTip" и начинаем разработку индикатора выполнения "ProgressBar"
DoEasy. Элементы управления (Часть 27): Продолжаем работу над WinForms-объектом "ProgressBar"
DoEasy. Элементы управления (Часть 28): Стили полосы в элементе управления "ProgressBar"
DoEasy. Элементы управления (Часть 29): Вспомогательный элемент управления "ScrollBar"
DoEasy. Элементы управления (Часть 30): Оживляем элемент управления "ScrollBar"
DoEasy. Элементы управления (Часть 31): Прокрутка содержимого элемента управления "ScrollBar"

Прикрепленные файлы |
MQL5.zip (4646.57 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Denis Kirichenko
Denis Kirichenko | 14 окт. 2023 в 17:35
Артём, так а что библиотека, дальше будет развиваться? Имхо, нужно копать дальше. Анатолий например свою биб-ку перестал публично поддерживать по известным причинам ((
Artyom Trishkin
Artyom Trishkin | 14 окт. 2023 в 19:25
Denis Kirichenko #:
Артём, так а что библиотека, дальше будет развиваться? Имхо, нужно копать дальше. Анатолий например свою биб-ку перестал публично поддерживать по известным причинам ((

Да, конечно же будет. Взял паузу в публикациях. Есть визуальный баг, который очень неприятен на глаз - обрезанные области иногда прорисовываются с мельканием. Где-то есть лишняя преждевременная перерисовка графика. Пока не нашёл место - много взаимосвязей - одно лечишь, другое калечишь...

Как найду, исправлю, так и продолжу.

Aliaksandr Hryshyn
Aliaksandr Hryshyn | 15 окт. 2023 в 15:47
Artyom Trishkin #:

Да, конечно же будет. Взял паузу в публикациях. Есть визуальный баг, который очень неприятен на глаз - обрезанные области иногда прорисовываются с мельканием. Где-то есть лишняя преждевременная перерисовка графика. Пока не нашёл место - много взаимосвязей - одно лечишь, другое калечишь...

Как найду, исправлю, так и продолжу.

Тоже столкнулся с этой проблемой, решил так:

class CCanvas_my:public CCanvas
  {
public:
   bool              Resize(const int width,const int height);
  };

bool CCanvas_my::Resize(const int width,const int height)
  {
   if(m_rcname!=NULL && width>0 && height>0)
      if(ArrayResize(m_pixels,width*height)>0)
        {
         m_width =width;
         m_height=height;
        }
   return true;
  }

Замена стандартной функции изменения размеров канваса. Если в вашем коде всё в порядке, то лишних перерисовок не будет.

У меня такого уровня графический интерфейс, используется 10 объектов CCanvas(наследники):


Artyom Trishkin
Artyom Trishkin | 15 окт. 2023 в 16:15
Aliaksandr Hryshyn #:

Тоже столкнулся с этой проблемой, решил так:

Замена стандартной функции изменения размеров канваса. Если в вашем коде всё в порядке, то лишних перерисовок не будет.

У меня такого уровня графический интерфейс, используется 10 объектов CCanvas(наследники):


Спасибо. У меня дело нее в изменении размеров. Это в моих методах ошибка.

Возможности СhatGPT от OpenAI в контексте разработки на языках MQL4 и MQL5 Возможности СhatGPT от OpenAI в контексте разработки на языках MQL4 и MQL5
В данной статье мы будем экспериментировать и разбираться с искусственным интеллектом ChatGPT от OpenAI, для того чтобы понять его возможности с целью уменьшения времени и трудоемкости разработки ваших советников, индикаторов и скриптов. Я быстро пройдусь по данной технологии и постараюсь показать вам, как правильно её использовать для программирования на языках MQL4 и MQL5.
Нейросети — это просто (Часть 47): Непрерывное пространство действий Нейросети — это просто (Часть 47): Непрерывное пространство действий
В данной статье мы расширяем спектр задач нашего агента. В процесс обучения будут включены некоторые аспекты мани- и риск-менеджмента, которые являются неотъемлемой частью любой торговой стратегии.
Теория категорий в MQL5 (Часть 8): Моноиды Теория категорий в MQL5 (Часть 8): Моноиды
Статья продолжает серию о реализации теории категорий в MQL5. Здесь мы вводим моноиды как домен (множество), который отличает теорию категорий от других методов классификации данных за счет включения правил и элемента равнозначности.
Возможности Мастера MQL5, которые вам нужно знать (Часть 6): Преобразование Фурье Возможности Мастера MQL5, которые вам нужно знать (Часть 6): Преобразование Фурье
Преобразование Фурье, введенное Жозефом Фурье, является средством разложения сложных волновых точек данных на простые составляющие волны. Эта особенность может быть полезной для трейдеров, и именно ее мы и рассмотрим в этой статье.