English Русский Deutsch
preview
MQL5取引ツール(第19回):チャート描画用のインタラクティブなツールパレットの構築

MQL5取引ツール(第19回):チャート描画用のインタラクティブなツールパレットの構築

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

はじめに

前回の第18回では、MetaQuotes Language 5 (MQL5)において、ベクターベースの矩形と三角形を組み合わせることで、向きを制御できる角丸の吹き出しを作成し、トレードインターフェース向けの動的なUI要素を実現しました。第19回となる今回は、ドラッグ可能なパネル、リサイズ機能、テーマ切り替え、および十字カーソル、ライン、図形などのツール用ボタンを備えた、チャート描画用のインタラクティブなツールパレットを開発します。このカスタマイズ可能なシステムは、リアルタイムな操作と操作ガイドを提供することで、分析作業を効率化します。本記事では以下のトピックを扱います。

  1. チャート向けインタラクティブツールパレットの設計
  2. MQL5での実装
  3. バックテスト
  4. 結論

本記事を読み終える頃には、トレードワークフローの拡張に活用できる、実用的なツールパレットが完成します。それでは始めましょう。


チャート向けインタラクティブツールパレットの設計

チャート描画用のインタラクティブなツールパレットは、十字カーソル、トレンドライン、ライン、矩形、フィボナッチ、テキスト、矢印などの主要な描画機能をボタンとして一元化し、トレードチャート上で素早く選択・適用できるようにします。また、リアルタイムなフィードバックを提供することで、操作性を向上させます。さらに、このツールパレットは、ドラッグによる移動、リサイズ、ダークテーマとライトテーマの切り替え、および作業領域を効率的に活用するための最小化機能をサポートします。加えて、操作ガイドやステータス更新を表示することで、直感的な操作を実現します。この設計により、チャートを煩雑にすることなく、ユーザーの操作に応じて柔軟に適応する、カスタマイズ可能で応答性の高いUIを提供し、分析作業を効率化します。実装では、ヘッダーおよびパネルの描画にキャンバスオブジェクトを使用し、インタラクティブな操作を実現するためにマウスイベントを管理します。また、リサイズモードやツールを表す列挙型を定義し、チャート上へのオブジェクト生成によって描画処理を実装します。ここでは、目標とするシステムの概要を視覚的に示します。

ツールパレットの目的


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                                                Tools Palette.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas canvasHeader;                                   //--- Declare header canvas
CCanvas canvasTools;                                    //--- Declare tools canvas

string canvasHeaderName = "HeaderCanvas";               //--- Set header canvas name
string canvasToolsName = "ToolsCanvas";                 //--- Set tools canvas name

input int CanvasX = 20;                                 // Panel X position
input int CanvasY = 30;                                 // Panel Y position
input int CanvasWidth = 350;                            // Panel width
input int CanvasHeight = 200;                           // Panel height
input double BackgroundOpacity = 0.8;                   // Background opacity

bool is_dark_theme = true;                              //--- Set initial dark theme flag
color DarkHeaderColor = C'60,60,60';                    //--- Set dark header color
color DarkHeaderHoverColor = clrRed;                    //--- Set dark header hover color
color DarkHeaderDragColor = clrMediumBlue;              //--- Set dark header drag color
color DarkHeaderTextColor = clrWhite;                   //--- Set dark header text color
color DarkBorderColor = clrBlack;                       //--- Set dark border color
color DarkTopColor = clrBlack;                          //--- Set dark top color
color DarkIconColor = clrWhite;                         //--- Set dark icon color
color LightHeaderColor = clrSilver;                     //--- Set light header color
color LightHeaderHoverColor = clrRed;                   //--- Set light header hover color
color LightHeaderDragColor = clrMediumBlue;             //--- Set light header drag color
color LightHeaderTextColor = clrBlack;                  //--- Set light header text color
color LightBorderColor = clrBlack;                      //--- Set light border color
color LightTopColor = clrWhite;                         //--- Set light top color
color LightIconColor = clrBlack;                        //--- Set light icon color
color DarkInstructionColor = clrLime;                   //--- Set dark instruction color
color LightInstructionColor = clrGreen;                 //--- Set light instruction color
color DarkStatusColor = clrMagenta;                     //--- Set dark status color
color LightStatusColor = clrPurple;                     //--- Set light status color
color DarkActiveBtnColor = clrGreen;                    //--- Set dark active button color
color LightActiveBtnColor = clrDarkGreen;               //--- Set light active button color
color DarkHoverBtnColor = clrDarkGray;                  //--- Set dark hover button color
color LightHoverBtnColor = clrLightGray;                //--- Set light hover button color

int currentCanvasX = CanvasX;                           //--- Initialize current X position
int currentCanvasY = CanvasY;                           //--- Initialize current Y position
int currentWidth = CanvasWidth;                         //--- Initialize current width
int currentHeight = CanvasHeight;                       //--- Initialize current height
bool panel_dragging = false;                            //--- Initialize panel dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                 //--- Initialize drag coordinates
int panel_start_x = 0, panel_start_y = 0;               //--- Initialize start coordinates
bool resizing = false;                                  //--- Initialize resizing flag

enum ENUM_RESIZE_MODE {
   NONE,                                                // No resize
   BOTTOM,                                              // Bottom resize
   RIGHT,                                               // Right resize
   BOTTOM_RIGHT                                         // Bottom-right resize
};

ENUM_RESIZE_MODE resize_mode = NONE;                    //--- Initialize resize mode
ENUM_RESIZE_MODE hover_mode = NONE;                     //--- Initialize hover mode
int resize_start_x = 0, resize_start_y = 0;             //--- Initialize resize start coordinates
int start_width = 0, start_height = 0;                  //--- Initialize start dimensions
const int resize_thickness = 5;                         //--- Set resize thickness
const int min_width = 150;                              //--- Set minimum width
const int min_height = 200;                             //--- Set minimum height
int hover_mouse_local_x = 0;                            //--- Initialize local hover X
int hover_mouse_local_y = 0;                            //--- Initialize local hover Y
bool header_hovered = false;                            //--- Initialize header hover flag
bool minimize_hovered = false;                          //--- Initialize minimize hover flag
bool close_hovered = false;                             //--- Initialize close hover flag
bool theme_hovered = false;                             //--- Initialize theme hover flag
bool resize_hovered = false;                            //--- Initialize resize hover flag
int prev_mouse_state = 0;                               //--- Initialize previous mouse state
int header_height = 27;                                 //--- Set header height
int button_size = 25;                                   //--- Set button size
int theme_x_offset = -75;                               //--- Set theme X offset
int minimize_x_offset = -50;                            //--- Set minimize X offset
int close_x_offset = -25;                               //--- Set close X offset
bool panel_minimized = false;                           //--- Initialize minimized flag

enum TOOL_TYPE {
   TOOL_NONE,                                           // No tool
   TOOL_CROSSHAIR,                                      // Crosshair tool
   TOOL_TRENDLINE,                                      // Trendline tool
   TOOL_HLINE,                                          // Horizontal line tool
   TOOL_VLINE,                                          // Vertical line tool
   TOOL_RECTANGLE,                                      // Rectangle tool
   TOOL_FIBO,                                           // Fibonacci tool
   TOOL_TEXT,                                           // Text tool
   TOOL_ARROW                                           // Arrow tool
};

TOOL_TYPE active_tool = TOOL_NONE;                      //--- Initialize active tool
bool drawing_first_click = false;                       //--- Initialize first click flag
datetime draw_time1 = 0;                                //--- Initialize draw time 1
double draw_price1 = 0.0;                               //--- Initialize draw price 1
long local_chart_id = 0;                                //--- Initialize local chart ID
string current_instruction = "";                        //--- Initialize current instruction

struct ToolButton {                                     // Define tool button structure
   string symbol;                                       // Store symbol
   string font;                                         // Store font
   TOOL_TYPE type;                                      // Store type
   string tooltip;                                      // Store tooltip
};

ToolButton buttons[8];                                  //--- Declare buttons array
int hovered_tool_index = -1;                            //--- Initialize hovered tool index

bool measuring = false;                                 //--- Initialize measuring flag
datetime fixed_time = 0;                                //--- Initialize fixed time
double fixed_price = 0.0;                               //--- Initialize fixed price
ulong last_click_time = 0;                              //--- Initialize last click time

const int tools_y_offset = -1;                          //--- Set tools Y offset

まず、パレットを構築するためのコアグラフィック機能にアクセスするために、「#include <Canvas/Canvas.mqh>」を使用してCanvasライブラリを含めることから実装を開始します。次に、パネルのヘッダーと本体を描画するためのグローバルなキャンバスオブジェクトとしてcanvasHeaderとcanvasToolsを宣言し、それぞれに「HeaderCanvas」と「ToolsCanvas」の名前を割り当てて参照できるようにします。また、位置、サイズ、背景の不透明度を設定する入力パラメータを定義し、ユーザーがパレットの表示を自由にカスタマイズできるようにします。テーマ切り替えに対応するため、is_dark_themeをtrueに初期化し、ダークテーマとライトテーマのそれぞれについて、ヘッダー、境界線、アイコン、操作ガイド、ステータス表示、およびボタンに使用する色定数を定義します。これにより、テーマを切り替えても一貫した外観を維持できます。

パネルの状態は、現在の位置とサイズを保持する変数、ドラッグ操作用のフラグ、ENUM_RESIZE_MODE列挙型(NONE、BOTTOM、RIGHT、BOTTOM_RIGHT)によるリサイズモード、ホバー状態、および操作性を確保するための最小サイズや判定領域の厚さなどの定数によって管理します。描画ツールについては、NONEからARROWまでのツールを定義したTOOL_TYPE列挙型を用意し、active_toolをNONEに初期化します。また、drawing_first_clickなどの描画状態を管理するフラグも用意します。さらに、シンボル、フォント、ツール種別、およびツールチップを保持するToolButton構造体を定義し、ツールパレットの各項目を格納するbuttons[8]配列を作成します。最後に、計測モード、固定ポイント、クリック時間、ホバー中のボタンインデックス、およびツールの配置位置を調整するためのY方向オフセットなどの変数を用意し、インタラクティブな機能を実装するための準備を整えます。続いて、コードをモジュール化して保守性を高めるために、いくつかのヘルパー関数を実装します。

//+------------------------------------------------------------------+
//| Get theme-aware colors                                           |
//+------------------------------------------------------------------+
color GetHeaderColor() { return is_dark_theme ? DarkHeaderColor : LightHeaderColor; }                //--- Return header color based on theme
color GetHeaderHoverColor() { return is_dark_theme ? DarkHeaderHoverColor : LightHeaderHoverColor; } //--- Return header hover color based on theme
color GetHeaderDragColor() { return is_dark_theme ? DarkHeaderDragColor : LightHeaderDragColor; }    //--- Return header drag color based on theme
color GetHeaderTextColor() { return is_dark_theme ? DarkHeaderTextColor : LightHeaderTextColor; }    //--- Return header text color based on theme
color GetBorderColor() { return is_dark_theme ? DarkBorderColor : LightBorderColor; }                //--- Return border color based on theme
color GetTopColor() { return is_dark_theme ? DarkTopColor : LightTopColor; }                         //--- Return top color based on theme
color GetIconColor() { return is_dark_theme ? DarkIconColor : LightIconColor; }                      //--- Return icon color based on theme
color GetInstructionColor() { return is_dark_theme ? DarkInstructionColor : LightInstructionColor; } //--- Return instruction color based on theme
color GetStatusColor() { return is_dark_theme ? DarkStatusColor : LightStatusColor; }                //--- Return status color based on theme
color GetActiveBtnColor() { return is_dark_theme ? DarkActiveBtnColor : LightActiveBtnColor; }       //--- Return active button color based on theme
color GetHoverBtnColor() { return is_dark_theme ? DarkHoverBtnColor : LightHoverBtnColor; }          //--- Return hover button color based on theme

//+------------------------------------------------------------------+
//| Get tool name from type                                          |
//+------------------------------------------------------------------+
string GetToolName(TOOL_TYPE t) {
   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      if (buttons[i].type == t) {                               //--- Check if type matches
         return StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Return tool name
      }
   }
   return "None";                                               //--- Return default if no match
}

//+------------------------------------------------------------------+
//| Check if tool is single-click                                    |
//+------------------------------------------------------------------+
bool IsSingleClickTool(TOOL_TYPE t) {
   switch(t) {                                                  //--- Switch on tool type
      case TOOL_HLINE:                                          //--- Handle horizontal line
      case TOOL_VLINE:                                          //--- Handle vertical line
      case TOOL_TEXT:                                           //--- Handle text
      case TOOL_ARROW:                                          //--- Handle arrow
         return true;                                           //--- Return true for single-click tools
      default:                                                  //--- Handle default
         return false;                                          //--- Return false otherwise
   }
}

続いて、GetHeaderColorのような一連のgetter関数を定義し、テーマに応じた色を取得できるようにします。これらの関数は、is_dark_themeフラグに基づいてダークテーマまたはライトテーマ用の色を返すため、ヘッダー、ホバー状態、ドラッグ状態、テキスト、境界線、アイコン、操作ガイド、ステータス表示、アクティブなボタン、およびホバー時の色を、一貫したスタイルで管理できます。また、各所でテーマ判定を繰り返す必要がなくなります。次に、GetToolName関数を実装します。この関数は、buttons配列を走査して指定されたツール種別と一致する項目を検索し、対応するツール名を取得します。取得したツールチップから「 Tool」までの部分を切り出して、表示用の名称を返します。一致する項目が見つからない場合は、Noneを返します。ツールの種類を判定するために、IsSingleClickTool関数も実装します。この関数は、ツール種別に対してswitch文を使用し、水平線、垂直線、テキスト、および矢印のように1回のクリックだけで配置できるツールについてはtrueを返し、それ以外のツールについてはfalseを返します。これにより、各ツールに応じた適切なイベント処理をおこなえるようになります。これらの準備が整ったら、ヘッダーとツールパレット本体の作成を初期化していきます。

//+------------------------------------------------------------------+
//| Draw header                                                      |
//+------------------------------------------------------------------+
void DrawHeader() {
   canvasHeader.Erase(0);                                       //--- Erase header canvas

   color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Get header background color
   canvasHeader.FillRectangle(0, 0, currentWidth - 1, header_height - 1, ColorToARGB(header_bg, 255)); //--- Fill header rectangle

   uint argbBorder = ColorToARGB(GetBorderColor(), 255);        //--- Get border ARGB
   canvasHeader.Rectangle(0, 0, currentWidth - 1, header_height - 1, argbBorder); //--- Draw border rectangle

   canvasHeader.FontSet("Arial Bold", 13);                      //--- Set font for title
   canvasHeader.TextOut(10, (header_height - 13)/2, "Tools Palette", ColorToARGB(GetHeaderTextColor(), 255)); //--- Draw title text

   int button_y = (header_height - 20)/2;                       //--- Compute button Y position

   int theme_button_x = currentWidth + theme_x_offset;          //--- Compute theme button X
   canvasHeader.FontSet("Wingdings", 20);                       //--- Set font for theme symbol
   string theme_symbol = CharToString(91);                      //--- Set theme symbol
   int theme_w = canvasHeader.TextWidth(theme_symbol);          //--- Get theme symbol width
   int theme_x = theme_button_x + (button_size - theme_w) / 2;  //--- Compute theme X position
   color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Get theme color
   canvasHeader.TextOut(theme_x, button_y, theme_symbol, ColorToARGB(theme_color, 255)); //--- Draw theme symbol

   int min_button_x = currentWidth + minimize_x_offset;         //--- Compute minimize button X
   string min_symbol = panel_minimized ? CharToString(111) : CharToString(114); //--- Set minimize symbol
   int min_w = canvasHeader.TextWidth(min_symbol);              //--- Get minimize symbol width
   int min_x = min_button_x + (button_size - min_w) / 2;        //--- Compute minimize X position
   color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Get minimize color
   canvasHeader.TextOut(min_x, button_y, min_symbol, ColorToARGB(min_color, 255)); //--- Draw minimize symbol

   int close_button_x = currentWidth + close_x_offset;          //--- Compute close button X
   canvasHeader.FontSet("Webdings", 20);                        //--- Set font for close symbol
   string close_symbol = CharToString(114);                     //--- Set close symbol
   int close_w = canvasHeader.TextWidth(close_symbol);          //--- Get close symbol width
   int close_x = close_button_x + (button_size - close_w) / 2;  //--- Compute close X position
   color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Get close color
   canvasHeader.TextOut(close_x, button_y, close_symbol, ColorToARGB(close_color, 255)); //--- Draw close symbol

   canvasHeader.Update();                                       //--- Update header canvas
}

//+------------------------------------------------------------------+
//| Draw tools panel                                                 |
//+------------------------------------------------------------------+
void DrawToolsPanel() {
   canvasTools.Erase(0);                                        //--- Erase tools canvas

   uint argb_fill = ColorToARGB(GetTopColor(), (uchar)(255 * BackgroundOpacity)); //--- Get fill ARGB
   canvasTools.FillRectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_fill); //--- Fill background rectangle

   uint argb_border = ColorToARGB(GetBorderColor(), 255);       //--- Get border ARGB
   canvasTools.Rectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_border); //--- Draw border rectangle

   int columns = 4;                                             //--- Set number of columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height
   int start_x = padding;                                       //--- Set start X
   int start_y = padding;                                       //--- Set start Y

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = start_x + col * (button_width + padding);         //--- Compute X position
      int y = start_y + row * (button_height + padding);        //--- Compute Y position

      color btn_bg = (active_tool == buttons[i].type) ? GetActiveBtnColor() : ((i == hovered_tool_index) ? GetHoverBtnColor() : clrNONE); //--- Get button background
      if (btn_bg != clrNONE) canvasTools.FillRectangle(x, y, x + button_width - 1, y + button_height - 1, ColorToARGB(btn_bg, 255)); //--- Fill button rectangle if needed

      canvasTools.FontSet(buttons[i].font, 35);                 //--- Set font for icon
      int text_w = canvasTools.TextWidth(buttons[i].symbol);    //--- Get icon width
      int text_h = canvasTools.TextHeight(buttons[i].symbol);   //--- Get icon height
      int icon_x = x + (button_width - text_w) / 2;             //--- Compute icon X
      int icon_y = y + 5;                                       //--- Set icon Y
      canvasTools.TextOut(icon_x, icon_y, buttons[i].symbol, ColorToARGB(GetIconColor(), 255)); //--- Draw icon

      string tool_name = StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Get tool name
      canvasTools.FontSet("Arial Bold", 12);                    //--- Set font for name
      int name_w = canvasTools.TextWidth(tool_name);            //--- Get name width
      canvasTools.TextOut(x + (button_width - name_w) / 2, y + button_height - 20, tool_name, ColorToARGB(GetIconColor(), 255)); //--- Draw tool name
   }

   if (StringLen(current_instruction) > 0) {                    //--- Check if instruction exists
      canvasTools.FontSet("Arial", 14);                         //--- Set font for instruction
      int instr_w = canvasTools.TextWidth(current_instruction); //--- Get instruction width
      int instr_x = (currentWidth - instr_w) / 2;               //--- Compute instruction X
      canvasTools.TextOut(instr_x, currentHeight - 30, current_instruction, ColorToARGB(GetInstructionColor(), 255)); //--- Draw instruction
   }

   string status_text = "Active: " + GetToolName(active_tool);  //--- Set status text
   canvasTools.FontSet("Arial Bold", 14);                       //--- Set font for status
   int status_w = canvasTools.TextWidth(status_text);           //--- Get status width
   int status_x = (currentWidth - status_w) / 2;                //--- Compute status X
   canvasTools.TextOut(status_x, currentHeight - 15, status_text, ColorToARGB(GetStatusColor(), 255)); //--- Draw status

   if (resize_hovered || resizing) {                            //--- Check if resize hovered or resizing
      string icon_font = "Wingdings 3";                         //--- Set icon font
      int icon_size = 25;                                       //--- Set icon size
      uchar icon_code = 0;                                      //--- Initialize icon code
      int angle = 0;                                            //--- Initialize angle
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: icon_code = (uchar)'2'; angle = 0; break; //--- Set for bottom
         case RIGHT: icon_code = (uchar)'1'; angle = 0; break;  //--- Set for right
         case BOTTOM_RIGHT: icon_code = (uchar)'2'; angle = 450; break; //--- Set for bottom-right
      }
      if (icon_code != 0) {                                     //--- Check if icon code set
         canvasTools.FontSet(icon_font, icon_size);             //--- Set font for icon
         canvasTools.FontAngleSet(angle);                       //--- Set font angle
         color icon_color = (resize_hovered || resizing) ? clrRed : GetIconColor(); //--- Get icon color
         int icon_x = (hover_mode == BOTTOM) ? MathMin(MathMax(hover_mouse_local_x - icon_size/2, 0), currentWidth - icon_size) : currentWidth - icon_size - 2; //--- Compute icon X
         int icon_y = (hover_mode == RIGHT) ? MathMin(MathMax(hover_mouse_local_y - icon_size/2, 0), currentHeight - icon_size) : currentHeight - icon_size - 2; //--- Compute icon Y
         if (hover_mode == BOTTOM_RIGHT) { icon_x = currentWidth - icon_size - 10; icon_y = currentHeight - icon_size; } //--- Adjust for bottom-right
         canvasTools.TextOut(icon_x, icon_y, CharToString(icon_code), ColorToARGB(icon_color, 255)); //--- Draw icon
         canvasTools.FontAngleSet(0);                           //--- Reset font angle
      }
   }

   canvasTools.Update();                                        //--- Update tools canvas
}

初期レンダリングを作成するために、DrawHeader関数を実装してパレットのヘッダーをキャンバス上に描画します。処理は、Eraseを0に設定して既存の内容を消去することから開始します。その後、ドラッグ中の場合はGetHeaderDragColor、ホバー中の場合はGetHeaderHoverColor、それ以外の場合は標準カラーを取得し、動的な背景色をARGB形式に変換して矩形を塗りつぶします。次に、GetBorderColorから取得した色をARGBに変換し、Rectangleを使用してボーダーを描画します。タイトル表示用には太字の"Arial"フォントを設定し、テーマのテキストカラーを使用してFontSetTextOutによりタイトル「Tools Palette」を描画します。コントロールを追加するために、まずボタンのY座標が中央揃えになるよう計算します。テーマボタンについては、Wingdingsフォントを設定し、ホバー状態に応じて「[」の位置と色を調整し、TextOutで描画します。同様に、最小化ボタンでは'o'または「r」を切り替えて表示し、閉じるボタンではWebdingsフォントを使用して「X」を描画します。各ボタンはホバー状態に応じて、黄色や赤などの色に変更します。最後にUpdateを呼び出してキャンバスを更新し、変更内容を表示します。必要に応じて、アイコンには独自の文字を使用してカスタマイズできます。使用可能なフォント文字一覧については、フォントシンボルの一覧を参照してください。

シンボルフォント

DrawToolsPanel関数では、まずツールパレット用のキャンバスを消去し、BackgroundOpacityを反映したARGB値を使用して背景色で塗りつぶした後、外枠の矩形を描画します。続いて、ボタンを4列に配置し、余白を考慮しながらレイアウトをおこないます。ループ処理では、ボタンがアクティブまたはホバー状態の場合に、GetActiveBtnColorまたはGetHoverBtnColorを使用して背景を描画します。その後、「Wingdings」の「v」のようなアイコンには専用フォントを設定し、TextOutを使用してアイコンカラーで中央に描画します。さらに、その下にツール名を太字で表示します。current_instructionに操作ガイドが設定されている場合は、GetInstructionColorを使用して、パネル下部中央付近にガイドを描画します。同様に、現在アクティブなツールの状態も表示します。リサイズ操作を示すために、resize_hoveredまたはresizingがtrueの場合は、「Wingdings 3」フォントを設定し、hover_modeに応じて矢印コードと回転角度を決定します。例えば、右下方向のリサイズでは文字「2」を450度の角度で使用します。矢印の描画位置は現在の状態に応じて動的に決定し、リサイズ中またはリサイズ可能な状態であることを示すため、赤色またはアイコンカラーで描画した後、回転角度を元に戻します。最後に、Updateを呼び出してキャンバスを更新し、描画内容を反映します。これでツールパレット本体の描画処理が完成したので、次は以下のロジックを使用して、このパレットを初期化および描画します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   currentCanvasX = CanvasX;                                    //--- Set current X from input
   currentCanvasY = CanvasY;                                    //--- Set current Y from input
   currentWidth = CanvasWidth;                                  //--- Set current width from input
   currentHeight = CanvasHeight;                                //--- Set current height from input

   if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, currentWidth, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create header canvas
      Print("Failed to create Header Canvas");                  //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   if (!canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create tools canvas
      Print("Failed to create Tools Canvas");                   //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   buttons[0].symbol = CharToString('v'); buttons[0].font = "Wingdings"; buttons[0].type = TOOL_CROSSHAIR; buttons[0].tooltip = "Crosshair Tool"; //--- Initialize button 0
   buttons[1].symbol = CharToString('.'); buttons[1].font = "Wingdings 3"; buttons[1].type = TOOL_TRENDLINE; buttons[1].tooltip = "Trendline Tool"; //--- Initialize button 1
   buttons[2].symbol = CharToString('*'); buttons[2].font = "Wingdings 3"; buttons[2].type = TOOL_HLINE; buttons[2].tooltip = "Horizontal Line Tool"; //--- Initialize button 2
   buttons[3].symbol = CharToString('+'); buttons[3].font = "Wingdings 3"; buttons[3].type = TOOL_VLINE; buttons[3].tooltip = "Vertical Line Tool"; //--- Initialize button 3
   buttons[4].symbol = CharToString('c'); buttons[4].font = "Webdings"; buttons[4].type = TOOL_RECTANGLE; buttons[4].tooltip = "Rectangle Tool"; //--- Initialize button 4
   buttons[5].symbol = CharToString('"'); buttons[5].font = "Webdings"; buttons[5].type = TOOL_FIBO; buttons[5].tooltip = "Fibonacci Tool"; //--- Initialize button 5
   buttons[6].symbol = CharToString('>'); buttons[6].font = "Webdings"; buttons[6].type = TOOL_TEXT; buttons[6].tooltip = "Text Tool"; //--- Initialize button 6
   buttons[7].symbol = CharToString(225); buttons[7].font = "Wingdings"; buttons[7].type = TOOL_ARROW; buttons[7].tooltip = "Arrow Tool"; //--- Initialize button 7

   DrawHeader();                                                //--- Draw header
   DrawToolsPanel();                                            //--- Draw tools panel

   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);            //--- Enable mouse move events
   ChartRedraw();                                               //--- Redraw chart
   local_chart_id = ChartID();                                  //--- Set local chart ID
   return(INIT_SUCCEEDED);                                      //--- Return initialization success
}

続いて、OnInitイベントハンドラで初期化処理を実装します。まず、ユーザー入力で指定された値を基に、現在のパネル位置とサイズを初期化し、設定内容と一致するようにします。次に、透過処理のためにARGB値を正規化してCreateBitmapLabelを使用し、ヘッダー用キャンバスを作成します。作成に失敗した場合はエラーログを出力し、INIT_FAILEDを返して初期化を終了します。同様に、Y方向のオフセットを考慮してヘッダーの下に配置されるツールパレット用キャンバスも作成します。ツールパレットの内容を構成するために、buttons配列の各要素を初期化します。各要素には、十字カーソル用の「v」のようなシンボル、Wingdingsなどのフォント、列挙型で定義したツール種別、およびホバー時に表示するツールチップを設定します。その後、それぞれの描画関数を呼び出してヘッダーとツールパレットを描画します。さらに、ChartSetIntegerを使用してマウス移動イベントを有効化し、ユーザー操作を取得できるようにします。最後に、チャートを再描画し、オブジェクト管理のためにローカルチャートIDを保存して、INIT_SUCCEEDEDを返します。コンパイルすると、次の結果が得られます。

パネル初期化

パネルが正しく初期化されていることが確認できます。次に、このパネルにインタラクティブな機能とテーマ切り替え機能を実装していきます。そのために、実装を容易にするため、さらにいくつかの補助関数を定義します。

//+------------------------------------------------------------------+
//| Toggle theme                                                     |
//+------------------------------------------------------------------+
void ToggleTheme() {
   is_dark_theme = !is_dark_theme;                              //--- Toggle theme flag
   DrawHeader();                                                //--- Redraw header
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Toggle minimize                                                  |
//+------------------------------------------------------------------+
void ToggleMinimize() {
   panel_minimized = !panel_minimized;                          //--- Toggle minimized flag
   if (panel_minimized) {                                       //--- Check if minimized
      canvasTools.Destroy();                                    //--- Destroy tools canvas
   } else {                                                     //--- Handle restored
      canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE); //--- Recreate tools canvas
      DrawToolsPanel();                                         //--- Redraw tools panel
   }
   DrawHeader();                                                //--- Redraw header
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Is mouse over header excluding buttons                           |
//+------------------------------------------------------------------+
bool IsMouseOverHeader(int mx, int my) {
   if (mx < currentCanvasX || mx > currentCanvasX + currentWidth || my < currentCanvasY || my > currentCanvasY + header_height) return false; //--- Check outside header
   int btn_left = currentCanvasX + currentWidth + theme_x_offset; //--- Compute buttons left
   if (mx >= btn_left && mx <= btn_left + button_size * 3) return false; //--- Check over buttons
   return true;                                                 //--- Return true if over header
}

//+------------------------------------------------------------------+
//| Is mouse over specific button                                    |
//+------------------------------------------------------------------+
bool IsMouseOverButton(int mx, int my, int offset) {
   int btn_left = currentCanvasX + currentWidth + offset;       //--- Compute button left
   return (mx >= btn_left && mx <= btn_left + button_size && my >= currentCanvasY && my <= currentCanvasY + header_height); //--- Return if over button
}

//+------------------------------------------------------------------+
//| Is mouse over resize area                                        |
//+------------------------------------------------------------------+
bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rm) {
   if (panel_minimized) return false;                           //--- Return false if minimized
   int tx = currentCanvasX;                                     //--- Set tools X
   int ty = currentCanvasY + header_height + tools_y_offset;    //--- Set tools Y
   int tr = tx + currentWidth;                                  //--- Set right
   int tb = ty + currentHeight;                                 //--- Set bottom
   bool over_right = mx >= tr - resize_thickness && mx <= tr && my >= ty && my <= tb; //--- Check over right
   bool over_bottom = my >= tb - resize_thickness && my <= tb && mx >= tx && mx <= tr; //--- Check over bottom
   if (over_bottom && over_right) { rm = BOTTOM_RIGHT; return true; } //--- Set and return bottom-right
   if (over_bottom) { rm = BOTTOM; return true; }               //--- Set and return bottom
   if (over_right) { rm = RIGHT; return true; }                 //--- Set and return right
   return false;                                                //--- Return false otherwise
}

//+------------------------------------------------------------------+
//| Get hovered tool                                                 |
//+------------------------------------------------------------------+
int GetHoveredTool(int mx, int my) {
   int lx = mx - currentCanvasX;                                //--- Compute local X
   int ly = my - (currentCanvasY + header_height + tools_y_offset); //--- Compute local Y
   if (lx < 0 || lx > currentWidth || ly < 0 || ly > currentHeight) return -1; //--- Return -1 if outside

   int columns = 4;                                             //--- Set columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = padding + col * (button_width + padding);         //--- Compute X
      int y = padding + row * (button_height + padding);        //--- Compute Y
      if (lx >= x && lx < x + button_width && ly >= y && ly < y + button_height) return i; //--- Return index if over
   }
   return -1;                                                   //--- Return -1 if none
}

//+------------------------------------------------------------------+
//| Toggle tool activation                                           |
//+------------------------------------------------------------------+
void ToggleTool(TOOL_TYPE type) {
   if (active_tool == type) {                                   //--- Check if already active
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair deactivation
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = TOOL_NONE;                                  //--- Deactivate tool
      drawing_first_click = false;                              //--- Reset first click
      current_instruction = "";                                 //--- Clear instruction
   } else {                                                     //--- Handle activation
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Deactivate previous crosshair
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = type;                                       //--- Set active tool
      drawing_first_click = false;                              //--- Reset first click
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair instruction
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      } else {                                                  //--- Handle other tools
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Deactivate current tool on Escape                                |
//+------------------------------------------------------------------+
void DeactivateTool() {
   if (active_tool == TOOL_CROSSHAIR) {                         //--- Check if crosshair active
      DeleteCrosshair();                                        //--- Delete crosshair
      measuring = false;                                        //--- Reset measuring
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
   }
   active_tool = TOOL_NONE;                                     //--- Deactivate tool
   drawing_first_click = false;                                 //--- Reset first click
   current_instruction = "";                                    //--- Clear instruction
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Delete measure objects                                           |
//+------------------------------------------------------------------+
void DeleteMeasureObjects() {
   ObjectDelete(local_chart_id, "Cross_HF");                    //--- Delete fixed horizontal
   ObjectDelete(local_chart_id, "Cross_VF");                    //--- Delete fixed vertical
   ObjectDelete(local_chart_id, "Measure_Line");                //--- Delete measure line
   ObjectDelete(local_chart_id, "Measure_Label");               //--- Delete measure label
}

//+------------------------------------------------------------------+
//| Delete crosshair                                                 |
//+------------------------------------------------------------------+
void DeleteCrosshair() {
   ObjectDelete(local_chart_id, "Cross_H");                     //--- Delete moving horizontal
   ObjectDelete(local_chart_id, "Cross_V");                     //--- Delete moving vertical
   DeleteMeasureObjects();                                      //--- Delete measure objects
   ChartRedraw();                                               //--- Redraw chart
}

ここでは、ToggleTheme関数を実装し、ダークモードとライトモードを切り替えます。is_dark_themeフラグの値を反転させることでテーマを変更し、その後ヘッダーとツールパネルを再描画して新しい配色を適用します。最後にチャートを更新し、変更内容を即座に表示します。次に、ToggleMinimize関数を作成し、panel_minimizedによってパネル状態を切り替えます。最小化時には、表示領域を節約するためにツールキャンバスを削除し、復元時には再作成します。その後、ヘッダーを再描画してチャートを更新します。ユーザー操作を検出するために、IsMouseOverHeader関数を定義します。この関数では、マウス位置がヘッダー領域内にあるかを確認します。ただし、テーマ切り替えボタンの配置領域(theme_offsetから始まる部分)は除外し、ドラッグ操作が可能な領域の場合のみtrueを返します。

同様に、IsMouseOverButton関数では、特定のボタン上にマウスがあるかを判定します。オフセットからボタンの左端位置を計算し、ボタンサイズおよびヘッダーの高さと比較することで、対象ボタン上にあるかを確認します。リサイズ処理については、IsMouseOverResize関数を使用します。この関数では、ツールパネルの下端、右端、または右下隅付近にマウスがあるかを判定します(最小化状態の場合は無視します)。検出された場合は、リサイズモードをBOTTOMRIGHTなどに設定し、対象領域内であればtrueを返します。さらに、GetHoveredTool関数を追加し、マウス位置の下にあるボタンを特定します。マウス座標をローカル座標へ変換し、余白を考慮した4列グリッドを走査します。ボタンの範囲内にある場合は対応するインデックスを返し、それ以外の場合は-1を返します。

ツール管理のために、ToggleTool関数ではツール種別に応じて有効化または無効化します。ツール切り替え時には、必要に応じて十字カーソル関連のオブジェクトを削除し、描画状態フラグをリセットします。また、「Click on chart to draw」のような適切な操作ガイドを設定してから、パネルを再描画します。DeactivateTool関数では、現在有効なツールを完全にリセットします。特に十字カーソル関連の要素を削除し、チャートスクロールを再度有効化します。その後、操作ガイドを更新し、パネルを再描画します。最後に、不要なオブジェクトを削除するためのクリーンアップ処理を追加します。DeleteMeasureObjects関数では、固定された十字カーソル、計測ライン、およびラベルを削除します。また、DeleteCrosshair関数では、移動する十字カーソルを削除した後、DeleteMeasureObjectsを呼び出して計測関連オブジェクトも削除し、チャートを再描画します。次に、チャートオブジェクトの処理と生成機能を実装していきます。

//+------------------------------------------------------------------+
//| Handle chart drawing for tools                                   |
//+------------------------------------------------------------------+
void HandleDrawing(int mx, int my) {
   datetime time;                                               //--- Declare time
   double price;                                                //--- Declare price
   int sw;                                                      //--- Declare subwindow
   if (!ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) return; //--- Convert XY to time/price or return

   string name = "Tool_" + TimeToString(TimeCurrent(), TIME_SECONDS); //--- Set object name
   color col = clrBlue;                                         //--- Set color
   ENUM_OBJECT obj_type = OBJ_VLINE;                            //--- Initialize object type
   datetime time1 = time;                                       //--- Set time1
   double price1 = price;                                       //--- Set price1
   datetime time2 = 0;                                          //--- Set time2
   double price2 = 0.0;                                         //--- Set price2
   bool create = true;                                          //--- Set create flag
   bool valid_obj = false;                                      //--- Set valid object flag

   string tool_name = GetToolName(active_tool);                 //--- Get tool name

   if (IsSingleClickTool(active_tool)) {                        //--- Check if single-click tool
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_HLINE:                                       //--- Handle horizontal line
            obj_type = OBJ_HLINE;                               //--- Set type
            time1 = 0;                                          //--- Set time1
            price1 = price;                                     //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_VLINE:                                       //--- Handle vertical line
            obj_type = OBJ_VLINE;                               //--- Set type
            time1 = time;                                       //--- Set time1
            price1 = 0;                                         //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_TEXT:                                        //--- Handle text
            obj_type = OBJ_TEXT;                                //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_ARROW:                                       //--- Handle arrow
            obj_type = OBJ_ARROW;                               //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
      }
   } else {                                                     //--- Handle two-click tools
      if (!drawing_first_click) {                               //--- Check if first click
         draw_time1 = time;                                     //--- Set draw time1
         draw_price1 = price;                                   //--- Set draw price1
         drawing_first_click = true;                            //--- Set first click flag
         create = false;                                        //--- Set no create
         current_instruction = "Click second point for " + tool_name + "."; //--- Set instruction
      } else {                                                  //--- Handle second click
         ChartXYToTimePrice(local_chart_id, mx, my, sw, time2, price2); //--- Get time2/price2
         drawing_first_click = false;                           //--- Reset first click
         time1 = draw_time1;                                    //--- Set time1
         price1 = draw_price1;                                  //--- Set price1
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_TRENDLINE: obj_type = OBJ_TREND; valid_obj = true; break; //--- Set trendline
         case TOOL_RECTANGLE: obj_type = OBJ_RECTANGLE; valid_obj = true; break; //--- Set rectangle
         case TOOL_FIBO: obj_type = OBJ_FIBO; valid_obj = true; break; //--- Set fibo
      }
   }

   if (create && valid_obj) {                                   //--- Check if create and valid
      if (ObjectCreate(local_chart_id, name, obj_type, sw, time1, price1, time2, price2)) { //--- Create object
         ObjectSetInteger(local_chart_id, name, OBJPROP_COLOR, col); //--- Set color
         ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
         ObjectSetInteger(local_chart_id, name, OBJPROP_WIDTH, 1); //--- Set width
         if (obj_type == OBJ_TEXT) ObjectSetString(local_chart_id, name, OBJPROP_TEXT, "Text"); //--- Set text
         if (obj_type == OBJ_ARROW) ObjectSetInteger(local_chart_id, name, OBJPROP_ARROWCODE, 233); //--- Set arrow code
         if (obj_type == OBJ_HLINE || obj_type == OBJ_VLINE) ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_DASH); //--- Set dash for lines
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTABLE, true); //--- Set selectable
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTED, true); //--- Set selected
         ChartRedraw(local_chart_id);                           //--- Redraw chart
         active_tool = TOOL_NONE;                               //--- Deactivate tool
         current_instruction = "";                              //--- Clear instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
}

チャート描画処理を実装するために、アクティブなツールとマウス位置に基づいてチャートオブジェクトの作成を管理するHandleDrawing関数を実装します。まず、ChartXYToTimePriceを使用して、マウス座標を時間と価格の値へ変換します。変換に失敗した場合は、それ以降の処理を行わずに関数を終了します。次に、現在時刻を利用して一意のオブジェクト名を生成します。また、デフォルトの色として青色を設定し、垂直ラインのオブジェクトタイプを初期値として指定します。その後、IsSingleClickToolを使用して1クリックで配置可能なツールかどうかを判定します。1クリックツールの場合は、switch文によって各ツールに応じた設定を行います。水平線ではOBJ_HLINEを設定し、時間値をリセットします。垂直線ではOBJ_VLINEを設定し、価格値をリセットします。テキストではOBJ_TEXT、矢印ではOBJ_ARROWを設定し、オブジェクト作成が有効であることを示すフラグを設定します。

2クリックで描画するツールの場合、まずdrawingfirstclickがfalseであれば1回目のクリックとして始点を保存し、操作ガイドを更新するだけで、オブジェクトは作成しません。 2回目のクリックでは、終点の座標を取得し、drawing_first_clickをリセットします。その後、ツールに応じてオブジェクトタイプを設定します。トレンドラインではOBJ_TREND、矩形ではOBJ_RECTANGLE、フィボナッチではOBJ_FIBOを使用し、作成可能なオブジェクトであることを確認します。作成フラグが有効で、かつオブジェクトが有効な場合は、ObjectCreateを使用してチャートオブジェクトを生成します。その後、色、スタイル、幅、テキスト、または必要に応じて矢印コードなどのプロパティを設定します。ラインの場合は破線設定も適用し、選択可能状態および選択状態を設定します。最後に、ChartRedrawでチャートを更新し、ツールを無効化して操作ガイドをクリアします。そして、DrawToolsPanelを呼び出してツールパネルを更新し、変更内容を反映します。次に、十字カーソルの処理を実装します。それ以外の描画ツールについては、基本的な処理は同様です。

//+------------------------------------------------------------------+
//| Update moving crosshair                                          |
//+------------------------------------------------------------------+
void UpdateMovingCrosshair(datetime time, double price) {
   string h_name = "Cross_H";                                   //--- Set horizontal name
   string v_name = "Cross_V";                                   //--- Set vertical name

   if (ObjectFind(local_chart_id, h_name) < 0) {                //--- Check if horizontal exists
      ObjectCreate(local_chart_id, h_name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, h_name, 0, 0, price);          //--- Move horizontal
   }

   if (ObjectFind(local_chart_id, v_name) < 0) {                //--- Check if vertical exists
      ObjectCreate(local_chart_id, v_name, OBJ_VLINE, 0, time, 0); //--- Create vertical line
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, v_name, 0, time, 0);           //--- Move vertical
   }
}

//+------------------------------------------------------------------+
//| Update fixed crosshair                                           |
//+------------------------------------------------------------------+
void UpdateFixedCrosshair() {
   string hf_name = "Cross_HF";                                 //--- Set fixed horizontal name
   string vf_name = "Cross_VF";                                 //--- Set fixed vertical name

   if (ObjectFind(local_chart_id, hf_name) < 0) {               //--- Check if fixed horizontal exists
      ObjectCreate(local_chart_id, hf_name, OBJ_HLINE, 0, 0, fixed_price); //--- Create fixed horizontal
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, hf_name, 0, 0, fixed_price);   //--- Move fixed horizontal
   }

   if (ObjectFind(local_chart_id, vf_name) < 0) {               //--- Check if fixed vertical exists
      ObjectCreate(local_chart_id, vf_name, OBJ_VLINE, 0, fixed_time, 0); //--- Create fixed vertical
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, vf_name, 0, fixed_time, 0);    //--- Move fixed vertical
   }
}

UpdateMovingCrosshair関数を実装し、チャート上で動的に表示される十字カーソルを管理します。まず、水平ライン用にCross_H、垂直ライン用にCross_Vというオブジェクト名を定義します。それぞれのオブジェクトが存在するかをObjectFindで確認し、存在しない場合は負の値が返されるため、その場合は作成処理を行います。オブジェクトが存在しない場合は、ObjectCreateを使用して水平ラインにはOBJ_HLINE、垂直ラインにはOBJ_VLINEを作成します。作成時には赤色の実線スタイルを設定します。一方、すでに存在する場合は、ObjectMoveを使用して現在の時間と価格位置へ移動させます。これにより、マウスカーソルの動きに合わせたリアルタイムな追従表示を実現します。次に、固定基準用の十字カーソルを管理するためにUpdateFixedCrosshair関数を作成します。この関数では、Cross_HFとCross_VFというオブジェクト名を使用し、同様にオブジェクトの存在を確認します。存在しない場合は、ObjectCreateによって固定位置用の水平ラインと垂直ラインを作成し、fixed_timeとfixed_priceの位置に配置します。このとき、緑色の実線スタイルを設定します。すでにオブジェクトが存在する場合は、その位置を更新します。これにより、計測モード中でも固定された基準点を保持でき、現在のカーソル位置との比較が可能になります。続いて、十字カーソルのクリック処理を実装します。

//+------------------------------------------------------------------+
//| Handle crosshair click                                           |
//+------------------------------------------------------------------+
void HandleCrosshairClick(datetime time, double price) {
   ulong now = GetMicrosecondCount();                           //--- Get current microseconds
   if (now - last_click_time < 500000) {                        //--- Check for double-click
      if (!measuring) {                                         //--- Check not measuring
         fixed_time = time;                                     //--- Set fixed time
         fixed_price = price;                                   //--- Set fixed price
         measuring = true;                                      //--- Set measuring flag
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         current_instruction = "Measuring. Double-click to exit."; //--- Set instruction
      } else {                                                  //--- Handle exit measuring
         measuring = false;                                     //--- Reset measuring
         DeleteMeasureObjects();                                //--- Delete objects
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      }
      DrawToolsPanel();                                         //--- Redraw tools panel
      last_click_time = 0;                                      //--- Reset click time
   } else {                                                     //--- Handle single click
      last_click_time = now;                                    //--- Update click time
   }
}

HandleCrosshairClick関数を実装し、十字カーソルモード中のクリック処理を管理します。まず、GetMicrosecondCountを使用して現在のマイクロ秒単位の時間を取得し、last_click_timeとの差分から500ミリ秒以内のダブルクリックであるかを判定します。ダブルクリックの場合は、計測状態を切り替えます。計測が開始されていない場合は、現在の時間と価格を固定ポイントとして保存し、計測モードを有効化します。また、ChartSetIntegerを使用してチャートのスクロール機能を無効化し、操作ガイドを更新します。すでに計測モードが有効な場合は、計測状態をリセットし、DeleteMeasureObjectsを呼び出して関連オブジェクトを削除します。その後、チャートスクロールを再度有効化し、デフォルトの操作ガイドへ戻します。処理の最後に、ツールパネルを再描画して状態を更新し、次回のクリック判定に備えてクリック時間をリセットします。シングルクリックの場合は、last_click_timeを現在時刻へ更新します。これにより、次のクリックが発生した際にダブルクリックとして判定できるようになります。次に、2つの十字カーソル間を結ぶ計測ラインを更新するためのロジックを実装します。

//+------------------------------------------------------------------+
//| Update measure line                                              |
//+------------------------------------------------------------------+
void UpdateMeasureLine(datetime time, double price) {
   string line_name = "Measure_Line";                           //--- Set measure line name
   if (ObjectFind(local_chart_id, line_name) < 0) {             //--- Check if line exists
      ObjectCreate(local_chart_id, line_name, OBJ_TREND, 0, fixed_time, fixed_price, time, price); //--- Create trend line
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_COLOR, clrBlue); //--- Set color
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_STYLE, STYLE_DASHDOT); //--- Set style
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_WIDTH, 1); //--- Set width
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, line_name, 0, fixed_time, fixed_price); //--- Move point 0
      ObjectMove(local_chart_id, line_name, 1, time, price);    //--- Move point 1
   }
}

//+------------------------------------------------------------------+
//| Update measure label                                             |
//+------------------------------------------------------------------+
void UpdateMeasureLabel(int mx, int my, datetime time, double price) {
   string label_name = "Measure_Label";                         //--- Set measure label name
   long period_sec = PeriodSeconds(_Period);                    //--- Get period seconds
   long virtual_fixed = fixed_time / period_sec;                //--- Compute virtual fixed
   long virtual_time = time / period_sec;                       //--- Compute virtual time
   int bars_diff = (int)MathAbs(virtual_fixed - virtual_time);  //--- Compute bars difference
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);      //--- Get point value
   long digits = SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);     //--- Get digits
   double pip_size = (digits == 3 || digits == 5) ? point * 10 : point; //--- Compute pip size
   double pips = MathAbs(price - fixed_price) / pip_size;       //--- Compute pips
   string price_diff = DoubleToString(MathAbs(price - fixed_price), (int)digits); //--- Format price diff
   string text = StringFormat("%d bars, %.1f pips, Price Diff: %s", bars_diff, pips, price_diff); //--- Format text

   if (ObjectFind(local_chart_id, label_name) < 0) {            //--- Check if label exists
      ObjectCreate(local_chart_id, label_name, OBJ_LABEL, 0, 0, 0); //--- Create label
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Set X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Set Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Set text
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_FONTSIZE, 9); //--- Set font size
      ObjectSetString(local_chart_id, label_name, OBJPROP_FONT, "Arial"); //--- Set font
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_COLOR, clrBlue); //--- Set color
   } else {                                                     //--- Handle existing
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Update X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Update Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Update text
   }
}

計測ラインを処理するために、計測モード中に2つのポイントを結ぶラインを表示するUpdateMeasureLine関数を実装します。まず、ライン用のオブジェクト名としてMeasure_Lineを定義し、ObjectFindを使用して既存オブジェクトの有無を確認します。オブジェクトが存在しない場合は、ObjectCreateを使用してOBJ_TRENDとして作成します。このラインは固定ポイントから現在の時間・価格位置まで描画され、青色の一点鎖線、幅1に設定し、延長線は無効化します。すでにオブジェクトが存在する場合は、ObjectMoveを使用して始点と終点の位置を更新します。これにより、計測中の2つのポイント間を視覚的につなぐ動的なライン表示を実現します。次に、マウス位置付近に計測値を表示するためにUpdateMeasureLabel関数を作成します。この関数では、まずPeriodSecondsから取得した時間足の秒数を使用して時間差をバー数へ変換します。時間差を秒数で割ることで仮想的なバー数を算出し、MathAbsによって絶対値を取得します。続いて、SymbolInfoDoubleから取得した銘柄のポイント値を基に、価格差をpipsへ変換します。3桁または5桁の価格精度を持つ銘柄については10倍することで調整します。また、価格差は絶対値として計算し、SymbolInfoIntegerから取得した桁数に合わせてフォーマットします。

その後、StringFormatを使用して、バー数、pips値(小数点以下1桁に丸める)、および価格差を含む表示テキストを作成します。ラベルオブジェクトが存在しない場合は、OBJ_LABELとして作成し、左上を基準にマウス位置からオフセットした場所へ配置します。フォントにはArial、サイズ9を設定し、色は青色にします。すでに存在する場合は、位置とテキスト内容を更新します。このラベル計算は、異なる銘柄の価格精度や時間足に対応しながら、時間幅と価格幅を正確に数値化するために重要です。これにより、手動計算を行うことなく、より精密な分析が可能になります。最後に、ツールパレットを実際に操作可能な状態にするため、イベントハンドラ内でチャートイベント処理を実装します。

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_KEYDOWN) {                              //--- Check key down event
      if (lparam == 27) {                                       //--- Check escape key
         DeactivateTool();                                      //--- Deactivate tool
      }
      return;                                                   //--- Return
   }
   if (id != CHARTEVENT_MOUSE_MOVE) return;                     //--- Return if not mouse move
   int mx = (int)lparam;                                        //--- Set mouse X
   int my = (int)dparam;                                        //--- Set mouse Y
   int ms = (int)sparam;                                        //--- Set mouse state

   bool prev_hh = header_hovered;                               //--- Store previous header hover
   bool prev_th = theme_hovered;                                //--- Store previous theme hover
   bool prev_mh = minimize_hovered;                             //--- Store previous minimize hover
   bool prev_ch = close_hovered;                                //--- Store previous close hover
   bool prev_rh = resize_hovered;                               //--- Store previous resize hover

   header_hovered = IsMouseOverHeader(mx, my);                  //--- Update header hover
   theme_hovered = IsMouseOverButton(mx, my, theme_x_offset);   //--- Update theme hover
   minimize_hovered = IsMouseOverButton(mx, my, minimize_x_offset); //--- Update minimize hover
   close_hovered = IsMouseOverButton(mx, my, close_x_offset);   //--- Update close hover
   resize_hovered = IsMouseOverResize(mx, my, hover_mode);      //--- Update resize hover

   string resize_tooltip = "";                                  //--- Initialize resize tooltip
   if (resize_hovered) {                                        //--- Check resize hovered
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: resize_tooltip = "Resize Bottom"; break;  //--- Set bottom tooltip
         case RIGHT: resize_tooltip = "Resize Right"; break;    //--- Set right tooltip
         case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set bottom-right tooltip
      }
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set tooltip
   } else if (!panel_minimized && hovered_tool_index >= 0) {    //--- Check tool hovered
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, buttons[hovered_tool_index].tooltip); //--- Set tool tooltip
   } else {                                                     //--- Handle no hover
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, ""); //--- Clear tooltip
   }

   if (prev_hh != header_hovered || prev_th != theme_hovered || prev_mh != minimize_hovered || prev_ch != close_hovered || prev_rh != resize_hovered) { //--- Check if hovers changed
      DrawHeader();                                             //--- Redraw header
      if (!panel_minimized) DrawToolsPanel();                   //--- Redraw tools if not minimized
      ChartRedraw();                                            //--- Redraw chart
   }

   if (!panel_minimized) {                                      //--- Check not minimized
      int ht = GetHoveredTool(mx, my);                          //--- Get hovered tool
      if (ht != hovered_tool_index) {                           //--- Check if changed
         hovered_tool_index = ht;                               //--- Update index
         DrawToolsPanel();                                      //--- Redraw tools
         ChartRedraw();                                         //--- Redraw chart
      }
   }

   if (resize_hovered || resizing) {                            //--- Check resize hovered or resizing
      hover_mouse_local_x = mx - currentCanvasX;                //--- Update local X
      hover_mouse_local_y = my - (currentCanvasY + header_height + tools_y_offset); //--- Update local Y
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 1 && prev_mouse_state == 0) {                      //--- Check mouse down
      if (header_hovered) {                                     //--- Check header hovered
         panel_dragging = true;                                 //--- Set dragging flag
         panel_drag_x = mx;                                     //--- Set drag X
         panel_drag_y = my;                                     //--- Set drag Y
         panel_start_x = currentCanvasX;                        //--- Set start X
         panel_start_y = currentCanvasY;                        //--- Set start Y
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         DrawHeader();                                          //--- Redraw header
         ChartRedraw();                                         //--- Redraw chart
      } else if (theme_hovered) {                               //--- Check theme hovered
         ToggleTheme();                                         //--- Toggle theme
      } else if (minimize_hovered) {                            //--- Check minimize hovered
         ToggleMinimize();                                      //--- Toggle minimize
      } else if (close_hovered) {                               //--- Check close hovered
         OnDeinit(0);                                           //--- Call deinit
      } else if (!panel_minimized) {                            //--- Check not minimized
         int ht = GetHoveredTool(mx, my);                       //--- Get hovered tool
         if (ht >= 0) {                                         //--- Check valid tool
            ToggleTool(buttons[ht].type);                       //--- Toggle tool
         } else if (active_tool != TOOL_NONE && active_tool != TOOL_CROSSHAIR) { //--- Check active tool
            HandleDrawing(mx, my);                              //--- Handle drawing
         } else if (IsMouseOverResize(mx, my, resize_mode)) {   //--- Check resize area
            resizing = true;                                    //--- Set resizing flag
            resize_start_x = mx;                                //--- Set start X
            resize_start_y = my;                                //--- Set start Y
            start_width = currentWidth;                         //--- Set start width
            start_height = currentHeight;                       //--- Set start height
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable scroll
            DrawToolsPanel();                                   //--- Redraw tools
            ChartRedraw();                                      //--- Redraw chart
         }
      }
   }

   if (panel_dragging && ms == 1) {                             //--- Check dragging
      int dx = mx - panel_drag_x;                               //--- Compute delta X
      int dy = my - panel_drag_y;                               //--- Compute delta Y
      currentCanvasX = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentWidth, panel_start_x + dx)); //--- Update X
      currentCanvasY = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - header_height - (panel_minimized ? 0 : currentHeight) - tools_y_offset, panel_start_y + dy)); //--- Update Y
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set header X
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, currentCanvasY); //--- Set header Y
      if (!panel_minimized) {                                   //--- Check not minimized
         ObjectSetInteger(0, canvasToolsName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set tools X
         ObjectSetInteger(0, canvasToolsName, OBJPROP_YDISTANCE, currentCanvasY + header_height + tools_y_offset); //--- Set tools Y
      }
      ChartRedraw();                                            //--- Redraw chart
   }

   if (resizing && ms == 1) {                                   //--- Check resizing
      int dx = mx - resize_start_x;                             //--- Compute delta X
      int dy = my - resize_start_y;                             //--- Compute delta Y
      int nw = MathMax(min_width, start_width + (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT ? dx : 0)); //--- Compute new width
      int nh = MathMax(min_height, start_height + (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT ? dy : 0)); //--- Compute new height
      nw = MathMin(nw, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentCanvasX); //--- Clamp width
      nh = MathMin(nh, (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - currentCanvasY - header_height - tools_y_offset); //--- Clamp height
      currentWidth = nw;                                        //--- Update width
      currentHeight = nh;                                       //--- Update height
      canvasTools.Resize(currentWidth, currentHeight);          //--- Resize tools canvas
      ObjectSetInteger(0, canvasToolsName, OBJPROP_XSIZE, currentWidth); //--- Set tools X size
      ObjectSetInteger(0, canvasToolsName, OBJPROP_YSIZE, currentHeight); //--- Set tools Y size
      canvasHeader.Resize(currentWidth, header_height);         //--- Resize header canvas
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth); //--- Set header X size
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Set header Y size
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 0 && prev_mouse_state == 1) {                      //--- Check mouse up
      panel_dragging = false;                                   //--- Reset dragging
      resizing = false;                                         //--- Reset resizing
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   datetime time = 0;                                           //--- Initialize time
   double price = 0.0;                                          //--- Initialize price
   int sw = 0;                                                  //--- Initialize subwindow
   if (ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) { //--- Convert XY to time/price
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Check crosshair active
         UpdateMovingCrosshair(time, price);                    //--- Update moving crosshair
         if (measuring) {                                       //--- Check measuring
            UpdateFixedCrosshair();                             //--- Update fixed crosshair
            UpdateMeasureLine(time, price);                     //--- Update measure line
            UpdateMeasureLabel(mx, my, time, price);            //--- Update measure label
         }
         ChartRedraw();                                         //--- Redraw chart
      }
      if (ms == 1 && prev_mouse_state == 0 && active_tool == TOOL_CROSSHAIR && 
          !header_hovered && !theme_hovered && !minimize_hovered && !close_hovered && !resize_hovered && 
          (panel_minimized || GetHoveredTool(mx, my) == -1)) {   //--- Check crosshair click conditions
         HandleCrosshairClick(time, price);                      //--- Handle click
         ChartRedraw();                                          //--- Redraw chart
      }
   }

   prev_mouse_state = ms;                                       //--- Update previous state
}

OnChartEventイベントハンドラーを使用して、パレットおよびツールに対するすべてのユーザー操作を管理します。まず、CHARTEVENT_KEYDOWNを確認して、エスケープキー(lparamが27)を検出します。検出した場合はDeactivateToolを使用して現在のツールを無効化し、その後早期リターンします。参考までに、エスケープ文字はASCIIテーブルではコード27として定義されています。以下をご覧ください。

ASCII文字27 - エスケープ

次に、イベントがCHARTEVENT_MOUSE_MOVEでない場合は処理を終了します。それ以外の場合は、パラメータからマウスのX座標、Y座標、状態を取得し、以前のホバー状態フラグを保存します。その後、IsMouseOverHeader、テーマ/最小化/閉じるボタン用のIsMouseOverButton、およびhover_modeを設定するIsMouseOverResizeなどの関数を使用して、現在のホバー状態を更新します。続いて、ObjectSetStringを使用してツールキャンバス上のツールチップを設定します。リサイズモードまたはホバー中のツールボタンがある場合は対応するツールチップを表示し、対象がない場合はクリアします。いずれかのホバー状態に変更があった場合は、ヘッダーを再描画し、最小化されていなければツールパネルも再描画した後、ChartRedraw関数を呼び出します。最小化されていない場合は、GetHoveredToolを使用してホバー中のツールインデックスを検出し、変更があれば再描画します。リサイズ状態の場合は、ローカルのホバー座標を更新し、ツールパネルを再描画します。

マウスボタン押下時(msが1、prevが0の場合)は、ヘッダー上にマウスがある場合、フラグを設定してドラッグを開始し、スクロールを無効化してヘッダーを再描画します。テーマボタンまたは最小化ボタン上にある場合は、それぞれの状態を切り替えます。閉じるボタン上にある場合はdeinitを呼び出します。ツールボタン上にある場合は、そのツールタイプを切り替えます。十字カーソル以外の描画処理についてはHandleDrawingで処理し、リサイズ領域上にある場合はフラグ、開始位置などを設定してリサイズを開始し、スクロールを無効化した後、ツールパネルを再描画します。ドラッグ中(msが1の場合)は、移動量を計算し、MathMaxおよびMathMin関数を使用して新しい位置をチャート範囲内に制限します。その後、グローバル変数を更新し、ヘッダーおよびツールパネル(表示中の場合)のオブジェクト距離を設定して、チャートを再描画します。リサイズ中(msが1の場合)は、モードに基づいて新しいサイズを計算し、範囲内に制限します。グローバル変数を更新し、Resizeでキャンバスサイズを変更した後、ObjectSetIntegerでサイズを設定します。その後、ヘッダー、ツールパネル、およびチャートを再描画します。

マウスボタン解放時(msが0、prevが1の場合)は、ドラッグ状態およびリサイズ状態のフラグをリセットし、スクロールを再有効化します。その後、ヘッダーとツールパネルを再描画し、チャートを更新します。ChartXYToTimePriceを使用してマウス座標を時間・価格座標へ変換します。十字カーソルが有効な場合は移動中の十字カーソルを更新し、計測中の場合は固定位置、計測ライン、およびラベルも更新します。条件を満たすダウンクリックがパレット外で発生した場合は、HandleCrosshairClickで処理し、再描画を実行します。最後に、次回イベント処理のためにprev_mouse_stateを現在の状態へ更新し、状態を保持した応答性の高いインタラクションを実現します。最後に、不要になったオブジェクトを削除します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   canvasHeader.Destroy();                                      //--- Destroy header canvas
   canvasTools.Destroy();                                       //--- Destroy tools canvas
   DeleteCrosshair();                                           //--- Delete crosshair
   measuring = false;                                           //--- Reset measuring flag
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);                //--- Enable mouse scroll

   int total = ObjectsTotal(local_chart_id);                    //--- Get total objects
   for (int i = total - 1; i >= 0; i--) {                       //--- Loop over objects backward
      string obj_name = ObjectName(local_chart_id, i);          //--- Get object name
      if (StringFind(obj_name, "Tool_") == 0) {                 //--- Check if our tool
         ObjectDelete(local_chart_id, obj_name);                //--- Delete object
      }
   }

   ChartRedraw();                                               //--- Redraw chart
}

OnDeinitイベントハンドラでは、Destroyメソッドを使用してヘッダーおよびツールキャンバスを破棄し、DeleteCrosshairを使用して有効になっている十字カーソルを削除します。その後、計測フラグをfalseにリセットし、ChartSetInteger関数を使用してマウススクロールを再有効化します。不要なオブジェクトが残らないようにするため、ローカルチャート上のオブジェクト総数をObjectsTotalで取得します。その後、安全に削除できるように後ろからループ処理を行い、ObjectNameを使用してオブジェクト名を取得します。そして、名前が「Tool_」で始まるオブジェクトをObjectDelete関数で削除します。最後に、ChartRedrawを呼び出してチャートを更新し、プログラム終了時のリソース解放処理を完了します。コンパイルすると、次の結果が得られます。

十字カーソルの実演

表示結果から、インタラクティブなツールパレットを作成できたことが確認でき、設定した目的を達成しています。残る作業はシステムの動作検証であり、これは次のセクションで扱います。


バックテスト

テストを実施し、以下はコンパイル後のビジュアライゼーションを単一のGraphics Interchange Format (GIF)画像としてまとめたビジュアライゼーションです。

ツールパレットGIF


結論

MQL5でチャート描画用のインタラクティブなツールパレットを構築しました。このパレットには、ドラッグおよびリサイズ可能なパネル、さらにダークモードとライトモードを切り替えるテーマ機能が搭載されています。十字カーソル、トレンドライン、ライン、矩形、フィボナッチリトレースメント、テキスト、矢印など、さまざまなツール用のボタンを組み込み、マウスイベントを管理することで、スムーズなツールの有効化とチャート上での操作を実現しました。このカスタマイズ可能なシステムにより、リアルタイムのフィードバックや操作説明を提供し、効率的なワークフローを実現する柔軟なUIを構築できます。このインタラクティブなツールパレットにより、チャートへの注釈追加や測定作業を効率化でき、高度なトレード戦略への統合に向けた準備が整いました。以降のパートでは、より整理されたツール構成を備えた、さらに詳細なパレットへ拡張していきます。どうぞお楽しみに。

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

添付されたファイル |
Tools_Palette.mq5 (62.96 KB)
MQL5入門(第41回)MQL5におけるファイル処理入門(III) MQL5入門(第41回)MQL5におけるファイル処理入門(III)
MQL5でCSVファイルを読み込み、取引データを動的配列に整理する方法を学びます。ファイル内の要素数を数える方法、すべてのデータを1つの配列に格納する方法、さらに各列を専用の配列へ分割する方法をステップごとに解説します。これにより、高度な分析やトレードパフォーマンスの可視化へ進むための基礎を構築できます。
MQL5入門(第40回):MQL5におけるファイル処理入門(II) MQL5入門(第40回):MQL5におけるファイル処理入門(II)
MQL5で、指定した期間の口座履歴を読み取り、構造化されたレコードをファイルに書き込むことで、CSV形式の取引ジャーナルを作成します。約定数の取得、チケット番号の取得、銘柄や注文タイプの判別、そしてエントリー(ロット、時間、価格、SL/TP)および決済(時間、価格、利益、結果)データの取得方法を、動的配列を使用して解説します。その結果、分析やレポート作成に適した、整理された永続的なログを作成できます。
MQL5でカスタムインジケータを作成する(第7回):セッション分析のためのハイブリッドTime Price Opportunity (TPO)マーケットプロファイル MQL5でカスタムインジケータを作成する(第7回):セッション分析のためのハイブリッドTime Price Opportunity (TPO)マーケットプロファイル
ハイブリッドTime Price Opportunity (TPO)マーケットプロファイル用のカスタムインジケータをMQL5で開発します。このインジケータは、タイムゾーン調整に対応し、イントラデイ、日足、週足、月足、固定期間など、複数のセッション時間足をサポートします。このインジケータでは、価格をグリッド化し、各セッションの高値、安値、始値、終値などのデータを追跡します。また、TPOカウントに基づいて、ポイントオブコントロール(PoC)やバリューエリア(VA)などの主要要素を計算します。さらに、TPO文字、単独プリント、VA、PoC、終値マーカーの色をカスタマイズ可能にし、チャート上にプロファイルを視覚的に描画することで、詳細なセッション分析を可能にします。
Market Memory Zonesインジケーターの自動化:価格が戻りやすい領域 Market Memory Zonesインジケーターの自動化:価格が戻りやすい領域
Market Memory Zonesをチャート上の概念から、完全なMQL5エキスパートアドバイザー(EA)へ発展させます。ディスプレイスメントゾーン、市場構造転換 (CHoCH)ゾーン、流動性スイープゾーンの検出をATRおよびローソク足構造フィルタによって自動化し、下位足での確認を適用するとともに、リスクベースのポジションサイズ計算、動的なストップロス(SL)、そして市場構造に基づくテイクプロフィット(TP)を実装します。また、検出、エントリー、トレード管理、可視化のコード構成に加え、バックテスト結果も簡潔に確認します。