チャート上に「ライン ON/OFF」のようなボタンを置いて、
クリックひとつでインジケーターの表示を切り替えたいと思ったことはないでしょうか。
あるいは、マウスカーソルを動かしただけで、
その位置の価格がラベルに表示されるインジケーターを見て、
「これはどう作るんだろう」と感じたことはないでしょうか。
この記事では、MQL5の`OnChartEvent()`とマウスイベント
(`CHARTEVENT_MOUSE_MOVE`・
`CHARTEVENT_OBJECT_CLICK`)を使い、
`OBJ_BUTTON`のボタンパネルと、
カーソル位置の価格表示を実際に動くコードで作る方法を解説します。
・OnChartEvent()がどんなタイミングで何を受け取るか
・マウスイベントを受け取るための事前準備
・OBJ_BUTTONでクリックできるボタンを作る方法
・ChartXYToTimePriceでカーソル位置の価格を取得する方法
OnChartEventとは何か
まず、チャート上のイベントを受け取る土台となる
`OnChartEvent()`から確認します。
呼ばれるタイミング(OnTick・OnCalculateとの違い)
`OnTick()`や`OnCalculate()`は、
新しいティックやバーが来たときに呼ばれます。
これに対して`OnChartEvent()`は、
キー入力・マウスのクリックや移動・オブジェクトの作成や削除など、
チャート上の「出来事」が起きるたびに呼ばれます。
値動きとは関係のないタイミングで発火する点が、最大の違いです。
4つの引数(id・lparam・dparam・sparam)の役割
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam);
`id`には、何が起きたかを表す`CHARTEVENT_`から始まる定数が入ります。
`lparam`・`dparam`・`sparam`に入る内容は、
`id`の種類によって変わります。
たとえば`CHARTEVENT_OBJECT_CLICK`では、
`sparam`にクリックされたオブジェクトの名前が入り、
`lparam`・`dparam`はクリック座標になります。
マウスイベントを受け取るための事前準備
マウスの移動イベント(`CHARTEVENT_MOUSE_MOVE`)は、
初期状態では発火しません。
`OnInit()`の中で、
次の1行を実行して明示的に有効化する必要があります。
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
・クリックイベント(CHARTEVENT_CLICK)自体は既定で発火します
・座標を伴うマウス移動の検知には別設定が必要です
・有効化を忘れるとボタンは反応するのにカーソル追従だけ動きません
OBJ_BUTTONでクリックできるボタンを作る
次に、実際にクリックできるボタンをチャート上に配置します。
ObjectCreateと位置・サイズの指定
`OBJ_BUTTON`はチャート座標ではなく、
画面の左上等を基準にしたスクリーン座標で配置するオブジェクトです。
ObjectCreate(0, "btnToggleLine", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, "btnToggleLine", OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, "btnToggleLine", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, "btnToggleLine", OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, "btnToggleLine", OBJPROP_XSIZE, 140); ObjectSetInteger(0, "btnToggleLine", OBJPROP_YSIZE, 24); ObjectSetString(0, "btnToggleLine", OBJPROP_TEXT, "ライン ON/OFF"); ObjectSetInteger(0, "btnToggleLine", OBJPROP_BGCOLOR, clrSteelBlue); ObjectSetInteger(0, "btnToggleLine", OBJPROP_COLOR, clrWhite);
`ObjectCreate`の`time1`・`price1`には、
ダミーで`0`を渡しています。
`OBJ_BUTTON`はスクリーン座標専用のため、この2つの値は使われません。
OBJPROP_STATEでボタンの押下状態を扱う
ボタンが押されているかどうかは、
`OBJPROP_STATE`(bool型)で取得できます。
bool pressed = ObjectGetInteger(0, "btnToggleLine", OBJPROP_STATE);
MQL5のボタンは、クリックすると押し込まれたまま表示が固定されます。
このままだと「押しっぱなし」に見えてしまうため、
判定処理のあとに自分で`false`へ戻す必要があります。
CHARTEVENT_OBJECT_CLICKでボタンのクリックを検知する
ボタンを配置しただけでは何も起きません。
クリックを検知する処理を書きます。
sparamにクリックされたオブジェクト名が入る
`CHARTEVENT_OBJECT_CLICK`が発生すると、
`sparam`にクリックされたオブジェクトの名前がそのまま渡されます。
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(sparam == "btnToggleLine")
{
lineVisible = !lineVisible;
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_COLOR,
lineVisible ? clrOrangeRed : clrNONE);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_STATE, false);
ChartRedraw(0);
}
}
`sparam`をボタン名と比較するだけなので、
複数のボタンを同じ`OnChartEvent()`の中で分岐して扱えます。
押しっぱなし状態を防ぐリセット処理
上のコードでは、ボタン判定の直後に
`OBJPROP_STATE`を`false`へ戻すことで、
押しっぱなし状態を解除しています。
・このリセットを忘れると見た目が押しっぱなしになります
・ただしクリック判定自体は毎回きちんと実行されています
・「2回押さないと切り替わらない」という誤解はしないよう注意してください
マウス座標から価格を取得する(ChartXYToTimePrice)
ボタンに加えて、カーソルを動かすだけで価格が分かる仕組みも作ります。
CHARTEVENT_MOUSE_MOVEで座標を受け取る
if(id == CHARTEVENT_MOUSE_MOVE)
{
int x = (int)lparam;
int y = (int)dparam;
...
}
`CHARTEVENT_MOUSE_MOVE`では、`lparam`にX座標、
`dparam`にY座標がピクセル単位で渡されます
(他のイベントとは型の使われ方が異なる点に注意)。
カーソル位置の価格をラベルに表示する
bool ChartXYToTimePrice(long chart_id, int x, int y,
int &sub_window, datetime &time, double &price);
座標をそのまま使うのではなく、`ChartXYToTimePrice`に渡すことで、
その座標が指す「時刻」と「価格」に変換できます。
int win; datetime t; double p;
if(ChartXYToTimePrice(0, x, y, win, t, p))
{
ObjectSetString(0, "lblCursorPrice", OBJPROP_TEXT,
"価格: " + DoubleToString(p, _Digits));
}
実際に動くコード全体
ボタンで水平線の表示・非表示を切り替えつつ、
マウス移動でカーソル位置の価格を表示するカスタムインジケーターです。
//+------------------------------------------------------------------+
//| MouseButtonPanel.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
bool lineVisible = true;
int OnInit()
{
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
ObjectCreate(0, "btnToggleLine", OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_XSIZE, 140);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_YSIZE, 24);
ObjectSetString(0, "btnToggleLine", OBJPROP_TEXT, "ライン ON/OFF");
ObjectSetInteger(0, "btnToggleLine", OBJPROP_BGCOLOR, clrSteelBlue);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_COLOR, clrWhite);
ObjectCreate(0, "cursorPriceLine", OBJ_HLINE, 0, 0, SymbolInfoDouble(_Symbol, SYMBOL_BID));
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_COLOR, clrOrangeRed);
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_STYLE, STYLE_DASH);
ObjectCreate(0, "lblCursorPrice", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "lblCursorPrice", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "lblCursorPrice", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "lblCursorPrice", OBJPROP_YDISTANCE, 54);
ObjectSetString(0, "lblCursorPrice", OBJPROP_TEXT, "価格: -");
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectDelete(0, "btnToggleLine");
ObjectDelete(0, "cursorPriceLine");
ObjectDelete(0, "lblCursorPrice");
}
int OnCalculate(const int rates_total, const int prev_calculated,
const datetime &time[], const double &open[],
const double &high[], const double &low[],
const double &close[], const long &tick_volume[],
const long &volume[], const int &spread[])
{
return(rates_total);
}
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK && sparam == "btnToggleLine")
{
lineVisible = !lineVisible;
ObjectSetInteger(0, "cursorPriceLine", OBJPROP_COLOR,
lineVisible ? clrOrangeRed : clrNONE);
ObjectSetInteger(0, "btnToggleLine", OBJPROP_STATE, false);
ChartRedraw(0);
return;
}
if(id == CHARTEVENT_MOUSE_MOVE)
{
int x = (int)lparam;
int y = (int)dparam;
int win;
datetime t;
double p;
if(ChartXYToTimePrice(0, x, y, win, t, p))
{
ObjectSetString(0, "lblCursorPrice", OBJPROP_TEXT,
"価格: " + DoubleToString(p, _Digits));
if(lineVisible)
ObjectSetDouble(0, "cursorPriceLine", OBJPROP_PRICE, p);
}
}
}
MetaEditorでのコンパイル・実行手順
- MetaEditorで新規カスタムインジケーター「MouseButtonPanel」を作成し、上のコードを貼り付けてコンパイルする
- MT5のナビゲーターからチャートへドラッグ&ドロップして適用する
- チャート左上に「ライン ON/OFF」ボタンと価格ラベルが表示されることを確認する
- チャート上でマウスを動かし、価格ラベルの数値が変化することを確認する
- ボタンをクリックし、オレンジの点線(カーソル追従ライン)が消えたり表示されたりすることを確認する
よくあるエラーと注意点
ここまでのコードを自分で改造するときに、
つまずきやすいポイントをまとめます。
ボタンを押してもイベントが反応しない
`sparam`の文字列比較は完全一致です。
ボタン名を後から変更したのに、
`OnChartEvent()`側の比較文字列を直していないと、
一生反応しません。
オブジェクト名は定数化して1箇所にまとめておくと事故を防げます。
CHARTEVENT_MOUSE_MOVEを有効にすると重くなる
・マウス移動イベントはカーソルを動かすたびに大量に発火します
・その中でヒストリカルデータの再取得や重い計算をすると動作が重くなります
・座標変換とラベル更新程度の軽い処理に留めてください
MQL4との違い
`OnChartEvent()`自体はMQL4にも存在し、書き方もほぼ同じです。
ただし`ChartXYToTimePrice`はMQL5の関数であり、
MQL4には存在しません。
MQL4で同等の処理をする場合は、`WindowPriceOnDropped()`等、
別の一連の関数を組み合わせる必要があります。
よくある質問
OnChartEventとマウスイベントについて、よく検索される疑問に回答します。
ボタンは何個でも置けますか?
はい、オブジェクト名を変えれば何個でも配置できます。
`OnChartEvent()`内の`sparam`の比較を、
ボタンの数だけ`if`文やswitch文で分岐させれば、
それぞれ別の処理を割り当てられます。
EA(エキスパートアドバイザー)でも同じコードは使えますか?
使えます。
`OnChartEvent()`はインジケーター・EAどちらでも、
実装できるイベント関数です。
EAにボタンを付ければ、「手動決済ボタン」のような操作パネルも同じ仕組みで作れます。
ボタンの見た目(色・サイズ)は自由に変えられますか?
`OBJPROP_BGCOLOR`・`OBJPROP_COLOR`・
`OBJPROP_XSIZE`・`OBJPROP_YSIZE`など、
この記事で使ったプロパティの値を変えるだけで、
色やサイズは自由に調整できます。
【MQL5】OnChartEventとマウスイベントでクリックできるボタンパネルを作る方法:まとめ
`OnChartEvent()`とマウスイベントを組み合わせれば、
チャート上にクリックできるボタンや、カーソルに追従する価格表示を自分で作れます。
ポイントは、`CHART_EVENT_MOUSE_MOVE`の事前有効化、
`CHARTEVENT_OBJECT_CLICK`でのボタン判定とステート解除、
`ChartXYToTimePrice`での座標→価格変換の3点です。
コメント