English Русский
DoEasy. Steuerung (Teil 32): Horizontale ScrollBar, Scrollen mit dem Mausrad

DoEasy. Steuerung (Teil 32): Horizontale ScrollBar, Scrollen mit dem Mausrad

MetaTrader 5Beispiele | 1 September 2023, 11:33
92 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Es ist viel Zeit vergangen, seit der letzte Artikel über die Bibliothek geschrieben wurde. In diesem Zeitraum erhielt das MetaTrader 5-Handelsterminal Unterstützung für eine neue Auftrags-FüllpolitikPassive / Book or Cancel (BOC, Buchen oder Löschen), während MQL5 neue Laufzeitfehlercodes für die Matrix- und Vektor-Methoden und ONNX-Modelle erhielt. Wir werden alle diese Ergänzungen heute in die Bibliothek aufnehmen.

Im vorherigen Artikel haben wir eine WinForms Scrollbar-Objektfunktionalität erstellt, die es uns ermöglicht, den Inhalt des Containers mit Hilfe der Pfeiltasten an den Rändern der Scrollbar zu scrollen. Das reicht natürlich nicht aus, um mit diesem Objekt vollständig arbeiten zu können. Deshalb werden wir hier die Möglichkeit schaffen, den Inhalt des Containers durch Ziehen des Schiebereglers mit der Maus zu verschieben. Die dafür geschaffenen Methoden ermöglichen es uns, die Funktionen zu verbinden, die es erlaubt, den Inhalt des Containers durch Scrollen mit dem Mausrad zu verschieben.

Wenn Sie den Mauszeiger über einen Bereich mit einer horizontalen Bildlaufleiste bewegen und das Mausrad drehen, wird der Inhalt des Containers nach rechts verschoben, wenn Sie das Rad nach oben drehen (von Ihnen weg), und nach links, wenn Sie das Rad nach unten drehen (zu Ihnen hin). So funktioniert zumindest der horizontale Bildlauf in Adobe PhotoShop, und genau das werden wir auch hier tun. In Zukunft werde ich die Möglichkeit hinzufügen, in der Bibliothek die Richtung des horizontalen (und auch des vertikalen) Scrollens des Inhalts des Containers mit dem Mausrad anzugeben.


Verbesserung der Bibliotheksklassen

Vor einiger Zeit, nach der Aktualisierung des Terminals, trat ein ärgerlicher Fehler auf - die Bibliotheks- und Beispieldateien für Artikel wurden nicht mehr kompiliert. Der Grund war meine Unachtsamkeit bei der Angabe des privaten Bereichs für einige Methoden der Klasse CTrading in der Datei Trading.mqh. Dementsprechend konnte die Klasse CTradingControl, die eine abgeleitete Klasse von CTrading ist, nicht auf diese Methoden zugreifen. Früher hat der Compiler diesen Fehler von mir geräuschlos ignoriert, aber nach dem Update begann er, ihn zu erkennen. Die Lösung ist einfach - wir müssen einen geschützten Abschnitt für private Methoden angeben, die von abgeleiteten Klassen nicht zugänglich sind, damit sie in der abgeleiteten Klasse verfügbar werden.

Öffnen Sie die Datei \MQL5\Include\DoEasy\Trading.mqh und geben Sie den geschützten Abschnitt für die Methode SetPrices() an, der sich im privaten Abschnitt befindet:

//--- Set the desired sound for a trading object
   void                 SetSoundByMode(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,CTradeObj *trade_obj);

protected:
//--- Set trading request prices
   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:
//--- Return the flag checking the permission to trade by (1) StopLoss, (2) TakeProfit distance, (3) order placement level by a StopLevel-based price
   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);

Nachdem wir die Methode deklariert haben, geben wir den privaten Abschnitt an seinen Platz zurück, damit andere Methoden nicht in den geschützten Bereich fallen.

Dasselbe habe ich bereits vor einiger Zeit mit zwei anderen Methoden in derselben Klasse gemacht:

//--- Return the error handling method
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(const uint result_code);
//--- Correct errors
   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) Open a position, (2) place a pending order

   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                                   
//--- Return the request object index in the list by (1) ID,
//--- (2) order ticket, (3) position ticket in the request
   int                  GetIndexPendingRequestByID(const uchar id);
   int                  GetIndexPendingRequestByOrder(const ulong ticket);
   int                  GetIndexPendingRequestByPosition(const ulong ticket);

public:

Wenn Sie beim Kompilieren der Beispieldateien für die vorangegangenen Artikel plötzlich auf den Fehler stoßen, dass der Zugriff auf private Methoden der Klasse CTrading verweigert wird, können Sie das Problem selbst beheben, indem Sie die oben genannten Verbesserungen umsetzen.


Da es jetzt eine neue Auftragsausführungspolitik und neue Laufzeitfehlercodes gibt, müssen wir eine Beschreibung dieser Neuerungen zu den Arrays der Bibliothekstextmeldungen hinzufügen.

Wir öffnen die Datei \MQL5\Include\DoEasy\Data.mqh und fügen den neuen Bibliotheksnachrichtenindex über die neue Ausführungsrichtlinie hinzu:

   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_FOK,            // Order is executed in the specified volume only, otherwise it is canceled
   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_IOK,            // Order is filled within an available volume, while the unfilled one is canceled
   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK,            // Order is placed in the market depth and cannot be executed immediately. If the order can be executed immediately when placed, it is canceled
   MSG_LIB_TEXT_REQUEST_ORDER_FILLING_RETURN,         // Order is filled within an available volume, while the unfilled one remains

und die Textnachrichten (Russisch und Englisch), die dem neu hinzugefügten Index entsprechen:

   {"Ордер исполняется исключительно в указанном объеме, иначе отменяется (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)"},


Hinzufügen der neuen, in MQL5 implementierten Fehlercodes (von 4020 bis 4025) zum Array der Laufzeit-Fehlermeldungen (Fehlercodes 0, 4001 - 4019):

//+------------------------------------------------------------------+
//| Array of execution time error messages (0, 4001 - 4025)          |
//| (1) in user's country language                                   |
//| (2) in the international language                                |
//+------------------------------------------------------------------+
string messages_runtime[][TOTAL_LANG]=
  {
   {"Операция выполнена успешно","Operation successful"},                                                                          // 0
   {"Неожиданная внутренняя ошибка","Unexpected internal error"},                                                                                  // 4001
   {"Ошибочный параметр при внутреннем вызове функции клиентского терминала","Wrong parameter in inner call of client terminal function"}, // 4002
   {"Ошибочный параметр при вызове системной функции","Wrong parameter when calling system function"},                                         // 4003
   {"Недостаточно памяти для выполнения системной функции","Not enough memory to perform 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 relocation of string"},                                          // 4008
   {"Неинициализированная строка","Not initialized string"},                                                                                       // 4009
   {"Неправильное значение даты и/или времени","Invalid date and/or time"},                                                                        // 4010
   {"Общее число элементов в массиве не может превышать 2147483647","Total amount of elements in array cannot exceed 2147483647"},             // 4011
   {"Ошибочный указатель","Wrong pointer"},                                                                                                        // 4012
   {"Ошибочный тип указателя","Wrong type of pointer"},                                                                                            // 4013
   {"Системная функция не разрешена для вызова","Function not allowed for call"},                                                               // 4014
   {"Совпадение имени динамического и статического ресурсов","Names of dynamic and static resource match"},                            // 4015
   {"Ресурс с таким именем в EX5 не найден","Resource with this name not found in EX5"},                                                  // 4016
   {"Неподдерживаемый тип ресурса или размер более 16 MB","Unsupported resource type or its size exceeds 16 Mb"},                                  // 4017
   {"Имя ресурса превышает 63 символа","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
  };
//+------------------------------------------------------------------+

Einige Fehlercodes werden (noch?) nicht verwendet, aber ihre Werte befinden sich direkt zwischen den verwendeten Fehlercodes. Um schnell zu verstehen, dass ein bestimmter Fehlercode jetzt in MQL5 verwendet wird, fügen wir einfach den Code selbst zur Meldung des unbekannten Fehlercodes hinzu.

Wenn die Bibliothek „Unbekannter Fehlercode“ anzeigt (wie jetzt, bevor Änderungen vorgenommen werden), ist es schwierig zu verstehen, welcher Code aus dem zuvor nicht verwendeten Code nun beteiligt ist. Durch Hinzufügen eines Fehlercodes zu der Meldung erhalten wir sofort eine Meldung über einen unbekannten Fehlercode mit Angabe der Nummer dieses Fehlers. Es genügt, die aktualisierte Hilfe zu öffnen und die Bibliothek mit einer Beschreibung dieses Fehlers zu ergänzen, so wie wir es jetzt tun. Solche Zusätze in dieser Datei werden bereits zu allen Meldungen über einen unbekannten Fehlercode hinzugefügt.

Für die Fehlercodes der Matrix- und Vektormethoden sowie der ONNX-Modelle werden wir zwei neue Arrays erstellen, da die Anfangswerte der Codes bei 5700 und 5800 beginnen und es nicht praktisch und nicht optimal ist, den „leeren Raum“ mit unbenutzten Codes mit fast tausend identischen Meldungen über einen unbekannten Code zu füllen. Außerdem verwendet die Bibliothek aus demselben Grund verschiedene Arrays für verschiedene Gruppen von Fehlercodes, und wir fügen zwei weitere Arrays nach dem Array der Laufzeitfehlermeldungen mit den Codes 5601 — 5626 hinzu:

//+------------------------------------------------------------------+
//| Array of execution time error messages  (5601 - 5626)            |
//| (Working with databases)                                         |
//| (1) in user's country language                                   |
//| (2) in the international language                                |
//+------------------------------------------------------------------+
string messages_runtime_sqlite[][TOTAL_LANG]=
  {
   {"Общая ошибка","Generic error"},                                                                                                               // 5601
   {"Внутренняя логическая ошибка в SQLite","SQLite internal logic error"},                                                                        // 5602
   {"Отказано в доступе","Access denied"},                                                                                                         // 5603
   {"Процедура обратного вызова запросила прерывание","Callback routine requested abort"},                                                         // 5604
   {"Файл базы данных заблокирован","Database file locked"},                                                                                       // 5605
   {"Таблица в базе данных заблокирована ","Database table locked"},                                                                               // 5606
   {"Сбой malloc()","Insufficient memory for completing operation"},                                                                               // 5607
   {"Попытка записи в базу данных, доступной только для чтения ","Attempt to write to readonly database"},                                         // 5608
   {"Операция прекращена с помощью sqlite3_interrupt() ","Operation terminated by sqlite3_interrupt()"},                                           // 5609
   {"Ошибка дискового ввода-вывода","Disk I/O error"},                                                                                             // 5610
   {"Образ диска базы данных испорчен","Database disk image corrupted"},                                                                           // 5611
   {"Неизвестный код операции в sqlite3_file_control()","Unknown operation code in sqlite3_file_control()"},                                       // 5612
   {"Ошибка вставки, так как база данных заполнена ","Insertion failed because database is full"},                                                 // 5613
   {"Невозможно открыть файл базы данных","Unable to open the database file"},                                                                     // 5614
   {"Ошибка протокола блокировки базы данных ","Database lock protocol error"},                                                                    // 5615
   {"Только для внутреннего использования","Internal use only"},                                                                                   // 5616
   {"Схема базы данных изменена","Database schema changed"},                                                                                       // 5617
   {"Строка или BLOB превышает ограничение по размеру","String or BLOB exceeds size limit"},                                                       // 5618
   {"Прервано из-за нарушения ограничения","Abort due to constraint violation"},                                                                   // 5619
   {"Несоответствие типов данных","Data type mismatch"},                                                                                           // 5620
   {"Ошибка неправильного использования библиотеки","Library used incorrectly"},                                                                   // 5621
   {"Использование функций операционной системы, не поддерживаемых на хосте","Uses OS features not supported on host"},                            // 5622
   {"Отказано в авторизации","Authorization denied"},                                                                                              // 5623
   {"Не используется ","Not used "},                                                                                                               // 5624
   {"2-й параметр для sqlite3_bind находится вне диапазона","Bind parameter error, incorrect index"},                                              // 5625
   {"Открытый файл не является файлом базы данных","File opened that is not database file"},                                                       // 5626
  };
//+------------------------------------------------------------------+
//| Array of execution time error messages  (5700 - 5706)            |
//| (Matrix and vector methods)                                      |
//| (1) in user's country language                                   |
//| (2) in the international language                                |
//+------------------------------------------------------------------+
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
  };  
//+------------------------------------------------------------------+
//| Array of execution time error messages  (5800 - 5808)            |
//| (ONNX models)                                                    |
//| (1) in user's country language                                   |
//| (2) in the international language                                |
//+------------------------------------------------------------------+
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__


Die Klasse CMessage ermöglicht die Anzeige von Bibliotheksmeldungen, deren Texte in Message-Arrays gespeichert sind. Wir müssen neue Textnachrichten hinzufügen, einschließlich derjenigen aus den heute erstellten neuen Arrays.

Wir öffnen die Datei \MQL5\Include\DoEasy\Services\Message.mqh und fügen die Handhabung der neu hinzugefügten Arrays in die Methode GetTextByID() ein. Die Methode schreibt die Textnachricht, die durch den an die Methode übergebenen Nachrichtenindex gespeichert ist, in die Variable m_text.

Für die Laufzeitfehler (0, 4001 — 4019) erweitern wir die Anzahl der behandelten Codes von 4019 auf 4025 und schreiben die neuen Textnachrichten aus den Arrays für die Fehlercodes von Matrix- und -Vektormethoden und den ONNX-Modelle:

//+------------------------------------------------------------------+
//| Get messages from the text array by an ID                        |
//+------------------------------------------------------------------+
void CMessage::GetTextByID(const int msg_id)
  {
   CMessage::m_text=
     (
      //--- Runtime errors (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]                   :
      //--- Runtime errors (Charts 4101 - 4116)
      msg_id>4100 && msg_id<4117    ?  messages_runtime_charts[msg_id-4101][m_lang_num]            :
      //--- Runtime errors (Graphical objects 4201 - 4205)
      msg_id>4200 && msg_id<4206    ?  messages_runtime_graph_obj[msg_id-4201][m_lang_num]         :
      //--- Runtime errors (MarketInfo 4301 - 4305)
      msg_id>4300 && msg_id<4306    ?  messages_runtime_market[msg_id-4301][m_lang_num]            :
      //--- Runtime errors (Access to history 4401 - 4407)
      msg_id>4400 && msg_id<4408    ?  messages_runtime_history[msg_id-4401][m_lang_num]           :
      //--- Runtime errors (Global Variables 4501 - 4524)
      msg_id>4500 && msg_id<4525    ?  messages_runtime_global[msg_id-4501][m_lang_num]            :
      //--- Runtime errors (Custom indicators 4601 - 4603)
      msg_id>4600 && msg_id<4604    ?  messages_runtime_custom_indicator[msg_id-4601][m_lang_num]  :
      //--- Runtime errors (Account 4701 - 4758)
      msg_id>4700 && msg_id<4759    ?  messages_runtime_account[msg_id-4701][m_lang_num]           :
      //--- Runtime errors (Indicators 4801 - 4812)
      msg_id>4800 && msg_id<4813    ?  messages_runtime_indicator[msg_id-4801][m_lang_num]         :
      //--- Runtime errors (Market depth 4901 - 4904)
      msg_id>4900 && msg_id<4905    ?  messages_runtime_books[msg_id-4901][m_lang_num]             :
      //--- Runtime errors (File operations 5001 - 5027)
      msg_id>5000 && msg_id<5028    ?  messages_runtime_files[msg_id-5001][m_lang_num]             :
      //--- Runtime errors (Converting strings 5030 - 5044)
      msg_id>5029 && msg_id<5045    ?  messages_runtime_string[msg_id-5030][m_lang_num]            :
      //--- Runtime errors (Working with arrays 5050 - 5063)
      msg_id>5049 && msg_id<5064    ?  messages_runtime_array[msg_id-5050][m_lang_num]             :
      //--- Runtime errors (Working with OpenCL 5100 - 5114)
      msg_id>5099 && msg_id<5115    ?  messages_runtime_opencl[msg_id-5100][m_lang_num]            :
      //--- Runtime errors (Working with databases 5120 - 5130)
      msg_id>5119 && msg_id<5131    ?  messages_runtime_database[msg_id-5120][m_lang_num]          :
      //--- Runtime errors (Working with WebRequest() 5200 - 5203)
      msg_id>5199 && msg_id<5204    ?  messages_runtime_webrequest[msg_id-5200][m_lang_num]        :
      //--- Runtime errors (Working with network (sockets) 5270 - 5275)
      msg_id>5269 && msg_id<5276    ?  messages_runtime_netsocket[msg_id-5270][m_lang_num]         :
      //--- Runtime errors (Custom symbols 5300 - 5310)
      msg_id>5299 && msg_id<5311    ?  messages_runtime_custom_symbol[msg_id-5300][m_lang_num]     :
      //--- Runtime errors (Economic calendar 5400 - 5402)
      msg_id>5399 && msg_id<5403    ?  messages_runtime_calendar[msg_id-5400][m_lang_num]          :
      //--- Runtime errors (Working with databases 5601 - 5626)
      msg_id>5600 && msg_id<5627    ?  messages_runtime_sqlite[msg_id-5601][m_lang_num]            :
      //--- Runtime errors (Matrix and vector methods 5700 - 5706)
      msg_id>5699 && msg_id<5707    ?  messages_runtime_matrix_vector[msg_id-5700][m_lang_num]     :
      //--- Runtime errors (ONNX models 5800 - 5808)
      msg_id>5799 && msg_id<5809    ?  messages_runtime_onnx[msg_id-5800][m_lang_num]              :
      //--- Trade server return codes (10004 - 10045)
      msg_id>10003 && msg_id<10047  ?  messages_ts_ret_code[msg_id-10004][m_lang_num]              :
     
     #else // MQL4
      msg_id>0    && msg_id<10      ?  messages_ts_ret_code_mql4[msg_id][m_lang_num]               :
      msg_id>63   && msg_id<66      ?  messages_ts_ret_code_mql4[msg_id-54][m_lang_num]            :
      msg_id>127  && msg_id<151     ?  messages_ts_ret_code_mql4[msg_id-116][m_lang_num]           :
      msg_id<4000                   ?  messages_ts_ret_code_mql4[26][m_lang_num]                   :
      //--- MQL4 runtime errors (4000 - 4030)
      msg_id<4031                   ?  messages_runtime_4000_4030[msg_id-4000][m_lang_num]         :
      //--- MQL4 runtime errors (4050 - 4075)
      msg_id>4049 && msg_id<4076    ?  messages_runtime_4050_4075[msg_id-4050][m_lang_num]         :
      //--- MQL4 runtime errors (4099 - 4112)
      msg_id>4098 && msg_id<4113    ?  messages_runtime_4099_4112[msg_id-4099][m_lang_num]         :
      //--- MQL4 runtime errors (4200 - 4220)
      msg_id>4199 && msg_id<4221    ?  messages_runtime_4200_4220[msg_id-4200][m_lang_num]         :
      //--- MQL4 runtime errors (4250 - 4266)
      msg_id>4249 && msg_id<4267    ?  messages_runtime_4250_4266[msg_id-4250][m_lang_num]         :
      //--- MQL4 runtime errors (5001 - 5029)
      msg_id>5000 && msg_id<5030    ?  messages_runtime_5001_5029[msg_id-5001][m_lang_num]         :
      //--- MQL4 runtime errors (5200 - 5203)
      msg_id>5199 && msg_id<5204    ?  messages_runtime_5200_5203[msg_id-5200][m_lang_num]         :
     #endif 
      
      //--- Library messages (ERR_USER_ERROR_FIRST)
      msg_id>ERR_USER_ERROR_FIRST-1 ?  messages_library[msg_id-ERR_USER_ERROR_FIRST][m_lang_num]   : 
      messages_library[MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE-ERR_USER_ERROR_FIRST][m_lang_num]
     );
  }
//+------------------------------------------------------------------+


Einige häufig in der Bibliothek verwendete Funktionen sind in \MQL5\Include\DoEasy\Services\DELib.mqh festgelegt.

Der Funktion, die die Beschreibung des Auftragsausfüllungsmodus zurückgibt, fügen wir die Behandlung der BoC-Ausfüllungsart hinzu:

//+------------------------------------------------------------------+
//| Return the order filling mode description                        |
//+------------------------------------------------------------------+
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)
     );
  }
//+------------------------------------------------------------------+

Wenn die Ausführungsrichtlinie nun Buchen oder Löschen (BoC) lautet, gibt die Funktion den Text zurück, der dem Index MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK im Nachrichten-Array entspricht, das wir heute hinzugefügt haben.


Wenn der EA aus dem Chart entfernt wurde, wurde im Log eine Fehlermeldung über das Schließen der Markttiefe angezeigt. Es gab jedoch weder eine Fehlerbeschreibung noch einen Fehlercode. Ich halte dieses Verhalten für falsch - es ist unklar, was hier passiert. Um die Situation zu korrigieren, werden wir die Methode, die die Markttiefe in der Symbolobjektklasse CSymbol schließt, in \MT5\MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh finalisieren.

Wir ermitteln den Fehlercode in dem Codeblock, der den Fehler beim Schließen der Markttiefe behandelt, und zeigen ihn im Protokoll an, nachdem wir den Fehler beschrieben haben:

//+-------------------------------------+
//| Close the market depth              |
//+-------------------------------------+
bool CSymbol::BookClose(void)
  {
//--- If the DOM subscription flag is off, subscription is disabled (or not enabled yet). Return 'true'
   if(!this.m_book_subscribed)
      return true;
//--- Save the result of unsubscribing from the DOM
   bool res=( #ifdef __MQL5__ ::MarketBookRelease(this.m_name) #else true #endif );
//--- If unsubscribed successfully, reset the DOM subscription flag and write the status to the object property
   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 the result of unsubscribing from DOM
   return res;
  }
//+------------------------------------------------------------------+

Jetzt wird klar, warum der Fehler beim Versuch, die Markttiefe zu schließen, aufgetreten ist.


Im Moment haben wir die Möglichkeit, den Inhalt des Containers zu verschieben, wenn wir auf die Pfeilschaltflächen im Scrollbar-Objekt klicken. Bei der Erstellung haben wir beschlossen, dass der Inhalt des Containers um zwei Bildschirmpixel verschoben werden soll. Dies ist völlig ausreichend für eine komfortable Positionierung des scrollbaren Inhalts mit den Bildlauftasten. Aber hier werde ich zwei weitere Möglichkeiten zum Scrollen schaffen - das Verschieben des Schiebereglers mit der Maus und das Scrollen mit dem Mausrad.

Wenn wir beim Verschieben des Schiebereglers keinen vorgegebenen Wert brauchen, um den der Inhalt des Containers verschoben wird (der Offset-Wert wird um den Betrag gesetzt, um den der Schieberegler verschoben wurde), dann müssen wir für das Scrollen mit dem Mausrad bestimmen, um wie viele Pixel der Inhalt des Containers bei einer einmaligen Auslösung des Mausradzählers verschoben wird. Der Zähler ist diskret und sendet ein Ereignis, wenn der Delta-Wert 120 oder -120 erreicht (je nach Richtung des Scrollrads). Für den Bildlauf mit dem Rad setzen wir den Wert auf vier Pixel.

Wir öffnen die Datei \MT5\MQL5\Include\DoEasy\Defines.mqh und legen den Namen der Makrosubstitution fest, die für den Offset-Wert verantwortlich ist, indem wir auf die Schaltfläche mit dem Pfeil klicken (DEF_CONTROL_SCROLL_BAR_SCROLL_STEP). Außerdem fügen wir eine neue Makro-Substitution hinzu, die für den Umfang der Verschiebung beim Scrollen mit dem Mausrad verantwortlich ist:

#define DEF_CONTROL_SCROLL_BAR_WIDTH                  (11)                 // Default ScrollBar control width
#define DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN         (8)                  // Minimum size of the capture area (slider)
#define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK      (2)                  // Shift step in pixels of the container content when scrolling by clicking the button
#define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL      (4)                  // Shift step in pixels of the container content when scrolling with the mouse wheel 
#define DEF_CONTROL_CORNER_AREA                       (4)                  // Number of pixels defining the corner area to resize
#define DEF_CONTROL_LIST_MARGIN_X                     (1)                  // Gap between columns in ListBox controls
#define DEF_CONTROL_LIST_MARGIN_Y                     (0)                  // Gap between rows in ListBox controls


Alle vorläufigen Korrekturen sind nun vorgenommen worden, jetzt müssen wir noch die Funktionen der Bildlaufleiste fertigstellen.

Zunächst soll es möglich sein, den Inhalt des Containers durch Verschieben der Bildlaufleiste mit der Maus zu verschieben. Die Logik ist wie folgt: Die Bildlaufleiste zeigt den Container und seinen Inhalt in verkleinerter Form an. Die Größe des Schiebereglers zeigt die Größe des sichtbaren Teils des Containers an, und die Bildlaufleiste (zwischen den Rändern der Pfeilschaltflächen) zeigt den Inhalt des Containers an, der darüber hinausgeht. Wir haben einen Ausgangspunkt für unsere Berechnungen - den Betrag, um den der Schieberegler vom rechten Rand der linken Pfeiltaste aus bewegt wird. Wir haben auch die Größe des Schiebereglers und die Größe des sichtbaren Teils des Containers. Wenn wir wissen, um wie viel der sichtbare Teil des Containers größer ist als die Größe des Schiebereglers und um wie viel der Schieberegler verschoben wurde, können wir berechnen, um wie viel der Inhalt des Containers verschoben werden muss:

  1. die Breite des sichtbaren Teils des Containers (W1)
  2. die Breite des Schiebereglers (W2)
  3. wie viel der sichtbare Teil des Containers größer ist als der Schieber (X = W1 / W2)
  4. Wie stark der Schieberegler verschoben wird (S1)
  5. Wie viel der Inhalt des Behälters verschoben werden soll (S1 * X)

Wenn wir also das Verhältnis zwischen der Größe des Schiebereglers und dem sichtbaren Teil des Containers kennen und wissen, um wie viel der Schieberegler verschoben wurde, können wir den Betrag berechnen, um den der Inhalt des Containers verschoben werden sollte. Wird in diesem Fall der Schieberegler nach links verschoben, so sollte der Inhalt des Containers nach rechts verschoben werden und umgekehrt.

Wir öffnen die Datei der Klasse der horizontalen Bildlaufleiste \MT5\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh und schreiben den Codeblock, der diese Berechnungen durchführt und den Inhalt des Containers verschiebt, in seine Ereignisbehandlung:

//+-------------------------------------+
//| Event handler                       |
//+-------------------------------------+
void CScrollBarHorisontal::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Get the pointers to control objects of the scrollbar
   CArrowLeftButton  *buttl=this.GetArrowButtonLeft();
   CArrowRightButton *buttr=this.GetArrowButtonRight();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttl==NULL || buttr==NULL || thumb==NULL)
      return;
//--- If the event ID is an object movement
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Declare the variables for the coordinates of the capture area
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Set the Y coordinate equal to the Y coordinate of the control element
      y=this.CoordY()+this.BorderSizeTop();
      //--- Adjust the X coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons
      if(x<buttl.RightEdge())
        x=buttl.RightEdge();
      if(x>buttr.CoordX()-thumb.Width())
        x=buttr.CoordX()-thumb.Width();
      //--- If the capture area object is shifted by the calculated coordinates
      if(thumb.Move(x,y,true))
        {
         //--- set the object relative coordinates
         thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
         thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
        }
      //--- Get the pointer to the base object
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Check if the content goes beyond the container
         base.CheckForOversize();

         //--- Calculate the distance the slider is from the left border of the scrollbar (from the right side of the left arrow button)
         int distance=thumb.CoordX()-buttl.RightEdge();
         
         //--- Declare a variable that stores the distance value before the slider shift
         static int distance_last=distance;
         //--- Declare a variable that stores the value in screen pixels the slider was shifted by
         int shift_value=0;
         
         //--- If the values of the past and current distances are not equal (the slider is shifted),
         if(distance!=distance_last)
           {
            //--- calculate the value the slider is shifted by
            shift_value=distance_last-distance;
            //--- and enter the new distance into the value of the previous distance for the next calculation
            distance_last=distance;
           }
         
         //--- Get the largest and smallest coordinates of the right and left sides of the base object content
         int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
         int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);

         //--- Get the coordinate offset of the left side of the base object content 
         //--- relative to the initial coordinate of the base object working area
         int extl=base.CoordXWorkspace()-cntt_l;
         
         //--- Calculate the relative value of the desired coordinate,
         //--- where the contents of the base object, shifted by the slider, should be located
         double x=(double)this.WidthWorkspace()*(double)distance/double(thumb.Width()!=0 ? thumb.Width() : DBL_MIN);
         
         //--- Calculate the required shift value of the base object content along the above calculated coordinate 
         int shift_need=extl-(int)::round(x);
         
         //--- If the slider is shifted to the left (positive shift value)
         if(shift_value>0)
           {
            if(cntt_l+shift_need<=base.CoordXWorkspace())
               base.ShiftDependentObj(shift_need,0);
           }
         //--- If the slider is shifted to the right (negative shift value) 
         if(shift_value<0)
           {
            if(cntt_r-shift_need>=base.RightEdgeWorkspace())
               base.ShiftDependentObj(shift_need,0);
           }
         ::ChartRedraw(this.ChartID());
        }
     }
//--- If any scroll button is clicked
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Get the base object
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Calculate how much each side of the content of the base object goes beyond its borders
      base.CheckForOversize();
      //--- Get the largest and smallest coordinates of the right and left sides of the base object content
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Set the number of pixels, by which the content of the base object should be shifted
      int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL);
      //--- If the left button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- If the right button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Calculate the width and coordinates of the slider
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

Fast jede Zeile des hinzugefügten Codeblocks wird von einem Kommentar begleitet. Wenn Sie den Schieberegler nach links bewegen, wird der Inhalt des Containers nach rechts verschoben. Gleichzeitig muss die Verschiebung des Containerinhalts begrenzt werden, damit der linke Rand des Inhalts nicht nach rechts vom linken Rand des Arbeitsbereichs des Containers - dem Bereich, in dem der Inhalt des Containers sichtbar ist - verschoben wird. Genauso wie beim Verschieben des Schiebereglers nach rechts, verschieben wir den Inhalt des Containers nach links, und gleichzeitig sollte der rechte Rand des Inhalts nicht nach links vom rechten Rand des Arbeitsbereichs des Containers gehen. Dazu werden die berechneten Koordinaten überprüft, bevor der Inhalt des Containers verschoben wird.

In der Methode, die die Parameter des Erfassungsbereichs berechnet und festlegt, werden wir die Berechnung der relativen Größe des sichtbaren Teilfensters leicht ändern, indem wir von der Berechnung in % wegkommen. Stattdessen berechnen wir das Verhältnis zwischen der Größe des Container-Arbeitsbereichs und der Größe des Schiebereglers (wie viel ist die eine Größe größer als die andere):

//+------------------------------------------------------------------+
//| Calculate and set the parameters of the capture area (slider)    |
//+------------------------------------------------------------------+
int CScrollBarHorisontal::SetThumbParams(void)
  {
//--- Get the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
//--- Get the capture area object (slider)
   CScrollBarThumb *thumb=this.GetThumb();
   if(thumb==NULL)
      return 0;
//--- Get the width size of the visible part inside the container
   int base_w=base.WidthWorkspace();
//--- Calculate the total width of all attached objects
   int objs_w=base_w+base.OversizeLeft()+base.OversizeRight();
//--- Calculate the relative size of the visible part window
   double px=(double)base_w/double(objs_w!=0 ? objs_w : 1);
//--- Calculate and adjust the size of the slider relative to the width of its workspace (not less than the minimum size)
   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();
//--- Calculate the coordinate of the slider and change its size to match the previously calculated one
   int thumb_x=this.CalculateThumbAreaDistance(thumb_size);
   if(!thumb.Resize(thumb_size,thumb.Height(),true))
      return 0;
//--- Shift the slider by the calculated X coordinate
   if(thumb.Move(this.BarWorkAreaCoord()+thumb_x,thumb.CoordY()))
     {
      thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
      thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
     }
//--- Return the calculated slider size
   return thumb_size;
  }
//+------------------------------------------------------------------+

Abgesehen von der Anpassung der Mindestgröße des Schiebereglers sollten wir auch seine maximale Größe ändern — der berechnete Wert der Breite des Schiebereglers sollte die tatsächliche Größe des Arbeitsbereichs der Bildlaufleiste (den Abstand zwischen den Pfeiltasten links und rechts) nicht überschreiten.

Wenn wir nun den EA aus dem vorigen Artikel mit den aktuellen Bibliotheksverbesserungen kompilieren, dann werden wir den Inhalt des Containers entsprechend in die entgegengesetzte Richtung verschieben, indem wir den Schieberegler der Bildlaufleiste bewegen:



Nun müssen wir es ermöglichen, den Inhalt des Containers mit dem Mausrad zu verschieben - das ist bequem und gewohnt. In vielen Programmen bewirkt das Scrollen mit dem Mausrad nur eine vertikale Verschiebung des Containerinhalts (z. B. in Browser-Seiten oder Texteditoren). In unserem Fall wird es jedoch etwas anders sein - wenn wir mit dem Mauszeiger über die horizontale Bildlaufleiste fahren und das Mausrad drehen, wird der Inhalt des Containers horizontal verschoben. Das erscheint mir logisch. Wenn eine vertikale Verschiebung erforderlich ist, sollte das Mausrad im Bereich der vertikalen Bildlaufleiste oder des Containerinhalts verschoben werden. Befindet sich der Cursor jedoch auf einer horizontalen Bildlaufleiste, dann ist es logisch, dass die Drehung des Mausrads eine horizontale Verschiebung des Containerinhalts zur Folge hat. Zumindest wird dies in Adobe PhotoShop so gehandhabt, und es ist logisch und praktisch.

Fast alles ist bereit, um diese Funktion zu implementieren. Wir haben ein Handhabe, die den Inhalt des Containers verschiebt, wenn die Pfeiltasten auf der Bildlaufleiste angeklickt werden. Wenn das Mausrad im Bereich der Bildlaufleiste blättert, wird ein Klick-Ereignis an die Umschalttasten gesendet. Je nachdem, in welche Richtung das Rad scrollt, wird entweder ein Klick-Ereignis auf die linke oder rechte Pfeiltaste gesendet.

Zusammen mit der Ereignis-ID werden auch einige Parameter an die Ereignisbehandlung übergeben - Integer, Real und String. Wenn ein Pfeilschaltflächenobjekt angeklickt wird, wird der Name des Objekts als String-Parameter übergeben. Wenn das Mausrad gescrollt wird, wird der String-Parameter nicht verwendet - dort steht ein leere Zeichenkette. Dies verwenden wir in der Ereignisbehandlung bei einem Klick auf die Schaltfläche, um zu bestimmen, welche Art von Ereignis es war - entweder eine Schaltfläche klicken oder Mausrad Scrollen. Anhand des erkannten Ereignisses bestimmen wir, um wie viel der Inhalt des Containers verschoben werden soll. Beim Klicken auf die Pfeiltaste - um zwei Pixel und beim Scrollen mit dem Mausrad - um vier.

Wir deklarieren im geschützten Bereich der Klasse die virtuelle Ereignisbehandlung „Der Cursor befindet sich im aktiven Bereich, das Mausrad wird gerollt“:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     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);

//--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler
   virtual void      MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
public:

Alle Klassen von Grafikelementen sind Abkömmlinge der CForm-Formularobjektklasse. Sie enthält bereits leere Ereignisbehandlungen für verschiedene Mausereignisse, die in abgeleiteten Klassen neu definiert werden müssen. Hier definieren wir das Ereignis der Mausradbewegung innerhalb des aktiven Bereichs dieses Objekts neu.

Schreiben wir die Implementierung außerhalb des Klassenkörpers schreiben:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+

Hier ist alles ganz einfach. Beim Scrollen des Mausrads wird der Delta-Wert des Rad-Scrolling-Zählers im Parameter dparam real übergeben.
Die Werte können 120 und -120 betragen, je nach Richtung des Bildlaufs.
Wenn Delta (an dparam übergeben) positiv ist, wird das Klicken auf die rechte Pfeiltaste als Ereignis betrachtet.
Wenn das Delta negativ ist, wird das Klicken auf die rechte Pfeiltaste als Ereignis
betrachtet.
Wir senden dieses Ereignis an die Ereignisbehandlung des Objekts (Bildlaufleiste).

Da wir zwei verschiedene Ereignisse (Klicken der Schaltfläche und Rollen des Mausrads) mit demselben Codeblock in der Ereignisbehandlung von OnChartEvent() des Scrollbar-Objekts behandeln, können wir das ursprüngliche Ereignis anhand des Werts des Parameters sparam bestimmen — wenn das Rad gerollt wird, enthält dieser Parameter eine leere Zeichenkette, und wenn die Schaltfläche angeklickt wird, enthält er ihren Namen. Wir definieren in der Ereignisbehandlung das Ereignis und legen die Größe der Verschiebung des Containerinhalts in Pixeln in Abhängigkeit vom identifizierten Ereignis fest. Für einen Tastenklick — zwei Offset-Pixel , für das Scrollen mit dem Mausrad — vier:

//--- If any scroll button is clicked
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Get the base object
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Calculate how much each side of the content of the base object goes beyond its borders
      base.CheckForOversize();
      //--- Get the largest and smallest coordinates of the right and left sides of the base object content
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Set the number of pixels, by which the content of the base object should be shifted
      int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL);
      //--- If the left button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- If the right button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Calculate the width and coordinates of the slider
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

Wenn wir nun den EA aus dem vorigen Artikel erneut kompilieren und versuchen, mit dem Mausrad zu rollen, während sich der Cursor auf der Bildlaufleiste befindet, dann verschiebt sich der Inhalt nur, wenn sich der Cursor nicht auf dem Schieberegler befindet:


Wenn sich der Cursor über dem Schieberegler befindet, findet kein Bildlauf statt. Warum? Einfach weil das Scrollbar-Slider-Objekt aktiv wird und es noch keine Behandlung eines solchen Ereignisses gibt. Fügen wir das hinzu.

Wir öffnen die Klassendatei des Scrollbar-Slider-Objekts \MT5\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh und deklarieren zwei Ereignisse der Maus im geschützten Bereich:

//+------------------------------------------------------------------+
//| ScrollBarThumb object class of the WForms controls               |
//+------------------------------------------------------------------+
class CScrollBarThumb : public CButton
  {
private:

protected:
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler
   virtual void      MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the form, the mouse wheel is being scrolled' event handler
   virtual void      MouseInsideWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Protected constructor with object type, chart ID and subwindow
                     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:

Warum brauchen wir zwei Bearbeiter? Jedes grafische Objekt der Bibliothek enthält Bereiche, die für die verschiedenen Zustände verantwortlich sind und Ereignisse senden, die diesen Bereichen entsprechen. Der aktive Bereich des Schiebereglerobjekts entlang des Umfangs ist ein Pixel kleiner als die Größe des Schiebereglers selbst. Wenn sich der Cursor innerhalb des aktiven Bereichs befindet, ist die Ereignisbehandlung für den aktiven Bereich des Objekts aktiv, und wenn sich der Cursor am äußersten Rand des Schiebereglers befindet, fällt er in eine andere Zone, in der die Ereignisbehandlung für den Cursor innerhalb des Formulars funktioniert.

Lassen Sie uns die Implementierung dieser beiden Ereignisbehandlungen außerhalb des Klassenkörpers schreiben.

Implementierung der Ereignisbehandlung von „Der Cursor befindet sich im aktiven Bereich, das Mausrad wird gedreht“:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+

Die Logik der Ereignisbehandlung ist identisch mit der Logik der Ereignisbehandlung für das oben beschriebene Scrollbar-Objekt. Aber hier bekommen wir zuerst den Zeiger auf das Basisobjekt für den Schieberegler, das ist das Scrollbar-Objekt. Dann bringen wir die Bildlaufleiste in den Vordergrund (ihr Schieberegler wird ebenfalls in den Vordergrund gebracht). Als Nächstes bestimmen wir das gewünschte Ereignis in Richtung des Scrollens mit dem Mausrad und senden das entsprechende Ereignis an dee Ereignisbehandlung des Basisobjekts — die Bildlaufleiste. Infolgedessen wird das Chart neu gezeichnet, um die Änderungen sofort anzuzeigen.


Implementierung der Ereignisbehandlung von „Der Cursor befindet sich innerhalb des Formulars, das Mausrad wird gerollt“:

Da diese Ereignisbehandlung völlig identisch mit der oben besprochenen sein sollte, rufen wir einfach die erste Ereignisbehandlung mit den Parametern auf, die ihr übergeben werden:

//+------------------------------------------------------------------+
//| 'The cursor is inside the form,                                  |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseInsideWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+


Die Funktion der horizontalen Bildlaufleiste ist fertig. Testen wir die Ergebnisse.


Test

Um den Test durchzuführen, werde ich den EA aus dem vorigen Artikel ohne jegliche Änderungen verwenden. Kompilieren wir ihn und führen ihn auf dem Chart mit der Einstellung „No“ für die automatische Größenanpassung des Containers an seinen Inhalt aus:



Überprüfen wir nun die Funktion aller Komponenten der erstellten horizontalen Bildlaufleiste:


Alles funktioniert wie geplant.



Was kommt als Nächstes?

Im nächsten Artikel werden wir die erstellte Funktionalität auf das Steuerelement „Vertikale Bildlaufleiste“ übertragen.


Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

 
DoEasy. Steuerung (Teil 26): Fertigstellung des ToolTip WinForms-Objekts und Weiterführung der ProgressBar-Entwicklung
DoEasy. Steuerung (Teil 27): Arbeiten am WinForms Objekt der ProgressBar
DoEasy. Steuerung (Teil 28): Balkenstile im ProgressBar-Steuerelement
DoEasy. Steuerung (Teil 29): ScrollBar-Hilfssteuerelement
DoEasy. Steuerung (Teil 30): Animieren des ScrollBar-Steuerelements
DoEasy. Steuerung (Teil 31): Scrollen des Inhalts des ScrollBar-Steuerelements

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/12849

Beigefügte Dateien |
MQL5.zip (4733.14 KB)
Entwicklung eines Replay-Systems — Marktsimulation (Teil 05): Hinzufügen einer Vorschau Entwicklung eines Replay-Systems — Marktsimulation (Teil 05): Hinzufügen einer Vorschau
Es ist uns gelungen, einen Weg zu finden, das Replay-System (Marktwiederholungssystem) auf realistische und zugängliche Weise umzusetzen. Lassen Sie uns nun unser Projekt fortsetzen und Daten hinzufügen, um das Wiedergabeverhalten zu verbessern.
Die ChatGPT-Funktionen von OpenAI im Rahmen der MQL4- und MQL5-Entwicklung Die ChatGPT-Funktionen von OpenAI im Rahmen der MQL4- und MQL5-Entwicklung
In diesem Artikel werden wir uns mit ChatGPT von OpenAI beschäftigen, um zu verstehen, welche Möglichkeiten es bietet, den Zeit- und Arbeitsaufwand für die Entwicklung von Expert Advisors, Indikatoren und Skripten zu reduzieren. Ich werde Sie schnell durch diese Technologie führen und versuchen, Ihnen zu zeigen, wie Sie sie für die Programmierung in MQL4 und MQL5 richtig einsetzen.
Die Handelstechnik RSI Deep Three Move Die Handelstechnik RSI Deep Three Move
Vorstellung der Handelstechnik RSI Deep Three Move für MetaTrader 5. Dieser Artikel basiert auf einer neuen Reihe von Studien, die einige Handelstechniken auf der Grundlage des RSI aufzeigen. Der RSI ist ein Indikator der technischen Analyse, der zur Messung der Stärke und Dynamik eines Wertpapiers, z. B. einer Aktie, einer Währung oder eines Rohstoffs, verwendet wird.
ONNX-Modelle in Klassen packen ONNX-Modelle in Klassen packen
Die objektorientierte Programmierung ermöglicht die Erstellung eines kompakteren Codes, der leicht zu lesen und zu ändern ist. Hier sehen wir uns das Beispiel für drei ONNX-Modelle an.