はじめに
MT5でパネル型EAを作る際、「ドラッグして移動できるようにしたい」というのはよくある要件です。
しかし、公式ドキュメント通りに実装しても動かないことが多く、非常に苦戦します。
この記事では、実際に動作する実装方法と、なぜ公式の方法では動かないのかを解説します。
❌ 動かない実装例(公式ドキュメント的なアプローチ)
試行1: OBJPROP_SELECTABLEを使う
// ドラッグバーを作成
ObjectCreate(0, "DragBar", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, "DragBar", OBJPROP_SELECTABLE, true); // 選択可能にする
// イベントハンドラ
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_DRAG && sparam == "DragBar")
{
// ここに移動処理を書く
}
}
結果: CHARTEVENT_OBJECT_DRAG が発火しない。
試行2: OBJPROP_SELECTEDも追加
ObjectSetInteger(0, "DragBar", OBJPROP_SELECTABLE, true);
ObjectSetInteger(0, "DragBar", OBJPROP_SELECTED, true);
結果: それでも CHARTEVENT_OBJECT_DRAG が発火しない。
試行3: DRAG_MODE_MOVEを設定(公式ドキュメントの記載)
ObjectSetInteger(0, "DragBar", OBJPROP_SELECTABLE, true);
ObjectSetInteger(0, "DragBar", OBJPROP_SELECTED, true);
ObjectSetInteger(0, "DragBar", OBJPROP_DRAG_MODE, DRAG_MODE_MOVE);
結果:
'DRAG_MODE_MOVE' - undeclared identifier
🤔 なぜ動かないのか?
問題1: 環境依存
DRAG_MODE_MOVE 定数は、MT5のビルドバージョンによって定義されていないことがあります。
- 新しいビルド: 定義されている
- 古いビルド: 未定義でコンパイルエラー
問題2: OBJPROPの仕様
OBJPROP_DRAG_MODE は比較的新しい機能で、すべての環境で確実に動作するわけではありません。
問題3: ドキュメントと実装の乖離
MQL5の公式ドキュメントには記載があっても、実際の実装が追いついていないケースがあります。
✅ 確実に動く実装方法(マウスMOVEイベント)
環境依存を避けるため、CHARTEVENT_MOUSE_MOVE を使って自力で実装します。
完全なコード例
// グローバル変数
int panelX = 10;
int panelY = 30;
int panelWidth = 220;
bool isDragging = false;
int dragStartX = 0;
int dragStartY = 0;
//+------------------------------------------------------------------+
//| 初期化 |
//+------------------------------------------------------------------+
int OnInit()
{
// マウスイベントを有効化(重要!)
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
CreatePanel();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| パネル作成 |
//+------------------------------------------------------------------+
void CreatePanel()
{
// 背景
ObjectCreate(0, "Panel_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Panel_BG", OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, "Panel_BG", OBJPROP_YDISTANCE, panelY);
ObjectSetInteger(0, "Panel_BG", OBJPROP_XSIZE, panelWidth);
ObjectSetInteger(0, "Panel_BG", OBJPROP_YSIZE, 200);
ObjectSetInteger(0, "Panel_BG", OBJPROP_BGCOLOR, clrWhite);
// タイトルバー(ドラッグ用)
ObjectCreate(0, "Panel_Title", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Panel_Title", OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, "Panel_Title", OBJPROP_YDISTANCE, panelY);
ObjectSetInteger(0, "Panel_Title", OBJPROP_XSIZE, panelWidth);
ObjectSetInteger(0, "Panel_Title", OBJPROP_YSIZE, 20);
ObjectSetInteger(0, "Panel_Title", OBJPROP_BGCOLOR, clrDarkGray);
}
//+------------------------------------------------------------------+
//| チャートイベント |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
// マウス移動イベント
if(id == CHARTEVENT_MOUSE_MOVE)
{
int mouseX = (int)lparam;
int mouseY = (int)dparam;
// sparamからマウスボタンの状態を取得
long mouseFlags = (long)StringToInteger(sparam);
bool leftButtonPressed = (mouseFlags & 1) != 0;
// ドラッグ中の処理
if(isDragging && leftButtonPressed)
{
// 移動量を計算
int deltaX = mouseX - dragStartX;
int deltaY = mouseY - dragStartY;
// パネルの全オブジェクトを移動
MovePanel(deltaX, deltaY);
// 次回の計算用に現在位置を保存
dragStartX = mouseX;
dragStartY = mouseY;
}
// ドラッグ開始判定
else if(!isDragging && leftButtonPressed)
{
// タイトルバーの範囲内かチェック
if(mouseX >= panelX && mouseX <= panelX + panelWidth &&
mouseY >= panelY && mouseY <= panelY + 20)
{
isDragging = true;
dragStartX = mouseX;
dragStartY = mouseY;
}
}
// マウスボタンが離された
if(!leftButtonPressed)
{
isDragging = false;
}
}
}
//+------------------------------------------------------------------+
//| パネル移動 |
//+------------------------------------------------------------------+
void MovePanel(int deltaX, int deltaY)
{
// パネル位置を更新
panelX += deltaX;
panelY += deltaY;
// 全オブジェクトを移動
for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
{
string name = ObjectName(0, i);
if(StringFind(name, "Panel_") == 0) // "Panel_"で始まるオブジェクト
{
int x = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE);
int y = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x + deltaX);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y + deltaY);
}
}
ChartRedraw();
}
🔑 実装のポイント
1. CHART_EVENT_MOUSE_MOVEを有効化
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
これを忘れると CHARTEVENT_MOUSE_MOVE が発火しません。
2. マウスボタンの状態を取得
long mouseFlags = (long)StringToInteger(sparam);
bool leftButtonPressed = (mouseFlags & 1) != 0;
sparam には文字列形式でマウスの状態が渡されます。
ビット演算で左ボタンの状態を判定します。
3. ドラッグ開始判定
if(mouseX >= panelX && mouseX <= panelX + panelWidth &&
mouseY >= panelY && mouseY <= panelY + 20)
{
isDragging = true;
dragStartX = mouseX;
dragStartY = mouseY;
}
タイトルバーの範囲内でクリックされたか判定します。
4. 移動量の計算
int deltaX = mouseX - dragStartX;
int deltaY = mouseY - dragStartY;
前回のマウス位置との差分を計算して移動します。
5. 位置の更新
dragStartX = mouseX;
dragStartY = mouseY;
次回の計算用に現在位置を保存します(これを忘れると動きがおかしくなる)。
📊 デバッグ方法
動作確認のために、デバッグ出力を追加すると良いでしょう。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_MOUSE_MOVE)
{
int mouseX = (int)lparam;
int mouseY = (int)dparam;
long mouseFlags = (long)StringToInteger(sparam);
bool leftButtonPressed = (mouseFlags & 1) != 0;
// デバッグ出力
Print("Mouse: X=", mouseX, " Y=", mouseY, " Button=", leftButtonPressed, " Dragging=", isDragging);
// ... 以下、通常の処理
}
}
🎯 応用: 画面外に出ないようにする
パネルが画面外に移動しないように制限を追加できます。
void MovePanel(int deltaX, int deltaY)
{
// 新しい位置を計算
int newX = panelX + deltaX;
int newY = panelY + deltaY;
// チャートのサイズを取得
int chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
int chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
// 画面外に出ないように制限
if(newX < 0) newX = 0;
if(newY < 0) newY = 0;
if(newX + panelWidth > chartWidth) newX = chartWidth - panelWidth;
if(newY + panelHeight > chartHeight) newY = chartHeight - panelHeight;
// 実際の移動量を再計算
deltaX = newX - panelX;
deltaY = newY - panelY;
// 移動量が0なら何もしない
if(deltaX == 0 && deltaY == 0) return;
// パネル位置を更新
panelX = newX;
panelY = newY;
// 全オブジェクトを移動
for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
{
string name = ObjectName(0, i);
if(StringFind(name, "Panel_") == 0)
{
int x = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE);
int y = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x + deltaX);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y + deltaY);
}
}
ChartRedraw();
}
⚠️ よくあるミス
ミス1: ChartRedraw()を忘れる
// NG: 移動したのに画面が更新されない
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, newX);
// OK: ChartRedraw()を呼ぶ
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, newX);
ChartRedraw();
ミス2: dragStartXを更新し忘れる
// NG: 移動量が累積されておかしくなる
int deltaX = mouseX - dragStartX;
MovePanel(deltaX, deltaY);
// dragStartXの更新を忘れている!
// OK: 毎回更新する
int deltaX = mouseX - dragStartX;
MovePanel(deltaX, deltaY);
dragStartX = mouseX;
dragStartY = mouseY;
ミス3: CHART_EVENT_MOUSE_MOVEを有効化し忘れる
// NG: これを忘れるとマウスイベントが来ない
int OnInit()
{
CreatePanel();
return(INIT_SUCCEEDED);
}
// OK: 必ず有効化する
int OnInit()
{
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); // これ!
CreatePanel();
return(INIT_SUCCEEDED);
}
📝 まとめ
OBJPROP_SELECTABLE+CHARTEVENT_OBJECT_DRAGは環境依存が強く、推奨しないDRAG_MODE_MOVE定数が未定義のビルドが存在する- 確実に動く方法:
CHARTEVENT_MOUSE_MOVEを使って自力実装 ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true)を忘れずに- マウスボタンの状態は
sparamから取得 dragStartX/Yの更新を忘れない
