Operando con el Calendario Económico MQL5 (Parte 10): Panel arrastrable y efectos al pasar el cursor para una navegación fluida por las noticias
Introducción
En este artículo, continuamos con la serie Calendario económico de MQL5 presentando un panel de control arrastrable y efectos al pasar el cursor, con el fin de mejorar el control y la navegación de los operadores por las noticias, garantizando así una experiencia de usuario flexible e intuitiva. Partiendo de la barra de desplazamiento dinámica y la presentación pulida de la Parte 9, nos centramos ahora en una interfaz de usuario (UI) adaptativa que permite reposicionar el calendario en el gráfico y ofrece retroalimentación visual ante las interacciones con los botones, optimizando así el acceso a las noticias económicas tanto en entornos en tiempo real como de backtesting. Estructuramos el artículo con los siguientes temas:
- Añadir un panel de control arrastrable para aumentar la flexibilidad del gráfico
- Implementación en MQL5
- Pruebas y validación
- Conclusión
¡Analicemos estas mejoras!
Añadir un panel de control arrastrable para aumentar la flexibilidad del gráfico
Para mejorar la usabilidad del Calendario Económico MQL5, introduciremos un panel de control arrastrable que nos permitirá reposicionar la interfaz en el gráfico, junto con una barra de desplazamiento posicionada dinámicamente para mantener una navegación fluida por las noticias. Nuestro objetivo es crear una herramienta centrada en el operador que se adapte a diversos diseños de gráficos, eliminando el posicionamiento fijo de las versiones anteriores y garantizando que el panel de control, los eventos de noticias y la barra de desplazamiento se muevan juntos sin esfuerzo. Así es como lo lograremos:
- Diseño de panel de control arrastrable: Implementaremos un sistema para detectar los clics del ratón en el área del encabezado, lo que permitirá a los usuarios arrastrar todo el panel de control. Todos los elementos de la interfaz de usuario actualizarán sus posiciones en tiempo real para mantener la alineación.
- Posicionamiento dinámico de la barra de desplazamiento: Ajustaremos la barra de desplazamiento para que utilice coordenadas relativas vinculadas a la posición del panel de control, garantizando así que siga siendo funcional y se mantenga correctamente situada durante el arrastre.
- Restricciones de los límites del gráfico: Aplicaremos límites para mantener el panel de control dentro del área visible del gráfico, evitando que se salga de la pantalla y garantizando su accesibilidad.
- Movimiento coherente de los elementos: Nos aseguraremos de que las noticias, los botones de filtro y las etiquetas de operaciones se muevan al unísono con el panel de control, ofreciendo así una interfaz unificada y profesional.
Este enfoque estratégico transformará el panel de control en una herramienta flexible que podremos posicionar según sea necesario, mejorando la visibilidad y la interacción con los gráficos. En resumen, esto es lo que pretendemos lograr.

Implementación en MQL5
Para introducir las mejoras en MQL5, primero tendremos que definir algunas funciones adicionales con el fin de mejorar la interactividad dinámica. Comenzaremos desde lo más simple hasta lo más complejo, es decir, desde los estados al pasar el cursor hasta los estados de arrastre. En primer lugar, definamos una función void que determine el estado de un botón al pasar el cursor por encima en función de la posición del cursor en el gráfico y actualice los estados de los botones, normalmente oscureciéndolos, aunque podemos definir nuestros propios colores para los estados al pasar el cursor. Primero, definamos la función responsable del oscurecimiento del color.
//+------------------------------------------------------------------+ //| Helper function to darken a color for hover effect | //+------------------------------------------------------------------+ color ColorToDarken(color clr) { int r = (clr & 0xFF); int g = ((clr >> 8) & 0xFF); int b = ((clr >> 16) & 0xFF); r = MathMax(0, r - 50); g = MathMax(0, g - 50); b = MathMax(0, b - 50); return (color)((b << 16) | (g << 8) | r); }
En primer lugar, definimos la función «ColorToDarken» para generar una versión más oscura de un color de entrada, que utilizaremos para mejorar la retroalimentación visual de los botones al pasar el cursor por encima de ellos. Comenzamos con el color de entrada «clr», un único número que combina los componentes de rojo, verde y azul, cada uno de los cuales oscila entre 0 y 255, para representar un color específico. Para separar estos componentes, utilizamos operaciones bit a bit, que nos permiten manipular los bits de «clr».
Para el componente rojo «r», utilizamos el operador «&» (AND bit a bit) con «0xFF», un valor hexadecimal equivalente a 255 en decimal, que actúa como un filtro para extraer únicamente los 8 bits menos significativos de «clr», lo que nos proporciona la intensidad del rojo. Para el componente verde «g», desplazamos «clr» 8 bits hacia la derecha utilizando el operador «>>» para trasladar los datos verdes a los 8 bits menos significativos y, a continuación, aplicamos el operador «&» con «0xFF» para aislarlos. Del mismo modo, extraemos el componente azul «b» desplazando «clr» 16 bits hacia la derecha y aplicando la operación «&» con «0xFF». Los caracteres en hexadecimal son los siguientes.

Para oscurecer el color, restamos 50 a cada componente —«r», «g» y «b»— con el fin de reducir su intensidad, y utilizamos la función «MathMax» para garantizar que ningún valor sea inferior a 0, ya que los valores de color negativos no son válidos. Por último, combinamos los componentes oscurecidos para obtener un único valor de color desplazando «b» 16 bits a la izquierda con el operador «<<», desplazando «g» 8 bits a la izquierda y fusionándolos con «r» mediante el operador «|» (OR bit a bit). Devolvemos este nuevo valor de «color», que aplicamos a los botones para crear un efecto más oscuro al pasar el cursor, lo que hace que la interfaz resulte más interactiva e intuitiva. Con esta función, ahora podemos formatear dinámicamente los estados de color al pasar el cursor por encima.
//+------------------------------------------------------------------+ //| Update hover states for header and buttons | //+------------------------------------------------------------------+ void updateHoverStates(int mouse_x, int mouse_y) { // Header hover int header_x = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_XDISTANCE); int header_y = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_YDISTANCE); int header_width = 740; int header_height = 30; bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height); if (is_header_hovered && !header_hovered) { ObjectSetInteger(0, MAIN_REC, OBJPROP_BGCOLOR, clrDarkGreen); header_hovered = true; } else if (!is_header_hovered && header_hovered) { ObjectSetInteger(0, MAIN_REC, OBJPROP_BGCOLOR, clrSeaGreen); header_hovered = false; } }
Aquí implementamos la función «updateHoverStates» para añadir un efecto al pasar el cursor por el encabezado. Recuperamos la posición de «HEADER_LABEL» mediante ObjectGetInteger para obtener «header_x» y «header_y» con OBJPROP_XDISTANCE y OBJPROP_YDISTANCE, definiendo así un área de 740 x 30 píxeles. Comprobamos si «mouse_x» y «mouse_y» se encuentran dentro de esta zona, estableciendo «is_header_hovered».
Si «is_header_hovered» es verdadero y «header_hovered» es falso, utilizamos ObjectSetInteger para establecer la propiedad «OBJPROP_BGCOLOR» de «MAIN_REC» en «clrDarkGreen» y actualizamos «header_hovered» a verdadero; si es falso y «header_hovered» es verdadero, lo restablecemos a clrSeaGreen y establecemos «header_hovered» en falso, proporcionando así una respuesta visual al pasar el cursor por encima. Ahora podemos hacer lo mismo con los demás botones.
// FILTER_CURR_BTN hover int curr_btn_x = (int)ObjectGetInteger(0, FILTER_CURR_BTN, OBJPROP_XDISTANCE); int curr_btn_y = (int)ObjectGetInteger(0, FILTER_CURR_BTN, OBJPROP_YDISTANCE); int curr_btn_width = 110; int curr_btn_height = 26; bool is_curr_btn_hovered = (mouse_x >= curr_btn_x && mouse_x <= curr_btn_x + curr_btn_width && mouse_y >= curr_btn_y && mouse_y <= curr_btn_y + curr_btn_height); if (is_curr_btn_hovered && !filter_curr_hovered) { ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_BGCOLOR, clrDarkGray); filter_curr_hovered = true; } else if (!is_curr_btn_hovered && filter_curr_hovered) { ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_BGCOLOR, clrBlack); filter_curr_hovered = false; } // FILTER_IMP_BTN hover int imp_btn_x = (int)ObjectGetInteger(0, FILTER_IMP_BTN, OBJPROP_XDISTANCE); int imp_btn_y = (int)ObjectGetInteger(0, FILTER_IMP_BTN, OBJPROP_YDISTANCE); int imp_btn_width = 120; int imp_btn_height = 26; bool is_imp_btn_hovered = (mouse_x >= imp_btn_x && mouse_x <= imp_btn_x + imp_btn_width && mouse_y >= imp_btn_y && mouse_y <= imp_btn_y + imp_btn_height); if (is_imp_btn_hovered && !filter_imp_hovered) { ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_BGCOLOR, clrDarkGray); filter_imp_hovered = true; } else if (!is_imp_btn_hovered && filter_imp_hovered) { ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_BGCOLOR, clrBlack); filter_imp_hovered = false; } // FILTER_TIME_BTN hover int time_btn_x = (int)ObjectGetInteger(0, FILTER_TIME_BTN, OBJPROP_XDISTANCE); int time_btn_y = (int)ObjectGetInteger(0, FILTER_TIME_BTN, OBJPROP_YDISTANCE); int time_btn_width = 70; int time_btn_height = 26; bool is_time_btn_hovered = (mouse_x >= time_btn_x && mouse_x <= time_btn_x + time_btn_width && mouse_y >= time_btn_y && mouse_y <= time_btn_y + time_btn_height); if (is_time_btn_hovered && !filter_time_hovered) { ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_BGCOLOR, clrDarkGray); filter_time_hovered = true; } else if (!is_time_btn_hovered && filter_time_hovered) { ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_BGCOLOR, clrBlack); filter_time_hovered = false; } // CANCEL_BTN hover int cancel_btn_x = (int)ObjectGetInteger(0, CANCEL_BTN, OBJPROP_XDISTANCE); int cancel_btn_y = (int)ObjectGetInteger(0, CANCEL_BTN, OBJPROP_YDISTANCE); int cancel_btn_width = 50; int cancel_btn_height = 30; bool is_cancel_btn_hovered = (mouse_x >= cancel_btn_x && mouse_x <= cancel_btn_x + cancel_btn_width && mouse_y >= cancel_btn_y && mouse_y <= cancel_btn_y + cancel_btn_height); if (is_cancel_btn_hovered && !cancel_hovered) { ObjectSetInteger(0, CANCEL_BTN, OBJPROP_BGCOLOR, clrDarkRed); cancel_hovered = true; } else if (!is_cancel_btn_hovered && cancel_hovered) { ObjectSetInteger(0, CANCEL_BTN, OBJPROP_BGCOLOR, clrRed); cancel_hovered = false; } // CURRENCY_BTNS hover int curr_size = 51, button_height = 22, spacing_x = 0, spacing_y = 3, max_columns = 4; for (int i = 0; i < ArraySize(curr_filter); i++) { int row = i / max_columns; int col = i % max_columns; int x_pos = panel_x + 525 + col * (curr_size + spacing_x); int y_pos = panel_y + 33 + row * (button_height + spacing_y); bool is_curr_hovered = (mouse_x >= x_pos && mouse_x <= x_pos + curr_size && mouse_y >= y_pos && mouse_y <= y_pos + button_height); string btn_name = CURRENCY_BTNS+IntegerToString(i); if (is_curr_hovered && !currency_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, clrLightGray); currency_btns_hovered[i] = true; } else if (!is_curr_hovered && currency_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, clrNONE); currency_btns_hovered[i] = false; } } // IMPACT_LABEL buttons hover int impact_size = 100; for (int i = 0; i < ArraySize(impact_labels); i++) { int x_pos = panel_x + 90 + impact_size * i; int y_pos = panel_y + 55; bool is_impact_hovered = (mouse_x >= x_pos && mouse_x <= x_pos + impact_size && mouse_y >= y_pos && mouse_y <= y_pos + 25); string btn_name = IMPACT_LABEL+string(i); color normal_color = clrBlack; if (impact_labels[i] == "None") normal_color = clrBlack; else if (impact_labels[i] == "Low") normal_color = clrYellow; else if (impact_labels[i] == "Medium") normal_color = clrOrange; else if (impact_labels[i] == "High") normal_color = clrRed; color hover_color = normal_color == clrBlack ? clrDarkGray : ColorToDarken(normal_color); if (is_impact_hovered && !impact_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, hover_color); impact_btns_hovered[i] = true; } else if (!is_impact_hovered && impact_btns_hovered[i]) { ObjectSetInteger(0, btn_name, OBJPROP_BGCOLOR, normal_color); impact_btns_hovered[i] = false; } }
Mejoramos la función «updateHoverStates» para añadir efectos al pasar el cursor por varios botones, lo que proporciona una respuesta visual a las interacciones del usuario. Para el «FILTER_CURR_BTN», utilizamos la función ObjectGetInteger para recuperar sus coordenadas «curr_btn_x» y «curr_btn_y» con OBJPROP_XDISTANCE y «OBJPROP_YDISTANCE», definiendo un área de 110 x 26 píxeles, y comprobamos si «mouse_x» y «mouse_y» se encuentran dentro de ella para establecer «is_curr_btn_hovered».
Si «is_curr_btn_hovered» es verdadero y «filter_curr_hovered» es falso, establecemos «FILTER_CURR_BTN» y OBJPROP_BGCOLOR en clrDarkGray utilizando ObjectSetInteger y actualizamos «filter_curr_hovered» a verdadero; en caso contrario, lo restablecemos a «clrBlack» y establecemos «filter_curr_hovered» en falso.
Aplicamos una lógica similar para «FILTER_IMP_BTN» (120 x 26 píxeles) y «FILTER_TIME_BTN» (70 x 26 píxeles), actualizando «filter_imp_hovered» y «filter_time_hovered», respectivamente. En el caso del botón «CANCEL_BTN» (50 x 30 píxeles), alternamos entre clrDarkRed y «clrRed» en función de los valores de «is_cancel_btn_hovered» y «cancel_hovered». En el caso de «CURRENCY_BTNS», recorremos cada botón, calculamos las coordenadas con «panel_x» y «panel_y», comprobamos si «is_curr_hovered» es verdadero y alternamos entre «clrLightGray» y «clrNONE» para «currency_btns_hovered[i]».
En el caso de los botones «IMPACT_LABEL», asignamos el «normal_color» en función del nivel de impacto (por ejemplo, «clrYellow» para el nivel bajo), calculamos el «hover_color» mediante «ColorToDarken» y alternamos los colores según «is_impact_hovered» e «impact_btns_hovered[i]», garantizando así efectos dinámicos al pasar el cursor. Para que los cambios surtan efecto, llamamos a la función en el controlador de eventos OnChartEvent.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { int mouse_x = (int)lparam; int mouse_y = (int)dparam; int mouse_state = (int)sparam; // Update hover states if (id == CHARTEVENT_MOUSE_MOVE) { updateHoverStates(mouse_x, mouse_y); } }
En el controlador de eventos OnChartEvent, comenzamos por obtener las coordenadas del ratón «mouse_x» y «mouse_y» a partir de los parámetros «lparam» y «dparam», así como el estado del ratón «mouse_state» a partir de «sparam», que indican la posición del ratón y si el botón está pulsado o liberado. Cuando el «id» del evento es CHARTEVENT_MOUSE_MOVE, llamamos a la función «updateHoverStates», pasando los valores «mouse_x» y «mouse_y», para comprobar si el cursor se encuentra sobre el encabezado o los botones y actualizar su aspecto visual en consecuencia, garantizando así una respuesta adecuada al pasar el cursor por los elementos interactivos del panel de control. A continuación, debemos capturar las actualizaciones de los demás elementos al hacer clic y determinar su estado de movimiento.
if (id == CHARTEVENT_OBJECT_CLICK) { UpdateFilterInfo(); CheckForNewsTrade(); if (sparam == CANCEL_BTN) { isDashboardUpdate = false; destroy_Dashboard(); } if (sparam == FILTER_CURR_BTN) { bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); enableCurrencyFilter = btn_state; if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("Success. Changes updated! State: "+(string)enableCurrencyFilter); ChartRedraw(0); } if (sparam == FILTER_IMP_BTN) { bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); enableImportanceFilter = btn_state; if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("Success. Changes updated! State: "+(string)enableImportanceFilter); ChartRedraw(0); } if (sparam == FILTER_TIME_BTN) { bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); enableTimeFilter = btn_state; if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("Success. Changes updated! State: "+(string)enableTimeFilter); ChartRedraw(0); } if (StringFind(sparam,CURRENCY_BTNS) >= 0) { string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT); if (debugLogging) Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr); bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); if (btn_state == false) { if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE."); for (int i = 0; i < ArraySize(curr_filter_selected); i++) { if (curr_filter_selected[i] == selected_curr) { for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) { curr_filter_selected[j] = curr_filter_selected[j + 1]; } ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1); if (debugLogging) Print("Removed from selected filters: ", selected_curr); break; } } } else { if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION"); bool already_selected = false; for (int j = 0; j < ArraySize(curr_filter_selected); j++) { if (curr_filter_selected[j] == selected_curr) { already_selected = true; break; } } if (!already_selected) { ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1); curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr; if (debugLogging) Print("Added to selected filters: ", selected_curr); } else { if (debugLogging) Print("Currency already selected: ", selected_curr); } } if (debugLogging) Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected)); if (debugLogging) ArrayPrint(curr_filter_selected); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED"); ChartRedraw(0); } if (StringFind(sparam, IMPACT_LABEL) >= 0) { string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT); ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp); if (debugLogging) Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")"); bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE); color color_border = btn_state ? clrNONE : clrBlack; if (btn_state == false) { if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE."); for (int i = 0; i < ArraySize(imp_filter_selected); i++) { if (impact_filter_selected[i] == selected_imp) { for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) { imp_filter_selected[j] = imp_filter_selected[j + 1]; impact_filter_selected[j] = impact_filter_selected[j + 1]; } ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1); ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1); if (debugLogging) Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); break; } } } else { if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION"); bool already_selected = false; for (int j = 0; j < ArraySize(imp_filter_selected); j++) { if (impact_filter_selected[j] == selected_imp) { already_selected = true; break; } } if (!already_selected) { ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1); ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1); imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl; impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp; if (debugLogging) Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); } else { if (debugLogging) Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")"); } } if (debugLogging) Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected)); if (debugLogging) ArrayPrint(imp_filter_selected); if (debugLogging) ArrayPrint(impact_filter_selected); if (MQLInfoInteger(MQL_TESTER)) filters_changed = true; update_dashboard_values(curr_filter_selected,imp_filter_selected); ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border); // Recalculate scrollbar ObjectDelete(0, SCROLL_LEADER); ObjectDelete(0, SCROLL_UP_REC); ObjectDelete(0, SCROLL_UP_LABEL); ObjectDelete(0, SCROLL_DOWN_REC); ObjectDelete(0, SCROLL_DOWN_LABEL); ObjectDelete(0, SCROLL_SLIDER); scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS; if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden"); if (scroll_visible) { createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack; color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack; createRecLabel(SCROLL_UP_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-5, CharToString(0x35), up_color, 15, "Webdings"); int down_y = panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE; createRecLabel(SCROLL_DOWN_REC, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray); createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings"); slider_height = calculateSliderHeight(); int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold"); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2); if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height); updateSliderPosition(); updateButtonColors(); } if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED"); ChartRedraw(0); } // Scrollbar button clicks if (scroll_visible && (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL)) { scrollUp(); updateButtonColors(); if (debugLogging) Print("Up button clicked (", sparam, "). CurrPos: ", scroll_pos); ChartRedraw(0); } if (scroll_visible && (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL)) { scrollDown(); updateButtonColors(); if (debugLogging) Print("Down button clicked (", sparam, "). CurrPos: ", scroll_pos); ChartRedraw(0); } }
Para mejorar los eventos de clic en objetos dinámicos cuando el «id» del evento es CHARTEVENT_OBJECT_CLICK, primero llamamos a «UpdateFilterInfo» y «CheckForNewsTrade» para actualizar los estados de los filtros y comprobar las condiciones de negociación. Si «sparam» es «CANCEL_BTN», establecemos «isDashboardUpdate» en «false» y llamamos a «destroy_Dashboard» para cerrar el panel de control. Para «FILTER_CURR_BTN», «FILTER_IMP_BTN» o «FILTER_TIME_BTN», utilizamos ObjectGetInteger para obtener «btn_state» con OBJPROP_STATE, actualizamos «enableCurrencyFilter», «enableImportanceFilter» o «enableTimeFilter», y ajustamos el texto y el color del botón utilizando «ObjectSetString» y ObjectSetInteger con «OBJPROP_TEXT» y «OBJPROP_COLOR» (por ejemplo, «clrLime» para habilitado, «clrRed» para deshabilitado); a continuación, llamamos a «update_dashboard_values» y reconstruimos la barra de desplazamiento utilizando «createRecLabel», «createLabel» y «createButton» para elementos como «SCROLL_LEADER» y «SCROLL_SLIDER», posicionados con «panel_x» y «panel_y», y actualizamos su estado con «updateSliderPosition» y «updateButtonColors».
Para «CURRENCY_BTNS», utilizamos ObjectGetString para obtener «selected_curr», lo añadimos o eliminamos de «curr_filter_selected» mediante ArrayResize, y actualizamos el panel de control de forma similar. En el caso de los botones «IMPACT_LABEL», llamamos a la función «get_importance_level» para asignar el valor de «selected_imp» a «selected_importance_lvl», gestionamos las matrices «imp_filter_selected» e «impact_filter_selected», establecemos «color_border» mediante «ObjectSetInteger» y actualizamos el panel de control. Si «sparam» es «SCROLL_UP_REC» o «SCROLL_UP_LABEL», llamamos a «scrollUp», y si es «SCROLL_DOWN_REC» o «SCROLL_DOWN_LABEL», llamamos a «scrollDown», actualizando los colores con «updateButtonColors» y volviendo a dibujar el gráfico con ChartRedraw para reflejar los cambios. Al compilar y ejecutar el programa, obtenemos los siguientes resultados.

La visualización muestra que obtenemos retroalimentación visual al pasar el cursor sobre los botones definidos. Lo que debemos hacer ahora es pasar a la parte compleja, que es el movimiento de todo el panel de control. Lo logramos mediante la siguiente lógica.
else if (id == CHARTEVENT_MOUSE_MOVE && isDashboardUpdate) { // Handle panel dragging int header_x = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_XDISTANCE); int header_y = (int)ObjectGetInteger(0, HEADER_LABEL, OBJPROP_YDISTANCE); int header_width = 740; int header_height = 30; if (prev_mouse_state == 0 && mouse_state == 1) { // Mouse button down if (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height) { panel_dragging = true; panel_drag_x = mouse_x; panel_drag_y = mouse_y; panel_start_x = panel_x; panel_start_y = panel_y; ChartSetInteger(0, CHART_MOUSE_SCROLL, false); if (debugLogging) Print("Panel drag started at x=", mouse_x, ", y=", mouse_y); } } if (panel_dragging && mouse_state == 1) { // Dragging panel int dx = mouse_x - panel_drag_x; int dy = mouse_y - panel_drag_y; panel_x = panel_start_x + dx; panel_y = panel_start_y + dy; // Ensure panel stays within chart boundaries int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); panel_x = MathMax(0, MathMin(panel_x, chart_width - 753)); // 753 = panel width panel_y = MathMax(0, MathMin(panel_y, chart_height - 410)); // 410 = panel height // Update positions of all panel objects ObjectSetInteger(0, MAIN_REC, OBJPROP_XDISTANCE, panel_x); ObjectSetInteger(0, MAIN_REC, OBJPROP_YDISTANCE, panel_y); ObjectSetInteger(0, SUB_REC1, OBJPROP_XDISTANCE, panel_x + 3); ObjectSetInteger(0, SUB_REC1, OBJPROP_YDISTANCE, panel_y + 30); ObjectSetInteger(0, SUB_REC2, OBJPROP_XDISTANCE, panel_x + 3 + 5); ObjectSetInteger(0, SUB_REC2, OBJPROP_YDISTANCE, panel_y + 30 + 50 + 27); ObjectSetInteger(0, HEADER_LABEL, OBJPROP_XDISTANCE, panel_x + 3 + 5); ObjectSetInteger(0, HEADER_LABEL, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, TIME_LABEL, OBJPROP_XDISTANCE, panel_x + 20); ObjectSetInteger(0, TIME_LABEL, OBJPROP_YDISTANCE, panel_y + 35); ObjectSetInteger(0, IMPACT_LABEL, OBJPROP_XDISTANCE, panel_x + 20); ObjectSetInteger(0, IMPACT_LABEL, OBJPROP_YDISTANCE, panel_y + 55); ObjectSetInteger(0, FILTER_LABEL, OBJPROP_XDISTANCE, panel_x + 320); ObjectSetInteger(0, FILTER_LABEL, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_XDISTANCE, panel_x + 380); ObjectSetInteger(0, FILTER_CURR_BTN, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_XDISTANCE, panel_x + 490); ObjectSetInteger(0, FILTER_IMP_BTN, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_XDISTANCE, panel_x + 610); ObjectSetInteger(0, FILTER_TIME_BTN, OBJPROP_YDISTANCE, panel_y + 5); ObjectSetInteger(0, CANCEL_BTN, OBJPROP_XDISTANCE, panel_x + 692+10); ObjectSetInteger(0, CANCEL_BTN, OBJPROP_YDISTANCE, panel_y + 1); // Update calendar buttons int startX = panel_x + 9; for (int i = 0; i < ArraySize(array_calendar); i++) { ObjectSetInteger(0, ARRAY_CALENDAR+IntegerToString(i), OBJPROP_XDISTANCE, startX); ObjectSetInteger(0, ARRAY_CALENDAR+IntegerToString(i), OBJPROP_YDISTANCE, panel_y + 82); startX += buttons[i] + 3; } // Update impact buttons for (int i = 0; i < ArraySize(impact_labels); i++) { ObjectSetInteger(0, IMPACT_LABEL+string(i), OBJPROP_XDISTANCE, panel_x + 90 + 100 * i); ObjectSetInteger(0, IMPACT_LABEL+string(i), OBJPROP_YDISTANCE, panel_y + 55); } // Update currency buttons int curr_size = 51, button_height = 22, spacing_x = 0, spacing_y = 3, max_columns = 4; for (int i = 0; i < ArraySize(curr_filter); i++) { int row = i / max_columns; int col = i % max_columns; int x_pos = panel_x + 525 + col * (curr_size + spacing_x); int y_pos = panel_y + 33 + row * (button_height + spacing_y); ObjectSetInteger(0, CURRENCY_BTNS+IntegerToString(i), OBJPROP_XDISTANCE, x_pos); ObjectSetInteger(0, CURRENCY_BTNS+IntegerToString(i), OBJPROP_YDISTANCE, y_pos); } // Update scrollbar if (scroll_visible) { ObjectSetInteger(0, SCROLL_LEADER, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET); ObjectSetInteger(0, SCROLL_LEADER, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET); ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET); ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET - 5); ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE); ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X); ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_YDISTANCE, panel_y + SCROLLBAR_Y_OFFSET + SCROLLBAR_HEIGHT - BUTTON_SIZE - 5); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X); // Y-position of slider is managed by updateSliderPosition() } // Update event display update_dashboard_values(curr_filter_selected, imp_filter_selected); // Update trade labels if they exist if (ObjectFind(0, "NewsCountdown") >= 0) { ObjectSetInteger(0, "NewsCountdown", OBJPROP_XDISTANCE, panel_x); ObjectSetInteger(0, "NewsCountdown", OBJPROP_YDISTANCE, panel_y - 33); } if (ObjectFind(0, "NewsTradeInfo") >= 0) { ObjectSetInteger(0, "NewsTradeInfo", OBJPROP_XDISTANCE, panel_x + 305); ObjectSetInteger(0, "NewsTradeInfo", OBJPROP_YDISTANCE, panel_y - 28); } ChartRedraw(0); if (debugLogging) Print("Panel moved to x=", panel_x, ", y=", panel_y); } if (mouse_state == 0) { // Mouse button released if (panel_dragging) { panel_dragging = false; ChartSetInteger(0, CHART_MOUSE_SCROLL, true); if (debugLogging) Print("Panel drag stopped."); ChartRedraw(0); } } }
En este caso, mejoramos la función OnChartEvent para implementar la funcionalidad de arrastre de paneles, lo que nos permite reubicar la interfaz cuando «isDashboardUpdate» es verdadero y el «id» del evento es CHARTEVENT_MOUSE_MOVE. Recuperamos la posición del encabezado utilizando ObjectGetInteger para obtener «header_x» y «header_y» a partir de «HEADER_LABEL» con «OBJPROP_XDISTANCE» y «OBJPROP_YDISTANCE», definiendo así un área de 740 x 30 píxeles. Cuando «prev_mouse_state» es 0 y «mouse_state» es 1 (botón del ratón pulsado), comprobamos si «mouse_x» y «mouse_y» se encuentran dentro del encabezado, establecemos «panel_dragging» en true, almacenamos «panel_drag_x», «panel_drag_y», «panel_start_x» y «panel_start_y», y desactivamos el desplazamiento del gráfico con ChartSetInteger y CHART_MOUSE_SCROLL.
Si «panel_dragging» es verdadero y «mouse_state» es 1, calculamos el desplazamiento de arrastre «dx» y «dy», actualizamos «panel_x» y «panel_y», y utilizamos MathMax y MathMin para limitarlos dentro de los valores «chart_width» y «chart_height» del gráfico (obtenidos mediante ChartGetInteger con «CHART_WIDTH_IN_PIXELS» y CHART_HEIGHT_IN_PIXELS). A continuación, reubicamos todos los elementos de la interfaz de usuario utilizando ObjectSetInteger con «OBJPROP_XDISTANCE» y «OBJPROP_YDISTANCE» para objetos como «MAIN_REC», «SUB_REC1», «SUB_REC2», «HEADER_LABEL», «TIME_LABEL», «IMPACT_LABEL», «FILTER_LABEL», «FILTER_CURR_BTN», «FILTER_IMP_BTN», «FILTER_TIME_BTN», «CANCEL_BTN», «ARRAY_CALENDAR», «IMPACT_LABEL» y «CURRENCY_BTNS», y actualizar los elementos de la barra de desplazamiento («SCROLL_LEADER», «SCROLL_UP_REC», «SCROLL_UP_LABEL», «SCROLL_DOWN_REC», «SCROLL_DOWN_LABEL», «SCROLL_SLIDER») si «scroll_visible» es verdadero, utilizando «panel_x» y «panel_y» con desplazamientos.
Llamamos a «update_dashboard_values» para actualizar las noticias, reubicar las etiquetas de operaciones «NewsCountdown» y «NewsTradeInfo» —si están presentes— mediante ObjectFind, y volver a dibujar el gráfico con ChartRedraw. Cuando «mouse_state» es 0 (se ha soltado el ratón), establecemos «panel_dragging» en false, volvemos a habilitar el desplazamiento mediante ChartSetInteger y volvemos a dibujar el gráfico, registrando los eventos de arrastre con Print si «debugLogging» está habilitado. A continuación, podemos gestionar la función de la barra de desplazamiento, siempre que el panel no se encuentre en modo de arrastre, para evitar conflictos entre eventos.
// Scrollbar handling (only if not dragging panel) if (scroll_visible && !panel_dragging) { if (prev_mouse_state == 0 && mouse_state == 1) { int xd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); int yd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); int xs = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE); int ys = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); // Skip if mouse is over header to prioritize panel dragging if (mouse_x >= (panel_x + 3 + 5) && mouse_x <= (panel_x + 3 + 5 + 740) && mouse_y >= (panel_y + 5) && mouse_y <= (panel_y + 5 + 30)) { return; } if (mouse_x >= xd && mouse_x <= xd + xs && mouse_y >= yd && mouse_y <= yd + ys) { moving_state_slider = true; mlb_down_x = mouse_x; mlb_down_y = mouse_y; mlb_down_yd_slider = yd; ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrDodgerBlue); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height + 2); ChartSetInteger(0, CHART_MOUSE_SCROLL, false); if (debugLogging) Print("Slider drag started at y=", mouse_y); } } if (moving_state_slider && mouse_state == 1) { int delta_y = mouse_y - mlb_down_y; int new_y = mlb_down_yd_slider + delta_y; int scroll_area_y_min = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE; int scroll_area_y_max = scroll_area_y_min + SCROLL_AREA_HEIGHT - slider_height; new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS); double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min); int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll); if (new_scroll_pos != scroll_pos) { scroll_pos = new_scroll_pos; update_dashboard_values(curr_filter_selected, imp_filter_selected); updateButtonColors(); if (debugLogging) Print("Slider dragged. CurrPos: ", scroll_pos, ", Total steps: ", max_scroll, ", Slider y=", new_y); } ChartRedraw(0); } if (mouse_state == 0) { if (moving_state_slider) { moving_state_slider = false; ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrLightSlateGray); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); ChartSetInteger(0, CHART_MOUSE_SCROLL, true); if (debugLogging) Print("Slider drag stopped."); ChartRedraw(0); } } } prev_mouse_state = mouse_state;
Aquí implementamos la funcionalidad de arrastre de la barra de desplazamiento, lo que nos permite desplazarnos por las noticias cuando «scroll_visible» es verdadero y «panel_dragging» es falso, garantizando así que no haya ningún conflicto con el arrastre del panel. Cuando «prev_mouse_state» es 0 y «mouse_state» es 1 (botón del ratón pulsado), aplicamos la lógica de desplazamiento que ya habíamos implementado, así como los demás estados de arrastre del deslizador y liberación. Tras la compilación, obtenemos el siguiente resultado.

En la visualización podemos observar que es posible pasar el cursor por encima de los botones y arrastrar el panel dentro de los límites del gráfico. Lo que queda ahora es realizar un backtesting exhaustivo del sistema, lo cual se aborda en la siguiente sección.
Pruebas y validación
Hemos probado el panel de control mejorado para confirmar que el movimiento dinámico, los botones con efecto al pasar el cursor y la barra de desplazamiento funcionan según lo previsto, lo que ofrece una experiencia fluida a la hora de navegar por las noticias. Nuestra prueba se centró en la retroalimentación visual de los botones del panel de control al pasar el cursor sobre ellos, el movimiento de todos los componentes del panel en las proximidades del gráfico y la integración de la barra de desplazamiento en modo en vivo. Hemos recopilado estas pruebas en un archivo conciso en formato Graphics Interchange Format (GIF) para mostrar gráficamente el rendimiento del panel de control, tal y como se muestra a continuación.

La visualización muestra que el panel de control funciona correctamente.
Conclusión
En conclusión, hemos mejorado la serie Calendario Económico de MQL5 con la incorporación de un panel de control arrastrable y efectos al pasar el cursor, lo que nos permite un posicionamiento flexible y una navegación intuitiva por las noticias. Estas mejoras, basadas en la barra de desplazamiento dinámica y la visualización optimizada de la Parte 9, garantizan una interacción fluida tanto en modo real como en Strategy Tester, lo que proporciona una plataforma sólida y adaptable para estrategias de trading basadas en noticias. Ahora puede utilizar este versátil panel de control como base y personalizarlo para adaptarlo a sus necesidades específicas de gráficos y trading.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18241
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Redes neuronales en el trading: Pipeline inteligente de predicciones (Final)
Particularidades del trabajo con números del tipo double en MQL4
Características del Wizard MQL5 que debe conocer (Parte 66): Uso de patrones FrAMA y Force Index con el núcleo de producto escalar
- 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