English Deutsch
preview
MQL5経済指標カレンダーを使った取引(第10回):シームレスなニュースナビゲーションのためのドラッグ可能ダッシュボードとインタラクティブホバー効果

MQL5経済指標カレンダーを使った取引(第10回):シームレスなニュースナビゲーションのためのドラッグ可能ダッシュボードとインタラクティブホバー効果

MetaTrader 5トレーディング |
17 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

本記事では、MQL5経済指標カレンダーの連載をさらに発展させ、ドラッグ可能なダッシュボードとインタラクティブなホバー効果を導入することで、ニュースイベントの操作性とナビゲーションを向上させ、柔軟で直感的なユーザー体験を実現します。第9回で実装した動的スクロールバーと洗練された表示を基盤に、今回はチャート上でカレンダーを再配置できるレスポンシブなユーザーインターフェース(UI)に焦点を当て、ボタン操作の視覚的フィードバックを提供します。これにより、リアルタイムおよびバックテスト環境における経済ニュースへのアクセスが最適化されます。この記事は次のトピックで構成されています。

  1. チャートの柔軟性を高めるドラッグ可能なダッシュボードの追加
  2. MQL5での実装
  3. テストと検証
  4. 結論

これらの機能強化について詳しく見ていきましょう。


チャートの柔軟性を高めるドラッグ可能なダッシュボードの追加

MQL5経済指標カレンダーの使いやすさを向上させるため、チャート上でインターフェースを自由に再配置できるドラッグ可能なダッシュボードを導入します。これに加えて、動的に位置が変化するスクロールバーを組み合わせ、ニュースのナビゲーションをスムーズに維持します。トレーダー中心のツールを目指し、従来の固定位置の制約を排除し、ダッシュボード、ニュースイベント、スクロールバーが一体となって動くようにします。これを実現する方法は以下の通りです。

  • ドラッグ可能なダッシュボード設定:ヘッダ領域でのマウスクリックを検知するシステムを実装し、ユーザーがダッシュボード全体をドラッグできるようにします。すべてのUI要素はリアルタイムで位置を更新し、整列を維持します。
  • 動的スクロールバーの位置調整:スクロールバーをダッシュボードの位置に連動する相対座標で調整し、ドラッグ中も正しく機能するようにします。
  • チャート境界の制約:ダッシュボードがチャートの表示範囲外に移動しないよう制限を設け、常にアクセス可能な状態を保ちます。
  • 要素の一体的な移動:ニュースイベント、フィルターボタン、トレードラベルがダッシュボードと同期して移動するようにし、統一感のあるプロフェッショナルなインターフェースを提供します。

この戦略的アプローチにより、ダッシュボードは必要に応じて自由に配置できる柔軟なツールとなり、チャートの視認性と操作性を向上させます。簡単に言えば、目指す成果は以下の通りです。

戦略計画


MQL5での実装

MQL5でこれらの改善をおこなうためには、まず動的なインタラクティブ性を強化するためのいくつかの追加関数を定義する必要があります。最も単純なものから複雑なものへ順に進めていきます。具体的には、まずホバー状態とドラッグ状態から始めます。まず、カーソルの位置に応じてボタンがホバー状態かどうかを判定し、ボタンの状態を更新するvoid関数を定義します。通常はボタンを暗くすることでホバー状態を表現しますが、ホバー時の色は任意に設定可能です。まず、色を暗くする処理を担当する関数から定義していきましょう。

//+------------------------------------------------------------------+
//| 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);
}

まず、入力された色を暗くするColorToDarken関数を定義します。これは、ボタンにカーソルを重ねた際に視覚的フィードバックを強化するために使用します。入力として受け取る色clrは、赤、緑、青の各成分を0~255の範囲で組み合わせた単一の数値で、特定の色を表現します。この成分を分離するために、ビット演算を用いてclrの各ビットを操作します。

赤成分「r」については、ビット単位のAND演算子「&」を0xFFと組み合わせて使用します。ここで0xFFは16進数で255に相当し、clrの下位8ビットのみを抽出して赤の強度を取得するフィルターの役割を果たします。緑成分「g」は、右シフト演算子「>>」でclrを8ビット右にシフトし、緑のデータを下位8ビットに移動させた後、「& 0xFF」で分離します。同様に、青成分「b」はclrを16ビット右にシフトして「& 0xFF」を適用し、抽出します。16進数での表記は以下の通りです。

255 16進数0xFF

色を暗くするために、各成分「r」「g」「b」から50を減算して強度を下げます。この際、MathMax関数を使用して0未満にならないように制御します(色の値は負になれません)。最後に、暗くした各成分を再び単一の色値に結合します。青成分「b」を16ビット左シフト「<<」、緑成分「g」を8ビット左シフトし、「r」とビット単位のOR演算子「|」で合成します。この新しいcolor値を返し、ボタンに適用することでホバー時に暗くなる効果を実現し、インターフェースをより直感的で操作しやすくします。この関数により、ホバー時の色を動的に設定する処理に活用できるようになります。

//+------------------------------------------------------------------+
//| 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;
   }
}

ここでは、ヘッダーにホバー効果を追加するupdateHoverStates関数を実装します。まず、ObjectGetIntegerを使用してHEADER_LABELの位置を取得し、OBJPROP_XDISTANCEOBJPROP_YDISTANCEからheader_xとheader_yを取得します。これにより、幅740ピクセル、高さ30ピクセルの領域を定義します。次に、mouse_xとmouse_yがこの領域内にあるかどうかをチェックし、is_header_hoveredを設定します。

is_header_hoveredがtrueで、かつheader_hoveredがfalseの場合、ObjectSetIntegerを使用してMAIN_RECのOBJPROP_BGCOLORをclrDarkGreenに設定し、header_hoveredをtrueに更新します。逆に、is_header_hoveredがfalseでheader_hoveredがtrueの場合は、背景色をclrSeaGreenに戻し、header_hoveredをfalseに設定します。これにより、ホバー時の視覚的フィードバックが提供されます。同じ手法を他のボタンにも適用することが可能です。

// 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;
   }
}

updateHoverStates関数を拡張し、複数のボタンにホバー効果を追加して、ユーザー操作に対する視覚的フィードバックを提供します。まずFILTER_CURR_BTNについては、ObjectGetIntegerを使用してOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEから座標curr_btn_xとcurr_btn_yを取得し、幅110ピクセル、高さ26ピクセルの領域を定義します。その後、mouse_xとmouse_yがこの領域内にあるかを確認し、is_curr_btn_hoveredを設定します。

is_curr_btn_hoveredがtrueかつfilter_curr_hoveredがfalseの場合、ObjectSetIntegerを使用してFILTER_CURR_BTNのOBJPROP_BGCOLORclrDarkGrayに設定し、filter_curr_hoveredをtrueに更新します。そうでない場合は、背景色をclrBlackに戻し、filter_curr_hoveredをfalseに設定します。

同様のロジックをFILTER_IMP_BTN(120×26ピクセル)およびFILTER_TIME_BTN(70×26ピクセル)に適用し、それぞれfilter_imp_hoveredとfilter_time_hoveredを更新します。CANCEL_BTN(50×30ピクセル)については、is_cancel_btn_hoveredとcancel_hoveredに応じてclrDarkRedとclrRedを切り替えます。CURRENCY_BTNSについては、各ボタンをループ処理し、panel_xとpanel_yから位置を計算してis_curr_hoveredを確認し、currency_btns_hovered[i]に応じてclrLightGrayとclrNONEを切り替えます。

IMPACT_LABELボタンについては、影響レベルに応じてnormal_colorを割り当て(例:LowならclrYellow)、ColorToDarkenを使用してhover_colorを計算します。その後、is_impact_hoveredとimpact_btns_hovered[i]に基づき色を切り替え、動的なホバー効果を実現します。これらの変更を有効にするため、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);
   }
}

OnChartEventイベントハンドラでは、まずlparamとdparamからマウス座標mouse_xとmouse_yを取得し、sparamからマウス状態mouse_stateを取得します。これにより、マウスの位置とクリック、リリースの状態がわかります。イベントidがCHARTEVENT_MOUSE_MOVEの場合、updateHoverStates関数を呼び出し、mouse_xとmouse_yを渡して、マウスがヘッダーやボタン上にあるかを確認し、それに応じて視覚表示を更新します。これにより、ダッシュボードのインタラクティブ要素に対してレスポンシブなホバー効果が得られます。その後、クリック時の他の要素の更新を取得し、移動状態を判定する必要があります。

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);
   }
}

動的オブジェクトのクリックイベントを強化するため、イベントidがCHARTEVENT_OBJECT_CLICKの場合、まずUpdateFilterInfoとCheckForNewsTradeを呼び出し、フィルター状態を更新し取引条件を確認します。sparamがCANCEL_BTNの場合、isDashboardUpdateをfalseに設定し、destroy_Dashboardを呼び出してダッシュボードを閉じます。FILTER_CURR_BTN、FILTER_IMP_BTN、またはFILTER_TIME_BTNの場合、ObjectGetIntegerを使用してOBJPROP_STATEからbtn_stateを取得し、enableCurrencyFilter、enableImportanceFilter、またはenableTimeFilterを更新します。その後、ObjectSetStringとObjectSetIntegerを使用してOBJPROP_TEXTおよびOBJPROP_COLORを調整します(例:有効時はclrLime、無効時はclrRed)。続いてupdate_dashboard_valuesを呼び出し、createRecLabel、createLabel、createButtonを用いてSCROLL_LEADERやSCROLL_SLIDERなどの要素をpanel_xとpanel_yに配置し、updateSliderPositionとupdateButtonColorsで状態を更新します。

CURRENCY_BTNSについては、ObjectGetStringでselected_currを取得し、ArrayResizeを使用してcurr_filter_selectedに追加または削除し、ダッシュボードを同様に更新します。IMPACT_LABELボタンについては、get_importance_levelを呼び出してselected_impをselected_importance_lvlにマッピングし、imp_filter_selectedおよびimpact_filter_selected配列を管理します。ObjectSetIntegerでcolor_borderを設定し、ダッシュボードを更新します。sparamがSCROLL_UP_RECまたはSCROLL_UP_LABELの場合はscrollUpを呼び出し、SCROLL_DOWN_RECまたはSCROLL_DOWN_LABELの場合はscrollDownを呼び出します。その後、updateButtonColorsで色を更新し、ChartRedrawでチャートを再描画して変更を反映させます。プログラムをコンパイルして実行すると、次の結果が得られます。

ホバー可能なボタン

可視化から、定義したボタン上で視覚的なホバーフィードバックが得られることが確認できます。次に取り組むべきは、より複雑な部分であるダッシュボード全体の移動です。これは以下のロジックによって実現されます。

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);
      }
   }

}

ここでは、OnChartEvent関数を拡張してパネルのドラッグ機能を実装します。これにより、isDashboardUpdateがtrueでイベントidがCHARTEVENT_MOUSE_MOVEの場合に、インターフェースの位置を再配置できるようになります。まず、ObjectGetIntegerを使用してHEADER_LABELからOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEを取得し、header_xとheader_yを取得します。これにより、幅740ピクセル、高さ30ピクセルのヘッダー領域を定義します。prev_mouse_stateが0でmouse_stateが1(マウスボタン押下)の場合、mouse_xとmouse_yがヘッダー内にあるかを確認し、panel_draggingをtrueに設定します。また、panel_drag_x、panel_drag_y、panel_start_x、panel_start_yを保存し、ChartSetIntegerCHART_MOUSE_SCROLLを使用してチャートのスクロールを無効化します。

panel_draggingがtrueでmouse_stateが1の場合、ドラッグオフセットdxとdyを計算し、panel_xとpanel_yを更新します。MathMaxおよびMathMinを使用して、ChartGetIntegerで取得したチャートのchart_widthとchart_height内に制約します(CHART_WIDTH_IN_PIXELSおよびCHART_HEIGHT_IN_PIXELSを使用)。その後、ObjectSetIntegerでOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEを用いて、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、CURRENCY_BTNSなどすべてのUI要素を再配置します。また、scroll_visibleがtrueの場合は、SCROLL_LEADER、SCROLL_UP_REC、SCROLL_UP_LABEL、SCROLL_DOWN_REC、SCROLL_DOWN_LABEL、SCROLL_SLIDERなどスクロールバー要素もpanel_xとpanel_yおよびオフセットを用いて更新します。

update_dashboard_valuesを呼び出してニュースイベントを更新し、存在する場合はObjectFindを用いて取引ラベルNewsCountdownとNewsTradeInfoを再配置し、ChartRedrawでチャートを再描画します。mouse_stateが0(マウスリリース)の場合、panel_draggingをfalseに設定し、ChartSetIntegerでスクロールを再度有効化してチャートを再描画します。debugLoggingが有効な場合は、Printでドラッグイベントをログに出力します。その後、パネルがドラッグモードでない場合に限り、スクロールバー機能を処理してイベントの競合を防ぎます。

// 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;

ここでは、スクロールバーのドラッグ機能を実装します。これにより、scroll_visibleがtrueでpanel_draggingがfalseの場合に、ニュースイベントをスクロールできるようになり、パネルのドラッグとの競合を防ぎます。prev_mouse_stateが0でmouse_stateが1(マウスボタン押下)の場合、既に実装したスクロールロジックおよび他のスライダーやリリース状態の処理を適用します。コンパイルすると以下の出力が得られます。

動きの相互作用

可視化から、ボタン上でホバーでき、チャートの端に近い範囲内でパネルをドラッグできることが確認できます。 次に残る作業は、システムの徹底的なバックテストであり、それは次のセクションで扱います。


テストと検証

強化されたダッシュボードをテストし、動的な移動、ホバー可能なボタン、およびスクロールバーが意図どおりに機能し、ニュースイベントのナビゲーションがシームレスにおこなえることを確認しました。テストでは、ダッシュボード全体のボタンのホバー時の視覚的フィードバック、チャートの範囲内でのパネル全体の移動、およびライブモードでのスクロールバーの統合性に重点を置きました。これらのテストは、ダッシュボードのパフォーマンスを視覚的に示すため、簡潔なGraphics Interchange Format (GIF)に記録しました。以下に示します。

バックテストGIF

可視化から、ダッシュボードがシームレスに動作することが確認できます。


結論

結論として、ドラッグ可能なダッシュボードとインタラクティブなホバー効果を導入することで、MQL5経済指標カレンダーの連載をさらに進化させました。これにより、ニュースイベントの柔軟な位置調整と直感的なナビゲーションが可能になりました。これらの強化機能は、第9回に実装した動的スクロールバーと洗練された表示を基盤としており、ライブモードおよびストラテジーテスターモードの両方でシームレスな操作を保証し、ニュース駆動型の取引戦略に適した堅牢で柔軟なプラットフォームを提供します。この多用途ダッシュボードを基盤として活用し、独自のチャートや取引ニーズに合わせてカスタマイズすることが可能です。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18241

添付されたファイル |
MQL5で自己最適化エキスパートアドバイザーを構築する(第7回):複数期間での同時取引 MQL5で自己最適化エキスパートアドバイザーを構築する(第7回):複数期間での同時取引
本連載記事では、テクニカル指標を使用する際の最適な期間を特定するためのさまざまな方法を検討してきました。本記事では、読者に対して逆のロジックを示します。すなわち、単一の最適期間を選ぶのではなく、利用可能なすべての期間を効果的に活用する方法を示します。このアプローチにより廃棄されるデータ量が減少し、通常の価格予測以外に機械学習アルゴリズムを活用する方法も得られます。
MetaTrader 5のPythonでMQL5のような取引クラスを構築する MetaTrader 5のPythonでMQL5のような取引クラスを構築する
MetaTrader 5のPythonパッケージは、Python言語でMetaTrader 5プラットフォーム用の取引アプリケーションを構築する簡単な方法を提供しますが、強力で有用なツールである一方で、アルゴリズム取引ソリューションを作成する際にはMQL5プログラミング言語ほど容易ではありません。本記事では、MQL5で提供されているものに類似した取引クラスを構築し、類似した構文を作成することで、MQL5と同様にPythonで自動売買ロボットをより簡単に作成できるようにします。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初心者からエキスパートへ:自動幾何解析システム 初心者からエキスパートへ:自動幾何解析システム
幾何学的パターンは、トレーダーに価格動向を簡潔に解釈する手段を提供します。多くのアナリストは手作業でトレンドラインや長方形、その他の形状を描き、形成されたパターンに基づいて取引判断をおこないます。本記事では、自動化による代替手段、すなわちMQL5を活用して最も一般的な幾何学パターンを検出・分析する方法を探ります。方法論を分解して説明し、実装の詳細を論じ、自動パターン認識がトレーダーの市場洞察をどのように鋭くできるかを強調します。