使用MQL5经济日历进行交易(第十部分):可拖动仪表盘与交互式悬停效果,实现流畅的新闻导航·进阶篇
📘

使用MQL5经济日历进行交易(第十部分):可拖动仪表盘与交互式悬停效果,实现流畅的新闻导航·进阶篇

第 2/3 篇

鼠标悬停时按钮怎么变色

在 MT5 自定义面板里做鼠标交互,核心是先算坐标命中区,再改对象背景色。下面这段逻辑分别处理了货币按钮和经济事件影响级别按钮的悬停反馈,命中区宽度用了固定像素:货币按钮宽 curr_size、影响标签宽 100,纵向高度分别是 button_height 和 25。 货币按钮部分只做二态切换:未悬停时背景设 clrNONE(透明),刚进入命中区时改成 clrLightGray,并用 currency_btns_hovered[i] 记录状态避免重复刷色。影响标签按钮则按字符串映射常态色——None/Low/Medium/High 分别对应黑/黄/橙/红,悬停时调 ColorToDarken 压暗一档,黑色特例换成 clrDarkGray。 事件入口挂在 OnChartEvent:CHARTEVENT_MOUSE_MOVE 时把 lparam、dparam 强转成鼠标 x/y 送进 updateHoverStates;CHARTEVENT_OBJECT_CLICK 则触发过滤刷新与新闻交易检查,点取消按钮会把 isDashboardUpdate 置 false。开 MT5 把这段塞进 EA,挂上图表就能直接看悬停变色效果,外汇与贵金属品种波动剧烈,面板交互层别挡住关键 K 线。

MQL5 / C++
  class="type">bool is_curr_hovered = (mouse_x >= x_pos && mouse_x <= x_pos + curr_size &&
                         mouse_y >= y_pos && mouse_y <= y_pos + button_height);
  class="type">class="kw">string btn_name = CURRENCY_BTNS+IntegerToString(i);
  if (is_curr_hovered && !currency_btns_hovered[i]) {
      ObjectSetInteger(class="num">0, btn_name, OBJPROP_BGCOLOR, clrLightGray);
      currency_btns_hovered[i] = true;
  } else if (!is_curr_hovered && currency_btns_hovered[i]) {
      ObjectSetInteger(class="num">0, btn_name, OBJPROP_BGCOLOR, clrNONE);
      currency_btns_hovered[i] = class="kw">false;
  }
}
class=class="str">"cmt">// IMPACT_LABEL buttons hover
class="type">int impact_size = class="num">100;
for (class="type">int i = class="num">0; i < ArraySize(impact_labels); i++) {
   class="type">int x_pos = panel_x + class="num">90 + impact_size * i;
   class="type">int y_pos = panel_y + class="num">55;
   class="type">bool is_impact_hovered = (mouse_x >= x_pos && mouse_x <= x_pos + impact_size &&
                             mouse_y >= y_pos && mouse_y <= y_pos + class="num">25);
   class="type">class="kw">string btn_name = IMPACT_LABEL+class="type">class="kw">string(i);
   class="type">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;
   class="type">color hover_color = normal_color == clrBlack ? clrDarkGray : ColorToDarken(normal_color);
   if (is_impact_hovered && !impact_btns_hovered[i]) {
      ObjectSetInteger(class="num">0, btn_name, OBJPROP_BGCOLOR, hover_color);
      impact_btns_hovered[i] = true;
   } else if (!is_impact_hovered && impact_btns_hovered[i]) {
      ObjectSetInteger(class="num">0, btn_name, OBJPROP_BGCOLOR, normal_color);
      impact_btns_hovered[i] = class="kw">false;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Chart event handler                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id, const class="type">long& lparam, const class="type">class="kw">double& dparam, const class="type">class="kw">string& sparam) {
   class="type">int mouse_x = (class="type">int)lparam;
   class="type">int mouse_y = (class="type">int)dparam;
   class="type">int mouse_state = (class="type">int)sparam;
   class=class="str">"cmt">// Update hover states
   if (id == CHARTEVENT_MOUSE_MOVE) {
      updateHoverStates(mouse_x, mouse_y);
   }
}
if (id == CHARTEVENT_OBJECT_CLICK) {
   UpdateFilterInfo();
   CheckForNewsTrade();
   if (sparam == CANCEL_BTN) {
      isDashboardUpdate = class="kw">false;

「币种过滤按钮与滚动条重建逻辑」

面板里的币种过滤开关不是单纯改个布尔量就完事。点击 FILTER_CURR_BTN 后,代码先读 OBJPROP_STATE 拿到按钮状态写进 enableCurrencyFilter,再用 ShortToString(0x2714) 和 0x274C 拼出对勾或叉号文本,颜色随状态在 clrLime 与 clrRed 间切换。 若处于策略测试器(MQLInfoInteger(MQL_TESTER) 为真),会把 filters_changed 置 true,随后调用 update_dashboard_values 刷新数据。调试日志会打出按钮状态与过滤标志,方便你直接在 MT5 专家日志里核对。 过滤条件变了,旧滚动条对象必须整组删掉重画。代码依次 ObjectDelete 了 SCROLL_LEADER、SCROLL_UP_REC、SCROLL_UP_LABEL、SCROLL_DOWN_REC、SCROLL_DOWN_LABEL、SCROLL_SLIDER 共 6 个图形对象,再根据 totalEvents_Filtered > VISIBLE_ITEMS 决定 scroll_visible。 可见项不足时滚动条隐藏;否则按 SCROLLBAR_X_OFFSET 等偏移量重建轨道与上下按钮,并用 Webdings 字体 0x35 / 0x36 画箭头。到顶时 up_color 变 clrLightGray,到底时 down_color 同理,slider_height 由 calculateSliderHeight() 算,slider_y 从轨道顶端加按钮尺寸起排。

MQL5 / C++
   destroy_Dashboard();
  }
  if (sparam == FILTER_CURR_BTN) {
    class="type">bool btn_state = ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE);
    enableCurrencyFilter = btn_state;
    if (debugLogging) Print(sparam+" STATE = "+(class="type">class="kw">string)btn_state+", FLAG = "+(class="type">class="kw">string)enableCurrencyFilter);
    class="type">class="kw">string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency";
    class="type">color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed;
    ObjectSetString(class="num">0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text);
    ObjectSetInteger(class="num">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);
    class=class="str">"cmt">// Recalculate scrollbar
    ObjectDelete(class="num">0, SCROLL_LEADER);
    ObjectDelete(class="num">0, SCROLL_UP_REC);
    ObjectDelete(class="num">0, SCROLL_UP_LABEL);
    ObjectDelete(class="num">0, SCROLL_DOWN_REC);
    ObjectDelete(class="num">0, SCROLL_DOWN_LABEL);
    ObjectDelete(class="num">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, class="num">1, clrNONE);
      class="type">int max_scroll = MathMax(class="num">0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
      class="type">color up_color = (scroll_pos == class="num">0) ? clrLightGray : clrBlack;
      class="type">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, class="num">1, clrDarkGray);
      createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-class="num">5, CharToString(0x35), up_color, class="num">15, "Webdings");
      class="type">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, class="num">1, clrDarkGray);
      createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-class="num">5, CharToString(0x36), down_color, class="num">15, "Webdings");
      slider_height = calculateSliderHeight();
      class="type">int slider_y = panel_y + SCROLLBAR_Y_OFFSET + BUTTON_SIZE;

◍ 重要性过滤触发后的滚动条重建逻辑

在 MT5 自定义面板里,点下重要性过滤按钮(FILTER_IMP_BTN)后,代码先读 OBJPROP_STATE 拿到按钮新状态,写进 enableImportanceFilter。接着用 ShortToString(0x2714) 拼出对勾、ShortToString(0x274C) 拼出叉号,分别配 clrLime / clrRed 改按钮文字与颜色,这一步直接决定盘面视觉反馈。 如果跑在策略测试器里(MQLInfoInteger(MQL_TESTER) 为真),filters_changed 置 true,随后 update_dashboard_values 按当前筛选重算展示数据。这里有个容易漏的点:过滤条件变了,事件总数可能变,所以旧滚动条组件必须整体删掉重画。 代码连续 ObjectDelete 了 SCROLL_LEADER、SCROLL_UP_REC、SCROLL_UP_LABEL、SCROLL_DOWN_REC、SCROLL_DOWN_LABEL、SCROLL_SLIDER 六个对象,再用 scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS 判断是否需要滚动条。假设 VISIBLE_ITEMS 是 10,过滤后剩 15 条,scroll_visible 为真,就会以 panel_x + SCROLLBAR_X_OFFSET 为基准重建背景与上下按钮。 重建时 max_scroll 取 ArraySize(displayableEvents) - VISIBLE_ITEMS 与 0 的较大值;scroll_pos 到顶时 up_color 变 clrLightGray 表示不可再上翻,到底时 down_color 同理。这种状态色切换能让交易者一眼看出滚动边界,外汇与贵金属行情事件密集时尤其有用,但这类 UI 改动不影响订单执行,杠杆品种高风险仍在。

MQL5 / C++
createButton(SCROLL_SLIDER, panel_x + SCROLLBAR_X_OFFSET + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, class="num">12, clrLightSlateGray, clrDarkGray, "Arial Bold");
ObjectSetInteger(class="num">0, SCROLL_SLIDER, OBJPROP_WIDTH, class="num">2);
if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
updateSliderPosition();
updateButtonColors();
}
if (debugLogging) Print("Success. Changes updated! State: "+(class="type">class="kw">string)enableCurrencyFilter);
ChartRedraw(class="num">0);
 }
 if (sparam == FILTER_IMP_BTN) {
  class="type">bool btn_state = ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE);
  enableImportanceFilter = btn_state;
  if (debugLogging) Print(sparam+" STATE = "+(class="type">class="kw">string)btn_state+", FLAG = "+(class="type">class="kw">string)enableImportanceFilter);
  class="type">class="kw">string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance";
  class="type">color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed;
  ObjectSetString(class="num">0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text);
  ObjectSetInteger(class="num">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);
  class=class="str">"cmt">// Recalculate scrollbar
  ObjectDelete(class="num">0, SCROLL_LEADER);
  ObjectDelete(class="num">0, SCROLL_UP_REC);
  ObjectDelete(class="num">0, SCROLL_UP_LABEL);
  ObjectDelete(class="num">0, SCROLL_DOWN_REC);
  ObjectDelete(class="num">0, SCROLL_DOWN_LABEL);
  ObjectDelete(class="num">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, class="num">1, clrNONE);
    class="type">int max_scroll = MathMax(class="num">0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
    class="type">color up_color = (scroll_pos == class="num">0) ? clrLightGray : clrBlack;
    class="type">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, class="num">1, clrDarkGray);

时间过滤开关与滚动条重建逻辑

在面板回调里,点下 FILTER_TIME_BTN 会先读按钮的 OBJPROP_STATE,把 enableTimeFilter 同步成该布尔值;调试日志会打出类似 FILTER_TIME_BTN STATE = true, FLAG = true 的串,方便你确认事件没丢。 文本与颜色跟着状态走:开启时拼出 ✔Time(0x2714)染成 clrLime,关闭则是 ✖Time(0x274C)染成 clrRed,用 ObjectSetString / ObjectSetInteger 直接改对象属性,不用重画整个面板。 若在策略测试器里跑(MQLInfoInteger(MQL_TESTER) 为真),filters_changed 置 true,随后 update_dashboard_values 按当前过滤重算。紧跟着把 SCROLL_LEADER、上下按钮、滑块等 6 个对象全删掉,依据 totalEvents_Filtered > VISIBLE_ITEMS 决定滚动条是否重现——可见项阈值由 VISIBLE_ITEMS 常量控,调它就能改触发滚动的条数。 滚动条初次创建时,向上箭头用 Webdings 字体 0x35、向下 0x36 画符号,滑块宽被强行设成 2 像素(ObjectSetInteger 改 OBJPROP_WIDTH),slider_height 由 calculateSliderHeight() 算,日志会带回 totalEvents_Filtered 与 slider_height 两个实数供你核对。外汇与贵金属行情事件密度波动大,这类面板过滤在高波动时段可能频繁触发重建,注意 MT5 对象句柄泄漏风险。

MQL5 / C++
createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-class="num">5, CharToString(0x35), up_color, class="num">15, "Webdings");
class="type">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, class="num">1, clrDarkGray);
createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-class="num">5, CharToString(0x36), down_color, class="num">15, "Webdings");
slider_height = calculateSliderHeight();
class="type">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, class="num">12, clrLightSlateGray, clrDarkGray, "Arial Bold");
ObjectSetInteger(class="num">0, SCROLL_SLIDER, OBJPROP_WIDTH, class="num">2);
if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
updateSliderPosition();
updateButtonColors();
}
if (debugLogging) Print("Success. Changes updated! State: "+(class="type">class="kw">string)enableImportanceFilter);
ChartRedraw(class="num">0);
  }
  if (sparam == FILTER_TIME_BTN) {
   class="type">bool btn_state = ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE);
   enableTimeFilter = btn_state;
   if (debugLogging) Print(sparam+" STATE = "+(class="type">class="kw">string)btn_state+", FLAG = "+(class="type">class="kw">string)enableTimeFilter);
   class="type">class="kw">string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time";
   class="type">color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed;
   ObjectSetString(class="num">0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text);
   ObjectSetInteger(class="num">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);
   class=class="str">"cmt">// Recalculate scrollbar
   ObjectDelete(class="num">0, SCROLL_LEADER);
   ObjectDelete(class="num">0, SCROLL_UP_REC);
   ObjectDelete(class="num">0, SCROLL_UP_LABEL);
   ObjectDelete(class="num">0, SCROLL_DOWN_REC);
   ObjectDelete(class="num">0, SCROLL_DOWN_LABEL);
   ObjectDelete(class="num">0, SCROLL_SLIDER);
   scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
   if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
   if (scroll_visible) {

「财经事件面板的滚动条与币种过滤逻辑」

事件面板在渲染时先画出银色轨道背景,再用 MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS) 算出最大滚动量;当 scroll_pos 到达 0 或 max_scroll 边界时,对应方向箭头会被染成 clrLightGray 表示不可点,否则为 clrBlack。上下按钮分别用 Webdings 字体 0x35、0x36 字符绘制,滑块高度由 calculateSliderHeight() 动态得出并贴着轨道内缩进。 调试开关 debugLogging 打开后,会在终端打印 totalEvents_Filtered 与 slider_height 的实际值,方便你核对过滤后的事件总数是否和面板行数匹配。 币种按钮走另一套回调:点击后读 OBJPROP_STATE,若为 false 表示取消选中,就从 curr_filter_selected 数组里做前向覆盖删除。该操作直接改变后续 displayableEvents 的过滤结果,面板重绘前记得看 ChartRedraw(0) 是否触发。 外汇与贵金属事件受宏观数据冲击大,这类面板过滤仅降低信息噪声,不预示价格方向,实盘仍需自担高风险。

MQL5 / C++
createRecLabel(SCROLL_LEADER, panel_x + SCROLLBAR_X_OFFSET, panel_y + SCROLLBAR_Y_OFFSET, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, class="num">1, clrNONE);
class="type">int max_scroll = MathMax(class="num">0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
class="type">color up_color = (scroll_pos == class="num">0) ? clrLightGray : clrBlack;
class="type">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, class="num">1, clrDarkGray);
createLabel(SCROLL_UP_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, panel_y + SCROLLBAR_Y_OFFSET-class="num">5, CharToString(0x35), up_color, class="num">15, "Webdings");
class="type">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, class="num">1, clrDarkGray);
createLabel(SCROLL_DOWN_LABEL, panel_x + SCROLLBAR_X_OFFSET + BUTTON_OFFSET_X, down_y-class="num">5, CharToString(0x36), down_color, class="num">15, "Webdings");
slider_height = calculateSliderHeight();
class="type">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, class="num">12, clrLightSlateGray, clrDarkGray, "Arial Bold");
ObjectSetInteger(class="num">0, SCROLL_SLIDER, OBJPROP_WIDTH, class="num">2);
if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
updateSliderPosition();
updateButtonColors();
}
if (debugLogging) Print("Success. Changes updated! State: "+(class="type">class="kw">string)enableTimeFilter);
ChartRedraw(class="num">0);
}
if (StringFind(sparam,CURRENCY_BTNS) >= class="num">0) {
class="type">class="kw">string selected_curr = ObjectGetString(class="num">0,sparam,OBJPROP_TEXT);
if (debugLogging) Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr);
class="type">bool btn_state = ObjectGetInteger(class="num">0,sparam,OBJPROP_STATE);
if (btn_state == class="kw">false) {
if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE.");
for (class="type">int i = class="num">0; i < ArraySize(curr_filter_selected); i++) {
if (curr_filter_selected[i] == selected_curr) {
for (class="type">int j = i; j < ArraySize(curr_filter_selected) - class="num">1; j++) {
curr_filter_selected[j] = curr_filter_selected[j + class="num">1];

◍ 按钮状态切换与滚动条重算的收尾逻辑

这段逻辑处在面板交互的末端:当货币对按钮被点中且处于已选状态时,先遍历 curr_filter_selected 数组判断是否已经收录,避免重复添加导致后续统计失真。若未收录则用 ArrayResize 扩容并写入,已收录仅打日志不动作。 操作完选中数组后,代码会删除旧滚动条的全部图元(SCROLL_LEADER、SCROLL_UP_REC 等 6 个对象),再根据 totalEvents_Filtered 与 VISIBLE_ITEMS 的大小关系决定 scroll_visible。实测中若过滤后事件数不超过可见行数,滚动条直接隐藏,省去无效绘制。 在测试环境下(MQLInfoInteger(MQL_TESTER) 为真)会把 filters_changed 置 true,触发回测中的重算;同时调用 update_dashboard_values 刷新面板。最后若 scroll_visible 为真,重画滑道并按 scroll_pos 是否为 0 给上箭头分配 clrLightGray 或 clrBlack,提示当前是否已滚到顶。外汇与贵金属品种波动剧烈,这类 UI 状态切换不影响报价风险,但误触筛选可能让你漏看关键事件。

MQL5 / C++
            }
            ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - class="num">1);
            if (debugLogging) Print("Removed from selected filters: ", selected_curr);
            break;
         }
      }
   } else {
      if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      class="type">bool already_selected = class="kw">false;
      for (class="type">int j = class="num">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) + class="num">1);
         curr_filter_selected[ArraySize(curr_filter_selected) - class="num">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);
   class=class="str">"cmt">// Recalculate scrollbar
   ObjectDelete(class="num">0, SCROLL_LEADER);
   ObjectDelete(class="num">0, SCROLL_UP_REC);
   ObjectDelete(class="num">0, SCROLL_UP_LABEL);
   ObjectDelete(class="num">0, SCROLL_DOWN_REC);
   ObjectDelete(class="num">0, SCROLL_DOWN_LABEL);
   ObjectDelete(class="num">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, class="num">1, clrNONE);
      class="type">int max_scroll = MathMax(class="num">0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
      class="type">color up_color = (scroll_pos == class="num">0) ? clrLightGray : clrBlack;

常见问题

先确认悬停事件里是否重绘了按钮背景色,再检查颜色变量有没有被过滤逻辑覆盖;可在事件回调里打印状态定位。
每次重建币种按钮都要同步重算滚动条最大范围与当前偏移,避免旧偏移超出新内容长度导致跳动。
小布可读取你的面板交互配置,提示哪些过滤开关缺少滚动条重建调用,并给出重算顺序建议。
大概率是的。触发重要性过滤后必须销毁旧滚动条并按新事件数重建,否则视图不会刷新。
可以,但要在开关回调里统一调用收尾的重算函数,确保两种过滤都走到滚动条重算与按钮状态同步。