Trading com o calendário econômico MQL5 (Parte 10): Painel arrastável e efeitos de hover nos botões
Introdução
Neste artigo, damos continuidade à série dedicada ao calendário econômico MQL5. Hoje adicionaremos a possibilidade de arrastar o painel e implementaremos efeitos de hover nos botões para facilitar a interação com as notícias, tornando a navegação e o uso da interface mais intuitivos. Partiremos da versão desenvolvida na Parte 9, em que adicionamos uma barra de rolagem dinâmica, e tornaremos a interface do usuário mais adaptável. Também permitiremos mover o calendário pelo gráfico e aperfeiçoaremos o funcionamento dos botões, tornando o calendário econômico ainda mais conveniente tanto na negociação real quanto no Testador de Estratégias. A estrutura do artigo inclui os seguintes tópicos:
- Adição do recurso de arraste do painel no gráfico
- Implementação em MQL5
- Testes e verificação
- Conclusão
Vejamos as alterações.
Adição do recurso de arraste do painel no gráfico
Para facilitar a utilização do calendário econômico MQL5, implementaremos um painel arrastável que permitirá alterar a posição da interface no gráfico, além de uma barra de rolagem com posicionamento dinâmico para navegar pelas notícias. Nosso objetivo é criar uma ferramenta flexível, capaz de se adaptar a diferentes layouts do gráfico. Eliminaremos o posicionamento fixo usado nas versões anteriores e adicionaremos a possibilidade de mover o painel, os eventos e a barra de rolagem. Faremos isso da seguinte forma:
- Projeto do painel arrastável: implementaremos um sistema que detecta os cliques do mouse na área do cabeçalho e permite arrastar todo o painel. As coordenadas de todos os elementos da interface serão atualizadas em tempo real, garantindo que todo o painel seja deslocado suavemente.
- Posicionamento dinâmico da barra de rolagem: passaremos a usar coordenadas relativas à posição do painel, para que a barra de rolagem continue funcional e corretamente posicionada durante o arraste.
- Limites do gráfico: adicionaremos restrições para manter o painel dentro da área visível do gráfico, evitando que ele saia da tela e garantindo que permaneça sempre acessível.
- Movimentação coordenada dos elementos: notícias, botões de filtragem e marcadores de negociação serão deslocados junto com o painel, formando uma interface unificada e profissional.
Com essa abordagem, o painel se tornará uma ferramenta flexível que poderá ser posicionada no local mais conveniente, melhorando a visualização do gráfico e a interação com ele. Em resumo, é isso que pretendemos alcançar.

Implementação em MQL5
Para implementar essas melhorias em MQL5, primeiro precisamos definir funções adicionais para incorporar interatividade dinâmica. Vamos avançar do mais simples para o mais complexo, começando pelos estados de hover e chegando aos estados de arraste. Começaremos definindo uma função void que identificará o estado de hover de um botão com base na posição do cursor no gráfico e atualizará seu estado visual, normalmente escurecendo a cor. Se preferir, também é possível definir cores próprias para o estado de hover. Primeiro, implementaremos a função responsável por escurecer a cor.
//+------------------------------------------------------------------+ //| 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); }
Definimos a função ColorToDarken para gerar uma versão mais escura da cor original. Ela será usada para reforçar o efeito visual ao passar o cursor sobre os botões. O parâmetro de entrada clr representa um único valor numérico que combina os componentes vermelho, verde e azul, cada um variando de 0 a 255, formando uma cor específica. Para separar esses componentes, utilizamos operações bit a bit, que permitem manipular individualmente os bits contidos em clr.
Para extrair o componente vermelho r, utilizamos o operador & (AND bit a bit) com 0xFF, um valor hexadecimal equivalente a 255 em decimal. Ele funciona como uma máscara que seleciona os 8 bits menos significativos de clr, correspondentes à intensidade do vermelho. Para obter o componente verde g, deslocamos clr 8 bits para a direita com o operador >> e aplicamos novamente & com 0xFF para isolar esse componente. De forma semelhante, o componente azul b é extraído deslocando clr 16 bits para a direita e aplicando & com 0xFF. Veja como o valor é representado em hexadecimal:

Para escurecer a cor, subtraímos 50 de cada componente r, g e b, reduzindo sua intensidade. Em seguida, utilizamos a função MathMax para impedir que qualquer valor fique abaixo de 0, já que um componente de cor não pode ser negativo. Depois, recombinamos os componentes escurecidos em um único valor de cor: deslocamos b 16 bits para a esquerda com o operador <<, g em 8 bits e combinamos ambos com r usando o operador OR bit a bit. O valor retornado, do tipo color, é aplicado aos botões, criando um efeito de hover mais escuro e tornando a interface mais intuitiva. Agora podemos usar essa função para formatar dinamicamente os elementos quando o cursor estiver sobre eles.
//+------------------------------------------------------------------+ //| 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; } }
Em seguida, implementaremos a função updateHoverStates para adicionar o efeito de hover ao cabeçalho. Obtemos a posição do cabeçalho HEADER_LABEL com a função ObjectGetInteger e extraímos as coordenadas header_x e header_y por meio das propriedades OBJPROP_XDISTANCE e OBJPROP_YDISTANCE, que delimitam uma área de 740x30 pixels. Verificamos se mouse_x e mouse_y estão dentro dessa área e definimos o flag is_header_hovered conforme necessário.
Se is_header_hovered for true e header_hovered for false, usamos a função ObjectSetInteger para definir a cor de fundo de MAIN_REC (OBJPROP_BGCOLOR) como clrDarkGreen e atualizamos header_hovered para true. Se o cursor sair dessa área e header_hovered estiver como true, restauramos a cor clrSeaGreen e definimos header_hovered como false. A mesma lógica é aplicada aos demais botões.
// 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; } }
Ampliamos a função updateHoverStates para adicionar efeitos de hover a vários botões, fornecendo feedback visual durante a interação do usuário. Para o botão FILTER_CURR_BTN, obtemos as coordenadas curr_btn_x e curr_btn_y com ObjectGetInteger por meio de OBJPROP_XDISTANCE e OBJPROP_YDISTANCE, delimitando uma área de 110x26 pixels. Em seguida, verificamos se mouse_x e mouse_y estão dentro dessa área para definir o flag is_curr_btn_hovered.
Se is_curr_btn_hovered for true e filter_curr_hovered for false, definimos a cor de fundo de FILTER_CURR_BTN (OBJPROP_BGCOLOR) como clrDarkGray usando a função ObjectSetInteger e atualizamos filter_curr_hovered para true. Caso contrário, restauramos a cor clrBlack e definimos filter_curr_hovered como false.
A mesma lógica é aplicada aos botões FILTER_IMP_BTN (120x26 pixels) e FILTER_TIME_BTN (70x26 pixels), atualizando, respectivamente, os estados filter_imp_hovered e filter_time_hovered. No botão de cancelamento CANCEL_BTN (50x30 pixels), a cor alterna entre clrDarkRed e clrRed conforme os estados is_cancel_btn_hovered e cancel_hovered. Para os botões de moedas CURRENCY_BTNS, percorremos cada botão em um laço, calculamos sua posição com base em panel_x e panel_y, determinamos o estado is_curr_hovered e alternamos entre clrLightGray e clrNONE de acordo com currency_btns_hovered[i].
Para os botões de importância IMPACT_LABEL, definimos normal_color de acordo com o nível de importância, por exemplo, clrYellow para Low. Em seguida, calculamos hover_color com ColorToDarken e alternamos a cor com base nos estados is_impact_hovered e impact_btns_hovered[i]. Para que essas alterações sejam aplicadas, chamamos a função no manipulador 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); } }
No manipulador OnChartEvent, primeiro obtemos as coordenadas do mouse mouse_x e mouse_y a partir dos parâmetros lparam e dparam, além do estado do mouse mouse_state a partir de sparam, que indica se o botão está pressionado ou solto. Quando id é igual a CHARTEVENT_MOUSE_MOVE, chamamos updateHoverStates, passando mouse_x e mouse_y para verificar se o cursor está sobre o cabeçalho ou algum botão e atualizar seu estado visual. Dessa forma, os elementos interativos do painel passam a fornecer um feedback visual claro ao usuário. Em seguida, precisamos tratar os cliques para atualizar os elementos e determinar o estado de arraste dos elementos.
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 tratar cliques em objetos quando id é igual a CHARTEVENT_OBJECT_CLICK, primeiro chamamos as funções UpdateFilterInfo e CheckForNewsTrade para atualizar os estados dos filtros e verificar as condições de negociação. Se sparam for igual a CANCEL_BTN, definimos isDashboardUpdate como false e chamamos destroy_Dashboard para fechar o painel. Para os botões FILTER_CURR_BTN, FILTER_IMP_BTN ou FILTER_TIME_BTN, obtemos o estado btn_state com ObjectGetInteger e OBJPROP_STATE, atualizamos enableCurrencyFilter, enableImportanceFilter ou enableTimeFilter e ajustamos o texto e a cor do botão com ObjectSetString e ObjectSetInteger, usando OBJPROP_TEXT e OBJPROP_COLOR. Por exemplo, clrLime indica o estado ativado e clrRed, o estado desativado. Em seguida, chamamos update_dashboard_values e recriamos a barra de rolagem com as funções createRecLabel, createLabel e createButton para os elementos SCROLL_LEADER e SCROLL_SLIDER, posicionando-os com base em panel_x e panel_y. Por fim, atualizamos a posição da barra de rolagem com updateSliderPosition e as cores dos botões com updateButtonColors.
Para os botões de moedas CURRENCY_BTNS, obtemos a moeda selecionada selected_curr com ObjectGetString, adicionamos essa moeda ao array curr_filter_selected ou a removemos dele por meio de ArrayResize e atualizamos o painel. Para os botões de importância do evento IMPACT_LABEL, chamamos a função get_importance_level para associar selected_imp a selected_importance_lvl, atualizamos os arrays imp_filter_selected e impact_filter_selected, definimos a cor da borda color_border com ObjectSetInteger e atualizamos o painel. Se sparam for igual a SCROLL_UP_REC ou SCROLL_UP_LABEL, chamamos scrollUp; se for SCROLL_DOWN_REC ou SCROLL_DOWN_LABEL, chamamos scrollDown. Em seguida, atualizamos as cores com updateButtonColors e redesenhamos o gráfico usando ChartRedraw. Após compilar e executar o programa, obtemos o seguinte resultado.

A animação mostra que o feedback visual ao passar o cursor sobre os botões foi implementado corretamente. O próximo passo é implementar a parte mais complexa, ou seja, o arraste de todo o painel. Para isso, usaremos a seguinte 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); } } }
Ampliamos a função OnChartEvent para permitir arrastar o painel quando isDashboardUpdate for true e id for igual a CHARTEVENT_MOUSE_MOVE. Com ObjectGetInteger, obtemos as coordenadas header_x e header_y de HEADER_LABEL por meio de OBJPROP_XDISTANCE e OBJPROP_YDISTANCE, delimitando uma área de 740x30 pixels. Quando o estado anterior do mouse prev_mouse_state for igual a 0 e o novo estado mouse_state for igual a 1, indicando que o botão do mouse foi pressionado, verificamos se mouse_x e mouse_y estão dentro da área do cabeçalho. Se estiverem, definimos panel_dragging como true, armazenamos as coordenadas em panel_drag_x, panel_drag_y, panel_start_x e panel_start_y e desativamos a rolagem do gráfico com ChartSetInteger e CHART_MOUSE_SCROLL.
Se panel_dragging for true e o estado atual mouse_state for igual a 1, calculamos os deslocamentos dx e dy, atualizamos a posição do painel em panel_x e panel_y e limitamos esses valores com MathMax e MathMin para mantê-los dentro das dimensões do gráfico chart_width e chart_height, obtidas com ChartGetInteger por meio de CHART_WIDTH_IN_PIXELS e CHART_HEIGHT_IN_PIXELS. Em seguida, reposicionamos todos os elementos da interface usando ObjectSetInteger com OBJPROP_XDISTANCE e OBJPROP_YDISTANCE para todos os objetos: 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 e CURRENCY_BTNS. Também atualizamos os elementos da barra de rolagem, SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL, SCROLL_DOWN_REC, SCROLL_DOWN_LABEL e SCROLL_SLIDER, caso scroll_visible seja true, posicionando-os com base em panel_x e panel_y e aplicando os deslocamentos correspondentes.
Chamamos a função update_dashboard_values para atualizar as notícias exibidas no painel e verificamos com ObjectFind se os marcadores de negociação NewsCountdown e NewsTradeInfo existem e, em caso afirmativo, os reposicionamos. Em seguida, executamos ChartRedraw. Quando mouse_state passa a ser 0, indicando que o botão do mouse foi solto, definimos panel_dragging como false, reativamos a rolagem do gráfico com ChartSetInteger e redesenhamos o gráfico, exibindo mensagens com Print quando necessário, caso o parâmetro debugLogging esteja ativado. Depois disso, podemos tratar a lógica da barra de rolagem somente quando o painel não estiver sendo arrastado, evitando conflitos entre os 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;
Aqui implementamos o arraste da barra de rolagem. Essa lógica é executada quando scroll_visible é true e panel_dragging é false, evitando conflitos com o arraste do painel. Quando prev_mouse_state é igual a 0 e mouse_state é igual a 1, aplicamos a lógica de rolagem e de tratamento do estado do controle deslizante implementada anteriormente. Após a compilação, obtemos o seguinte resultado.

Na animação, podemos ver que é possível passar o cursor sobre os botões e arrastar o painel sem ultrapassar os limites do gráfico. Agora falta apenas realizar um teste completo do sistema, o que faremos na próxima seção.
Testes e validação
Testamos o painel atualizado para confirmar que o arraste do painel, os estados de hover dos botões e a barra de rolagem funcionam corretamente. Durante os testes, verificamos todo o feedback visual dos botões ao passar o cursor sobre eles, o deslocamento de todos os componentes do painel dentro dos limites do gráfico e a integração da barra de rolagem em tempo real. Os resultados são apresentados em um GIF (Graphics Interchange Format) compacto para demonstrar visualmente o funcionamento do painel.

Como podemos ver, o painel funciona sem falhas.
Conclusão
Nesta parte da série sobre o calendário econômico MQL5, implementamos um painel arrastável e efeitos interativos de hover nos botões. Espero que isso torne a navegação pelos eventos mais prática. Como base para essas melhorias, utilizamos os recursos desenvolvidos na Parte 9, que incluíam uma barra de rolagem dinâmica e um design aprimorado. Os novos recursos podem ser usados tanto na negociação real quanto no Testador de Estratégias, oferecendo uma plataforma confiável para estratégias de trading baseadas em notícias Você pode usar este painel aprimorado como ponto de partida e adaptá-lo às suas próprias necessidades de negociação.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18241
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Criação de EAs auto-otimizáveis em MQL5 (Parte 7): Negociação simultânea com indicadores de vários períodos
Criando um Painel Administrativo de Trading em MQL5 (Parte X): Interface baseada em recursos externos
Recursos do Assistente MQL5 que você precisa conhecer (Parte 67): Uso dos padrões do TRIX e da Faixa Percentual de Williams
Repensando Estratégias Clássicas (Parte 14): Configurações de Alta Probabilidade
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso