島根県安来市のフリーランスエンジニア_プログラマー画像1
MT5でパネルをドラッグ移動させる方法 – OBJPROP_SELECTABLEだけでは動かない罠 – Eatransform

MT5でパネルをドラッグ移動させる方法 – OBJPROP_SELECTABLEだけでは動かない罠

はじめに

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 の更新を忘れない

マーダーミステリー制作が10倍楽になるツールを公開

新サイト「中国バズ」を公開しました