![DoEasy. Elementos de control (Parte 32): "ScrollBar" horizontal, desplazamiento con la rueda del ratón](https://c.mql5.com/i/articles/overlay.png)
DoEasy. Elementos de control (Parte 32): "ScrollBar" horizontal, desplazamiento con la rueda del ratón
Contenido
Concepto
Ha pasado mucho tiempo desde que se escribió el último artículo sobre la biblioteca; durante ese tiempo, en el sitio web ha aparecido el soporte para la nueva política de ejecución de órdenes Pasiva / Book or Cancel (BOC), mientras que en el lenguaje MQL5 han aparecido nuevos códigos de error de tiempo de ejecución para los métodos matriciales y vectoriales y los modelos ONNX. Hoy agregaremos todas estas adiciones a la biblioteca.
En el último artículo, nos detuvimos en la creación de la funcionalidad WinForms para un objeto de barra de desplazamiento que nos permite desplazar el contenido del contenedor usando los botones de flecha ubicados en los bordes de la barra de desplazamiento. Esto, por supuesto, no resulta suficiente para trabajar al completo con dicho objeto, así que hoy implementaremos la posibilidad de desplazarnos por el contenido del contenedor arrastrando el control deslizante de desplazamiento con el ratón. Los métodos creados para esto, entre tanto, harán posible conectar al objeto de barra de desplazamiento una funcionalidad que permita mover el contenido del contenedor desplazando la rueda del ratón.
Si pasamos el cursor sobre un área con una barra de desplazamiento horizontal y desplazamos la rueda del ratón, esto hará que el contenido del contenedor se desplace hacia la derecha al desplazar la rueda hacia arriba (lejos de nosotros) y hacia la izquierda al desplazar la rueda hacia abajo (hacia nosotros). Al menos así es como funciona el desplazamiento horizontal en Adobe PhotoShop; nosotros haremos lo mismo aquí. Más tarde, añadiremos la capacidad de especificar en la biblioteca la dirección de desplazamiento horizontal (y también vertical) del contenido del contenedor usando la rueda del ratón.
Mejorando las clases de la biblioteca
Hace algún tiempo, tras una nueva actualización del terminal, apareció un error molesto: la biblioteca y los archivos de ejemplo de los artículos dejaron de compilarse. La razón fue un descuido por mi parte al especificar la sección privada para algunos métodos de la clase CTrading en el archivo Trading.mqh. Como consecuencia, la clase CTradingControl, que es una clase derivada de CTrading, no podía acceder a dichos métodos. Antes, el compilador no detectaba este error mío, pero después de la actualización comenzó a detectarse. La solución a la situación es simple: deberemos especificar una sección protegida para los métodos privados a los que no se puede acceder desde las clases derivadas y que estarán disponibles en la clase heredada.
Así, abriremos el archivo \MQL5\Include\DoEasy\Trading.mqh e indicaremos la sección protegida para el método SetPrices(), ubicada en la sección privada:
//--- 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);
Tras declarar el método, retornaremos la sección privada a su lugar para que otros métodos no caigan en la protegida.
Por cierto, un poco antes ya hice lo mismo con otros dos métodos de la misma clase:
//--- 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:
Si de repente, al compilar archivos de ejemplo para artículos anteriores, encuentra un error que le deniega el acceso a los métodos privados de la clase CTrading, podrá solucionarlo usted mismo realizando las modificaciones anteriores.
Como ahora hay una nueva política de ejecución de órdenes y nuevos códigos de error de tiempo de ejecución, deberemos agregar una descripción de estas novedades a los arrays de mensajes de texto de la biblioteca.
Así, abriremos el archivo \MQL5\Include\DoEasy\Data.mqh e introduciremos el nuevo índice del mensaje sobre la nueva política de ejecución:
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
y los mensajes de texto (ruso e inglés) correspondientes al índice recién añadido:
{"Ордер исполняется исключительно в указанном объеме, иначе отменяется (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)"},
A la serie de mensajes de error en tiempo de ejecución (códigos de error 0, 4001 - 4019) añadiremos los nuevos códigos de error que han aparecido en el lenguaje MQL5, del 4020 al 4025:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
Algunos códigos de error no están en uso (¿todavía?), pero sus valores se encuentran directamente entre los códigos de error en uso. Para comprender rápidamente que este código de error se ha utilizado en MQL5, simplemente añadiremos el propio código a su mensaje sobre un código de error desconocido.
Si la biblioteca genera una entrada de registro "Código de error desconocido" (como se ve ahora, antes de que se realizaran los cambios), resultará difícil entender qué código no utilizado anteriormente se utiliza ahora. Al añadir un código de error al mensaje, recibiremos inmediatamente un mensaje sobre un código de error desconocido que indicará el número de este error. Lo único que deberemos hacer es abrir la guía de ayuda actualizada y añadir una descripción de este error a la biblioteca de la misma forma que lo hacemos ahora. Estas adiciones en este archivo ya se han agregado a todos los mensajes sobre un código de error desconocido.
Para los códigos de error de los métodos matriciales y vectoriales y los modelos ONNX, crearemos dos nuevos arrays, ya que los valores iniciales de los códigos comienzan a partir de 5700 y 5800, y rellenar con códigos no utilizados el “espacio vacío” con casi mil mensajes idénticos sobre un código desconocido no resultará práctico ni óptimo. Además, por la misma razón, la biblioteca utilizará diferentes arrays para diferentes grupos de códigos de error, así, añadiremos dos arrays más después del array de mensajes de error de tiempo de ejecución con los códigos 5601 - 5626:
//+------------------------------------------------------------------+ //| 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__
Para mostrar los mensajes de la biblioteca cuyos textos se almacenan en arrays de mensajes, tenemos la clase CMessage. Deberemos añadirle el procesamiento de los nuevos mensajes de texto añadidos a los arrays existentes, y a partir de los nuevos arrays creados hoy.
Así, abriremos el archivo \MQL5\Include\DoEasy\Services\Message.mqh y añadiremos el procesamiento de los arrays recién agregados al método GetTextByID(). El método escribirá en la variable m_text el mensaje de texto almacenado según el índice del mensaje transmitido al método.
Para los errores de tiempo de ejecución (0, 4001 - 4019), ampliaremos el número de códigos procesados de 4019 a 4025, y para los códigos de error de los métodos matriciales y de los vectores y modelos ONNX, anotaremos los mensajes de texto a partir de los nuevos arrays:
//+------------------------------------------------------------------+ //| 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] ); } //+------------------------------------------------------------------+
Algunas funciones comunes utilizadas en la biblioteca están escritas en el archivo \MQL5\Include\DoEasy\Services\DELib.mqh.
En la función que retorna la descripción del modo de rellenado de la orden, añadiremos el procesamiento del tipo de rellenado BoC:
//+------------------------------------------------------------------+ //| 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) ); } //+------------------------------------------------------------------+
Ahora, si la política de ejecución es Book or Cancel, la función retornará el texto correspondiente al índice MSG_LIB_TEXT_REQUEST_ORDER_FILLING_BOK en el array de mensajes que hemos añadido hoy.
Al eliminar un asesor experto del gráfico, en el diario de registro se mostraba un mensaje sobre un error al cerrar la Profundidad de Mercado. No se mostraba la descripción del error ni su código. Considero esto un comportamiento incorrecto; simplemente no está del todo claro qué ocurre. Para corregir la situación, modificaremos el método que cierra la profundidad de mercado en la clase de objeto de símbolo CSymbol en el archivo \MT5\MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh.
En el bloque de código que procesa el error de cierre de la profundidad de mercado, obtendremos el código de error y lo mostraremos en el diario de registro después de la descripción de este error:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Ahora quedará claro por qué ha sucedido el error al intentar cerrar la profundidad de mercado.
En estos momentos, ya tenemos implementada la funcionalidad necesaria para cambiar el contenido del contenedor clicando en los botones de flecha en el objeto de la barra de desplazamiento. Al crearla, decidimos que la magnitud en la que se desplazaría el contenido del contenedor sería igual a dos píxeles de la pantalla. Esto basta para posicionar cómodamente el contenido desplazable usando los botones de desplazamiento. Sin embargo, hoy crearemos dos opciones de desplazamiento más: moviendo el control deslizante de la barra de desplazamiento con el ratón y desplazándonos con la rueda.
Si, al mover el control deslizante de desplazamiento, no necesitamos tener un valor predeterminado según el cual se desplazará el contenido del contenedor (la magnitud de desplazamiento se establecerá en el valor según el cual se ha desplazado el control deslizante), entonces para desplazarnos con la rueda del ratón necesitaremos determinar cuántos píxeles se moverá el contenido del contenedor de una vez al activarse el contador de la rueda del ratón. El contador es discreto y envía un evento cuando el valor Delta llega a 120 o -120 (dependiendo de la dirección en la que se desplace la rueda). Para desplazarse con la rueda, estableceremos el valor en cuatro píxeles.
Luego abriremos el archivo \MT5\MQL5\Include\DoEasy\Defines.mqh y corregiremos el nombre de la macrosustitución responsable del valor de desplazamiento clicando en el botón de flecha (DEF_CONTROL_SCROLL_BAR_SCROLL_STEP), y añadiremos una nueva macrosustitución responsable del valor de desplazamiento al movernos con la rueda del ratón:
#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
Ya se han realizado todas las correcciones preliminares, ahora finalizaremos la funcionalidad de la barra de desplazamiento.
Primero, implementaremos el desplazamiento del contenido del contenedor moviendo la barra de desplazamiento con el ratón. La lógica será la siguiente: la barra de desplazamiento mostrará esquemáticamente, de forma reducida, el contenedor y su contenido. El control deslizante, su tamaño, mostrará el tamaño de la parte visible del contenedor, mientras que la barra de desplazamiento (entre los bordes de los botones de flecha) mostrará el contenido del contenedor más allá de sus límites. Tenemos un punto de partida en el que basaremos nuestros cálculos: la magnitud en la que el control deslizante se desplaza desde el borde derecho del botón de flecha izquierda. Asimismo, tenemos el tamaño del control deslizante y el tamaño visible del contenedor. Sabiendo en cuánto supera el tamaño de la parte visible del contenedor al tamaño del control deslizante y en qué magnitud se ha desplazado el mismo, podremos calcular cuánto hay que desplazar el contenido del contenedor:
- Anchura de la parte visible del contenedor (W1)
- Anchura del control deslizante (W2)
- ¿En cuánto supera la parte visible del contenedor al control deslizante (X = W1 / W2)?
- Cuánto se desplaza el control deslizante (S1)
- ¿Cuánto tenemos que desplazar el contenido del contenedor (S1 * X)?
Por ello, conociendo la relación entre los tamaños del control deslizante y la parte visible del contenedor, y cuánto se ha desplazado el control deslizante, calcularemos la magnitud en la que se debe desplazar el contenido del contenedor. Además, si el control deslizante se desplaza hacia la izquierda, el contenido del contenedor deberá moverse hacia la derecha y viceversa.
Ahora abriremos el archivo de la clase de barra de desplazamiento horizontal \MT5\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh y en su manejador de eventos escribiremos un bloque de código que realizará estos cálculos y desplazará el contenido del contenedor:
//+------------------------------------------------------------------+ //| 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(); } } //+------------------------------------------------------------------+
Casi cada línea del bloque de código añadido se explica con comentarios. Tenga en cuenta que cuando movamos el control deslizante hacia la izquierda, desplazaremos el contenido del contenedor hacia la derecha. Al hacerlo, deberemos restringir el desplazamiento del contenido del contenedor para que el borde izquierdo del contenido no se mueva a la derecha del borde izquierdo del área de trabajo del contenedor, es decir, el área dentro de la cual el contenido del contenedor será visible. Así como cuando movemos el control deslizante hacia la derecha, desplazamos el contenido del contenedor hacia la izquierda y, al mismo tiempo, el borde derecho del contenido no deberá extenderse hacia la izquierda del borde derecho del área de trabajo del contenedor. Todo esto se realizará comprobando las coordenadas calculadas antes de desplazar el contenido del contenedor.
En el método que calcula y establece los parámetros del área de captura, cambiaremos ligeramente el cálculo del tamaño relativo de la ventana de la parte visible; evitaremos los cálculos en porcentajes, calculando así la relación del tamaño área de trabajo del contenedor respecto al tamaño del control deslizante (en cuánto supera un tamaño al otro):
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Además de ajustar el tamaño mínimo del control deslizante, realizaremos un ajuste de su tamaño máximo: el valor calculado de la anchura del control deslizante no deberá superar al tamaño real del área de trabajo de la barra de desplazamiento (la distancia entre los botones de flecha izquierdo y derecho).
Si ahora compilamos el asesor del artículo anterior con las mejoras actuales de la biblioteca, al mover el control deslizante de la barra de desplazamiento, desplazaremos en consecuencia el contenido del contenedor en la dirección opuesta:
Ahora necesitamos posibilitar el desplazamiento del contenido del contenedor con la rueda del ratón; esto es para nosotros algo cómodo y familiar. En muchos programas, el desplazamiento de la rueda del ratón solo provoca el movimiento vertical del contenido del contenedor (piense en las páginas del navegador o en los editores de texto). Pero el nuestro será un poco diferente: al pasar el cursor sobre la barra de desplazamiento horizontal y girar la rueda del ratón, desplazaremos el contenido del contenedor horizontalmente, cosa que me parece lógica. Si se requiere un movimiento vertical, deberemos girar la rueda del ratón en el área de la barra de desplazamiento vertical o en el contenido del contenedor. Pero si el cursor está en una barra de desplazamiento horizontal, entonces será lógico esperar un desplazamiento horizontal del contenido del contenedor al girar la rueda del ratón. Al menos así se hace en Adobe PhotoShop, y resulta lógico y cómodo.
Para implementar esta funcionalidad, aunque parezca mentira, ya casi está todo listo. Tenemos un manejador que cambiará el contenido del contenedor al clicar en los botones de flecha de la barra de desplazamiento. Al darse un evento de giro de la rueda del ratón en el área de la barra de desplazamiento, enviaremos un evento de clic a los botones de desplazamiento. Dependiendo de la dirección de giro de la rueda, enviaremos un evento de clic en el botón de flecha izquierda o derecha.
Junto con el identificador del evento, al manejador de eventos se transmitirán algunos parámetros: enteros, reales y de cadena. Al clicar en un objeto de botón de flecha, el nombre del objeto de botón de flecha se transmitirá como parámetro de cadena. Al girar la rueda del ratón, el parámetro de cadena no se usará; ahí tendremos una cadena vacía. Esto es lo que usaremos en el manejador de eventos de clic del botón para determinar qué evento ha tenido lugar realmente: ya sea el clic en un botón o el giro de la rueda del ratón, y, según el evento detectado, determinaremos cuánto se debe mover el contenido del contenedor. Al clicar en un botón de flecha, se moverá dos píxeles, y al desplazar la rueda del ratón, cuatro.
En la sección protegida de la clase, declararemos un manejador virtual para el evento "Cursor dentro del área activa, la rueda del ratón se está desplazando":
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:
Todas las clases de elementos gráficos son descendientes de la clase de objeto de formulario CForm. En esta ya se contienen los manejadores vacíos para varios eventos del ratón que deberán redefinirse en clases heredadas. Aquí redefiniremos el manejador de eventos de giro de la rueda del ratón dentro del área activa de este objeto.
Fuera del cuerpo de la clase, escribiremos su implementación:
//+------------------------------------------------------------------+ //| '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()); } //+------------------------------------------------------------------+
Aquí todo es simple: al mover la rueda del ratón, el valor Delta del contador de giro de la rueda se transmitirá al parámetro real dparam.
Los valores pueden ser 120 y -120, según el sentido de giro.
Si el Delta (transmitido a dparam) es positivo, entonces el evento será un clic en el botón de flecha izquierda.
Si el Delta es negativo, el evento será un clic en el botón de flecha hacia la derecha,
y enviaremoseste evento al manejador de eventos de este objeto (barra de desplazamiento).
Como estamos procesando dos eventos distintos (el clic en un botón y el giro de la rueda del ratón) con el mismo bloque de código en el manejador OnChartEvent() del objeto de la barra de desplazamiento, podremos determinar qué evento se ha basado inicialmente en el valor del parámetro de cadena sparam: cuando la rueda se desplace, en este parámetro habrá una línea vacía, y cuando cliquemos en el botón, se hallará su nombre. En el manejador de eventos, determinaremos qué evento ha ocurrido y estableceremos el tamaño de desplazamiento del contenido del contenedor en píxeles según el evento identificado. Para clicar en un botón, dos píxeles de desplazamiento, para desplazar la rueda del ratón, cuatro:
//--- 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(); } } //+------------------------------------------------------------------+
Ahora, si compilamos nuevamente el asesor del artículo anterior e intentamos girar la rueda del ratón mientras el cursor se encuentra en la barra de desplazamiento, el contenido se desplazará solo si el cursor no está en el control deslizante:
Si el cursor toca el control deslizante, no habrá desplazamiento. ¿Por qué? Pues simplemente porque el objeto del control deslizante de la barra de desplazamiento se activará, pero aún no tendrá un manejador para tal evento. Vamos a añadirlo.
Así, abriremos el archivo de la clase de objeto de control deslizante de la barra de desplazamiento \MT5\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh y declararemos dos manejadores de eventos de ratón en la sección protegida:
//+------------------------------------------------------------------+ //| 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:
¿Por qué se necesitan dos manejadores? Cada objeto gráfico de la biblioteca contiene áreas que son responsables de sus diferentes estados y envían los eventos correspondientes a estas áreas. El área activa del objeto de control deslizante de la barra de desplazamiento a lo largo de su perímetro será un píxel más pequeña que el tamaño del control deslizante. Como consecuencia, cuando el cursor se encuentre dentro del área activa, el manejador para el área activa del objeto estará activo, mientras que si el cursor está en el borde del control deslizante, terminará en otra zona donde el manejador para el cursor dentro del formulario funcionará.
Fuera del cuerpo de la clase, escribiremos la implementación de estos dos manejadores.
Implementación del manejador de eventos "Cursor dentro del área activa, la rueda del ratón se está desplazando":
//+------------------------------------------------------------------+ //| '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()); } //+------------------------------------------------------------------+
La lógica del manejador es idéntica a la lógica del manejador de los objetos de barra de desplazamiento anteriormente analizada. Pero aquí primero obtendremos un puntero al objeto básico del control deslizante, que es el objeto de la barra de desplazamiento. Luego colocaremos la barra de desplazamiento en primer plano (su control deslizante también pasará a primer plano). A continuación, determinaremos el evento requerido en la dirección de giro de la rueda del ratón y enviaremos el evento correspondiente al manejador de eventos del objeto básico: la barra de desplazamiento. Como resultado, redibujaremos el gráfico para mostrar inmediatamente los cambios.
Implementación del manejador de eventos "Cursor dentro del formulario, la rueda del ratón se está desplazando":
Como este manejador deberá ser completamente idéntico al discutido anteriormente, simplemente llamaremos al primer manejador, con los parámetros transmitidos a este:
//+------------------------------------------------------------------+ //| '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); } //+------------------------------------------------------------------+
Ahora ya tenemos todo listo para la funcionalidad de la barra de desplazamiento horizontal. Vamos a comprobar qué hemos conseguido.
Simulación
Para la prueba, tomaremos el asesor del artículo anterior sin ningún cambio. Luego lo compilaremos y lo ejecutaremos en el gráfico, estableciendo en los ajustes de inicio "No" para el modo de cambio automático del tamaño del contenedor para que se ajuste a su contenido:
Luego veremos cómo funcionan todos los componentes de la funcionalidad de la barra de desplazamiento horizontal creada:
Todo marcha según lo planeado.
¿Qué es lo próximo?
En el próximo artículo, transferiremos la funcionalidad creada al elemento de control "Barra de desplazamiento vertical".
*Artículos de esta serie:
DoEasy. Elementos de control (Parte 26): Mejoramos el objeto WinForms "ToolTip" y comenzamos a desarrollar "ProgressBar"
DoEasy. Elementos de control (Parte 27): Seguimos trabajando en el objeto WinForms "ProgressBar"
DoEasy. Elementos de control (Parte 28): Estilos de barra en el control «ProgressBar»
DoEasy. Elementos de control (Parte 29): Control auxiliar "ScrollBar"
DoEasy. Elementos de control (Parte 30): Animando el elemento de control "ScrollBar"
DoEasy. Elementos de control (Parte 31): Desplazamiento por el contenido del control "ScrollBar"
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/12849
![Desarrollo de un sistema de repetición — Simulación de mercado (Parte 17): Ticks y más ticks (I)](https://c.mql5.com/2/55/replay-p17-avatar.png)
![Redes neuronales: así de sencillo (Parte 47): Espacio continuo de acciones](https://c.mql5.com/2/55/Neural_Networks_Part_47_avatar.png)
![Desarrollo de un sistema de repetición — Simulación de mercado (Parte 18): Ticks y más ticks (II)](https://c.mql5.com/2/56/replay-p18-avatar.png)
![Posibilidades de ChatGPT de OpenAI en el marco de desarrollo de MQL4 y MQL5](https://c.mql5.com/2/55/mql5-openai-avatar.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso