Qtプログラミング:QGraphicsView::dragMoveEventを使ったドラッグ&ドロップの具体例

2025-05-27

この関数の役割と動作

QGraphicsViewQGraphicsScene の内容を表示するためのウィジェットです。ユーザーが何かをドラッグしている最中に、そのドラッグ操作が QGraphicsView のビューポート内を移動するたびに、この dragMoveEvent が呼び出されます。

具体的には、以下の目的でこの関数をオーバーライド(再実装)します。

    • ユーザーがドラッグしているデータ(MIMEデータ)を、現在のビューポートの位置にドロップできるかどうかを判断します。
    • ドロップが可能であれば、イベントの acceptProposedAction() を呼び出して、ドロップを受け入れることをQtに伝えます。これにより、通常、マウスカーソルの形状が変更され、ドロップが許可されていることを視覚的にユーザーに示します(例:ドロップ可能な場所では「+」マークが表示されるなど)。
    • ドロップが不可能な場合は、イベントの ignore() を呼び出し、ドロップを受け入れないことを示します。
  1. ドラッグ中の視覚的な変更

    • ドラッグ中のアイテムが特定の領域に入ったときに、その領域のハイライト表示を変更したり、ドロップされた場合のプレビューを表示したりするなど、ドラッグ中の視覚的なフィードバックを提供するために使用されます。

QDragMoveEvent *event について

dragMoveEvent 関数には、QDragMoveEvent 型のポインタ event が引数として渡されます。この event オブジェクトには、ドラッグ操作に関する以下の情報が含まれています。

  • acceptProposedAction() / ignore()
    ドロップイベントを受け入れるか拒否するかをQtに伝えます。
  • proposedAction()
    ドラッグ操作の現在の提案されているアクション(コピー、移動、リンクなど)を取得します。
  • mimeData()
    ドラッグされているデータ(ファイルパス、テキスト、カスタムデータなど)を含む QMimeData オブジェクトを取得します。このデータの内容に基づいて、ドロップを受け入れるかどうかを決定します。
  • pos() または scenePos()
    現在のドラッグ位置をビューポート座標またはシーン座標で取得します。これにより、マウスカーソルの位置に基づいてドロップの判断を行うことができます。

典型的な使用例

QGraphicsView を継承したカスタムクラスで dragMoveEvent をオーバーライドする一般的なコードの構造は次のようになります。

#include <QGraphicsView>
#include <QDragMoveEvent>
#include <QMimeData>
#include <QDebug> // デバッグ出力用

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT // Qtのメタオブジェクトシステムを使用する場合に必要

public:
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setAcceptDrops(true); // ビューでドロップを受け入れるように設定
    }

protected:
    void dragMoveEvent(QDragMoveEvent *event) override
    {
        // ドラッグされているデータが特定の形式を持っているかチェック
        if (event->mimeData()->hasFormat("application/x-my-custom-data")) {
            // ドロップを受け入れる
            event->acceptProposedAction();
            qDebug() << "ドラッグ移動中:カスタムデータを受け入れます";

            // 必要に応じて、ドラッグ中の視覚的なフィードバックを更新
            // 例: ドロップ位置に仮の表示を行うなど
        } else {
            // 他のMIMEタイプは無視するか、親クラスの処理に任せる
            event->ignore();
            qDebug() << "ドラッグ移動中:カスタムデータではありません。無視します";
            // QGraphicsView::dragMoveEvent(event); // 親クラスのデフォルト動作が必要な場合
        }
    }
};


dragMoveEvent が全く呼び出されない

原因

  • イベントフィルターや他のイベントハンドラがイベントを消費している
    アプリケーション内で他のイベントフィルターやイベントハンドラが dragMoveEvent よりも先にイベントを処理し、event->accept()event->ignore() を呼び出している場合、QGraphicsViewdragMoveEvent に到達しないことがあります。
  • ドラッグ操作が開始されていない
    ドラッグ&ドロップは、通常 QDrag オブジェクトを作成し、start() メソッドを呼び出すことで開始されます。この開始処理が適切に行われていないと、ドラッグイベントは発生しません。
  • setAcceptDrops(true) が設定されていない
    QGraphicsView またはドロップを受け入れたいウィジェットで setAcceptDrops(true) を呼び出す必要があります。これは、ウィジェットがドロップイベントを受け入れる準備ができていることをQtに伝えます。

トラブルシューティング

  • event->ignore() を呼び出している他のイベントハンドラがないか確認します。
  • デバッガーを使って、dragMoveEvent の冒頭にブレークポイントを設定し、呼び出されているかどうかを確認します。
  • ドラッグを開始する側のコード(例: mousePressEvent から QDrag を開始する部分)が正しく実装されていることを確認してください。
  • QGraphicsView のコンストラクタで setAcceptDrops(true); を呼び出していることを確認してください。

ドロップカーソルが期待通りに変化しない(常に「禁止」マークなど)

原因

  • dropEvent が期待通りに動作しない
    dragMoveEvent はドロップの可否を通知するだけで、実際のドロップ処理は dropEvent で行われます。dropEvent のロジックに問題がある場合、dragMoveEvent の挙動が意図通りに見えないことがあります。
  • QMimeData のフォーマットが一致しない
    ドラッグされているデータ(event->mimeData())が、受け入れ側のウィジェットが処理できるMIMEフォーマットと一致しない場合。
  • event->acceptProposedAction() を呼び出していない
    dragMoveEvent 内で、ドロップを受け入れる条件が満たされたときに event->acceptProposedAction() を呼び出していない場合、Qtはドロップを受け入れられないと判断し、禁止マークを表示します。

トラブルシューティング

  • dragMoveEvent が正しく acceptProposedAction() を呼び出しているにもかかわらずカーソルが変わらない場合、dropEvent も適切に実装されているか確認してください。
  • event->mimeData()->hasFormat("your/custom/mime-type") のように、ドラッグされているデータのMIMEタイプを正確にチェックしているか確認してください。タイプミスや不一致があると、ドロップが拒否されます。
  • dragMoveEvent 内で、ドロップ可能な条件が満たされたら 必ず event->acceptProposedAction(); を呼び出すようにしてください。

ドラッグ中の視覚的なフィードバックが更新されない/ちらつく

原因

  • 重い処理
    dragMoveEvent の中で複雑な計算やI/O処理を行っている場合、GUIの応答性が低下し、ちらつきが発生することがあります。dragMoveEvent は頻繁に呼び出されるため、軽量な処理に限定すべきです。
  • 再描画のトリガー不足
    dragMoveEvent 内で、ドラッグ中の位置に基づいて QGraphicsSceneQGraphicsView の表示を更新したい場合、適切なタイミングで再描画をトリガーする必要があります(例: viewport()->update()scene()->update())。

トラブルシューティング

  • プロファイラを使用して、dragMoveEvent 内でパフォーマンスのボトルネックとなっている処理がないか特定し、最適化してください。
  • 複雑な視覚的フィードバックが必要な場合は、QGraphicsScene に仮のアイテムを追加したり、QGraphicsItemprepareGeometryChange() を利用したりするなど、Graphics Viewフレームワークの最適化機能を利用することを検討してください。
  • dragMoveEvent の中で、描画を更新する必要がある部分に対して update() を呼び出していることを確認してください。

QGraphicsItem へのドロップが機能しない

原因

  • アイテムのヒットテストの問題
    マウスカーソルが実際にドロップを受け入れたい QGraphicsItem の上に乗っていることを、QGraphicsView::mapToScene()QGraphicsScene::itemAt() などを使って正確に判断する必要があります。
  • QGraphicsScene がイベントを消費している
    QGraphicsScene をオーバーライドして dragMoveEvent を実装している場合、そのイベントが QGraphicsItem に到達しない可能性があります。
  • QGraphicsView がイベントを消費している
    デフォルトでは、QGraphicsView はビューポート全体に対するドラッグイベントを処理します。特定の QGraphicsItem がドロップを受け入れるようにするには、QGraphicsItem 自体にも ItemIsSelectableItemIsMovable といったフラグに加え、ItemAcceptsDrops フラグを設定し、dragMoveEvent (QGraphicsItem::dragMoveEvent) をオーバーライドする必要があります。

トラブルシューティング

  • QGraphicsScene をオーバーライドしている場合、dragMoveEvent の中で QGraphicsScene::dragMoveEvent(event); を呼び出して、イベントがアイテムに伝播されるようにする必要があります。
  • QGraphicsViewdragMoveEvent 内で、event->scenePos() を使用してマウスカーソルのシーン座標を取得し、scene()->itemAt(event->scenePos(), transform()) などを使って、その位置にある QGraphicsItem を特定します。特定したアイテムにイベントを転送するか、そのアイテムがドロップを受け入れられるかどうかを判断するロジックを追加します。
  • QGraphicsItem のカスタムクラスで dragMoveEvent をオーバーライドし、そこで event->acceptProposedAction() を呼び出すようにします。
  • ドロップを受け入れたい QGraphicsItem のコンストラクタで setFlags(QGraphicsItem::ItemAcceptsDrops); を設定していることを確認してください。

ドラッグ&ドロップの座標変換の問題

原因

  • ビューポート座標とシーン座標の混同
    QGraphicsView のイベントはビューポート座標(ウィジェットのピクセル座標)で提供されますが、QGraphicsSceneQGraphicsItem の位置はシーン座標で扱われます。これらの変換を正しく行わないと、ドロップ位置がずれるなどの問題が発生します。

トラブルシューティング

  • 常に適切な座標変換関数(mapToScene, mapFromScene, mapToItem, mapFromItem など)を使用していることを確認してください。
  • QGraphicsScene のイベントハンドラ(例: QGraphicsSceneDragDropEvent)では、scenePos() がシーン座標を返します。
  • QDragMoveEventpos() はビューポート座標です。これをシーン座標に変換するには mapToScene(event->pos()) を使用します。

macOS でのドラッグ&ドロップの問題

原因

  • macOSでは、Qtのドラッグ&ドロップの挙動が他のOSと若干異なる場合があります。特に、特定のMIMEタイプや、ファイルパスの扱いにおいて問題が発生することが報告されています。

トラブルシューティング

  • ファイルパスの場合、event->mimeData()->urls() を使用して QUrl のリストを取得し、toLocalFile() でローカルファイルパスに変換します。
  • macOS固有のMIMEタイプ(例: text/uri-list ではなく application/x-vnd.qt.urltext/plain としてURLが渡される場合がある)を考慮する必要があるかもしれません。
  • setAcceptDrops(true) を忘れない
    これが最も基本的な設定であり、多くのドラッグ&ドロップ問題の原因となります。
  • 最小限の再現コードの作成
    問題が発生した場合、その問題を再現する最小限のコードを作成することで、原因を特定しやすくなります。
  • デバッグ出力の活用
    qDebug() を使って、dragMoveEvent が呼び出されているか、event->mimeData() の内容が期待通りか、acceptProposedAction() が呼び出されているかなどをログに出力し、動作を追跡します。


例1:ファイルパスのドラッグ&ドロップ(QGraphicsView全体で受け入れる)

この例では、ファイルエクスプローラーなどから QGraphicsView にファイルをドラッグ&ドロップするシナリオを想定しています。dragMoveEvent では、ドラッグされているのがファイルであるかどうかを確認し、適切なカーソルフィードバックを提供します。

MyGraphicsView.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QGraphicsScene>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QUrl>
#include <QDebug> // デバッグ出力用

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT

public:
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);

protected:
    // QGraphicsView::dragEnterEvent() もオーバーライドする必要があります
    // これは、ドラッグ操作がビューに入ったときに一度だけ呼び出されます
    void dragEnterEvent(QDragEnterEvent *event) override;

    // ドラッグ操作がビュー内で移動するたびに呼び出されます
    void dragMoveEvent(QDragMoveEvent *event) override;

    // ドロップが実行されたときに呼び出されます
    void dropEvent(QDropEvent *event) override;
};

#endif // MYGRAPHICSVIEW_H

MyGraphicsView.cpp

#include "MyGraphicsView.h"

MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    // このビューがドロップイベントを受け入れるように設定
    setAcceptDrops(true);
}

void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
{
    // ドラッグされているデータがURLリスト(通常はファイルパス)を持っているかチェック
    if (event->mimeData()->hasUrls()) {
        // ドロップを受け入れることをQtに通知
        event->acceptProposedAction();
        qDebug() << "Drag entered: URLs detected.";
    } else {
        // それ以外のデータは無視
        event->ignore();
        qDebug() << "Drag entered: No URLs. Ignored.";
    }
}

void MyGraphicsView::dragMoveEvent(QDragMoveEvent *event)
{
    // dragEnterEvent と同様に、ドラッグされているデータがファイルパスであるかを確認
    if (event->mimeData()->hasUrls()) {
        // ドロップを受け入れることをQtに通知
        // これにより、マウスカーソルが適切な「ドロップ可能」な形状に変わります
        event->acceptProposedAction();
        qDebug() << "Drag moving: URLs detected. Accepting proposed action.";

        // オプション:ドラッグ中の視覚的なフィードバック
        // 例えば、マウスカーソルの位置に一時的なハイライト表示を描画するなど
        // ここでは単純にデバッグ出力に留めます
        QPointF scenePos = mapToScene(event->pos());
        qDebug() << "Drag moving at scene position:" << scenePos;

    } else {
        // ドロップを受け入れないことをQtに通知
        event->ignore();
        qDebug() << "Drag moving: No URLs. Ignoring.";
    }
}

void MyGraphicsView::dropEvent(QDropEvent *event)
{
    // dragMoveEvent で受け入れた条件が満たされた場合のみ、このイベントが呼び出されます
    if (event->mimeData()->hasUrls()) {
        QList<QUrl> urls = event->mimeData()->urls();
        for (const QUrl &url : urls) {
            // ファイルパスを取得
            QString filePath = url.toLocalFile();
            qDebug() << "Dropped file:" << filePath;
            // ここで、ドロップされたファイルパスに対する処理を実装します
            // 例: 画像ファイルをロードしてシーンに追加するなど
        }
        event->acceptProposedAction(); // ドロップ処理が完了したことを通知
    } else {
        event->ignore();
    }
}

main.cpp

#include <QApplication>
#include "MyGraphicsView.h"
#include <QGraphicsScene>
#include <QMainWindow>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene *scene = new QGraphicsScene();
    MyGraphicsView *view = new MyGraphicsView(scene);

    // シーンに何かアイテムを追加(例:背景として)
    scene->addRect(0, 0, 800, 600, QPen(Qt::black), QBrush(Qt::lightGray));
    scene->setSceneRect(0, 0, 800, 600); // シーンの有効範囲を設定

    QMainWindow window;
    window.setCentralWidget(view);
    window.setWindowTitle("Drag & Drop Example (QGraphicsView)");
    window.resize(800, 600);
    window.show();

    return a.exec();
}

例2:特定のQGraphicsItemの上にドラッグされた場合のフィードバック

この例では、QGraphicsItem を継承したカスタムアイテムを作成し、そのアイテムの上にドラッグされた場合に視覚的なフィードバック(例:枠線の色を変える)を提供します。

まず、カスタムの QGraphicsRectItem を作成し、ドラッグ&ドロップイベントを受け入れられるようにします。

DraggableRectItem.h

#ifndef DRAGGABLERECTITEM_H
#define DRAGGABLERECTITEM_H

#include <QGraphicsRectItem>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QDebug>

class DraggableRectItem : public QGraphicsRectItem
{
public:
    DraggableRectItem(const QRectF &rect, QGraphicsItem *parent = nullptr);

protected:
    // QGraphicsItem::dragEnterEvent() もオーバーライド
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
    // QGraphicsItem::dragMoveEvent() をオーバーライド
    void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override;
    // QGraphicsItem::dropEvent() をオーバーライド
    void dropEvent(QGraphicsSceneDragDropEvent *event) override;
    // QGraphicsItem::dragLeaveEvent() もオーバーライド
    void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override;

private:
    QBrush originalBrush;
};

#endif // DRAGGABLERECTITEM_H

DraggableRectItem.cpp

#include "DraggableRectItem.h"

DraggableRectItem::DraggableRectItem(const QRectF &rect, QGraphicsItem *parent)
    : QGraphicsRectItem(rect, parent)
{
    // アイテムがドロップイベントを受け入れるように設定
    setAcceptDrops(true);
    // アイテムのブラシを保存
    originalBrush = brush();
}

void DraggableRectItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
    // 例として、テキストデータを受け入れるとする
    if (event->mimeData()->hasText()) {
        event->acceptProposedAction();
        qDebug() << "Item: Drag entered with text.";
        // ドロップ可能であることを視覚的に示す(例:色を変える)
        setBrush(Qt::green);
    } else {
        event->ignore();
        qDebug() << "Item: Drag entered, no text. Ignored.";
    }
}

void DraggableRectItem::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
    // dragEnterEvent と同じ条件をチェック
    if (event->mimeData()->hasText()) {
        event->acceptProposedAction();
        qDebug() << "Item: Drag moving with text at" << event->pos();
        // ここで、ドラッグ中のマウスカーソルの位置に基づいて、より詳細なフィードバックを提供できます
        // 例:アイテム内の特定のサブ領域をハイライト表示するなど
    } else {
        event->ignore();
        qDebug() << "Item: Drag moving, no text. Ignored.";
    }
}

void DraggableRectItem::dropEvent(QGraphicsSceneDragDropEvent *event)
{
    if (event->mimeData()->hasText()) {
        QString droppedText = event->mimeData()->text();
        qDebug() << "Item: Dropped text: " << droppedText;
        // ドロップされたテキストを処理するロジック
        // 例:アイテムのテキストを変更するなど
        event->acceptProposedAction();
    } else {
        event->ignore();
    }
    // ドロップ処理後、元のブラシに戻す
    setBrush(originalBrush);
}

void DraggableRectItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
{
    qDebug() << "Item: Drag left.";
    // ドラッグがアイテムから離れたら、元のブラシに戻す
    setBrush(originalBrush);
    QGraphicsRectItem::dragLeaveEvent(event); // 親クラスの処理を呼び出す
}

main.cpp (上記例1の main.cpp を変更)

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMainWindow>
#include "DraggableRectItem.h" // カスタムアイテムをインクルード

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene *scene = new QGraphicsScene();
    QGraphicsView *view = new QGraphicsView(scene);
    view->setAcceptDrops(true); // ビュー全体でドロップを受け入れるように設定

    // シーンに背景アイテムを追加
    scene->addRect(0, 0, 800, 600, QPen(Qt::black), QBrush(Qt::lightGray));
    scene->setSceneRect(0, 0, 800, 600);

    // カスタムのドラッグ可能な四角形アイテムをシーンに追加
    DraggableRectItem *rect1 = new DraggableRectItem(QRectF(100, 100, 150, 100));
    rect1->setBrush(Qt::blue);
    scene->addItem(rect1);

    DraggableRectItem *rect2 = new DraggableRectItem(QRectF(300, 200, 120, 80));
    rect2->setBrush(Qt::red);
    scene->addItem(rect2);

    QMainWindow window;
    window.setCentralWidget(view);
    window.setWindowTitle("Drag & Drop Example (QGraphicsItem)");
    window.resize(800, 600);
    window.show();

    return a.exec();
}

この例では、DraggableRectItem のインスタンスに対してテキストをドラッグすると、そのアイテムの背景色が緑色に変わり、ドロップを受け入れられることを示します。ドラッグがアイテムから離れると元の色に戻ります。

この例では、QGraphicsViewdragMoveEvent を使って、ドラッグ中にビューポート上に仮のプレビュー(ドラッグ中のアイテムがドロップされた場合にどこに配置されるかを示す)を描画する方法を示します。

MyGraphicsViewWithPreview.h

#ifndef MYGRAPHICSVIEWWITHPREVIEW_H
#define MYGRAPHICSVIEWWITHPREVIEW_H

#include <QGraphicsView>
#include <QGraphicsScene>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QPainter> // 描画用
#include <QDebug>

class MyGraphicsViewWithPreview : public QGraphicsView
{
    Q_OBJECT

public:
    MyGraphicsViewWithPreview(QGraphicsScene *scene, QWidget *parent = nullptr);

protected:
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dragMoveEvent(QDragMoveEvent *event) override;
    void dragLeaveEvent(QDragLeaveEvent *event) override; // ドラッグがビューから離れたときに呼び出される
    void dropEvent(QDropEvent *event) override;

    // ビューポートの描画をカスタマイズするためにオーバーライド
    void drawForeground(QPainter *painter, const QRectF &rect) override;

private:
    bool m_isDragging = false;
    QPointF m_dragScenePos; // ドラッグ中のシーン座標
    QRectF m_previewRect;   // プレビューの矩形(シーン座標)
};

#endif // MYGRAPHICSVIEWWITHPREVIEW_H

MyGraphicsViewWithPreview.cpp

#include "MyGraphicsViewWithPreview.h"

MyGraphicsViewWithPreview::MyGraphicsViewWithPreview(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    setAcceptDrops(true);
}

void MyGraphicsViewWithPreview::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasText()) { // テキストデータを受け入れる例
        event->acceptProposedAction();
        m_isDragging = true; // ドラッグ中フラグを立てる
        qDebug() << "Preview View: Drag entered.";
    } else {
        event->ignore();
    }
}

void MyGraphicsViewWithPreview::dragMoveEvent(QDragMoveEvent *event)
{
    if (event->mimeData()->hasText()) {
        event->acceptProposedAction();
        m_isDragging = true;
        m_dragScenePos = mapToScene(event->pos()); // イベントのビューポート座標をシーン座標に変換

        // プレビュー矩形を更新
        // ここでは、ドラッグ中のアイテムが20x20のサイズだと仮定
        m_previewRect = QRectF(m_dragScenePos.x() - 10, m_dragScenePos.y() - 10, 20, 20);

        // ビューポートの再描画をトリガーして、前景にプレビューを描画させる
        viewport()->update();
        qDebug() << "Preview View: Drag moving at scene pos" << m_dragScenePos;
    } else {
        event->ignore();
        m_isDragging = false; // 無関係なドラッグの場合はフラグをリセット
        viewport()->update(); // プレビューを消すために再描画
    }
}

void MyGraphicsViewWithPreview::dragLeaveEvent(QDragLeaveEvent *event)
{
    m_isDragging = false; // ドラッグ終了
    viewport()->update(); // プレビューを消すために再描画
    QGraphicsView::dragLeaveEvent(event);
    qDebug() << "Preview View: Drag left.";
}

void MyGraphicsViewWithPreview::dropEvent(QDropEvent *event)
{
    if (event->mimeData()->hasText()) {
        QString droppedText = event->mimeData()->text();
        QPointF dropScenePos = mapToScene(event->pos());
        qDebug() << "Preview View: Dropped text: " << droppedText << " at scene pos:" << dropScenePos;

        // ここで、ドロップされたテキストと位置を使って新しいQGraphicsItemをシーンに追加するなどの処理
        QGraphicsTextItem *textItem = new QGraphicsTextItem(droppedText);
        textItem->setPos(dropScenePos);
        scene()->addItem(textItem);

        event->acceptProposedAction();
    } else {
        event->ignore();
    }
    m_isDragging = false; // ドラッグ終了
    viewport()->update(); // プレビューを消すために再描画
}

// QGraphicsView::drawForeground をオーバーライドして、プレビューを描画
void MyGraphicsViewWithPreview::drawForeground(QPainter *painter, const QRectF &rect)
{
    QGraphicsView::drawForeground(painter, rect); // 親クラスの前景描画を呼び出す

    if (m_isDragging) {
        painter->setPen(QPen(Qt::DashLine)); // 点線
        painter->setBrush(Qt::NoBrush); // 塗りつぶしなし

        // シーン座標のプレビュー矩形をビューポート座標に変換して描画
        painter->drawRect(mapFromScene(m_previewRect).boundingRect());
    }
}

main.cpp (上記例1の main.cpp を変更)

#include <QApplication>
#include <QGraphicsScene>
#include <QMainWindow>
#include "MyGraphicsViewWithPreview.h" // カスタムビューをインクルード

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene *scene = new QGraphicsScene();
    MyGraphicsViewWithPreview *view = new MyGraphicsViewWithPreview(scene);

    scene->addRect(0, 0, 800, 600, QPen(Qt::black), QBrush(Qt::lightGray));
    scene->setSceneRect(0, 0, 800, 600);

    QMainWindow window;
    window.setCentralWidget(view);
    window.setWindowTitle("Drag & Drop Example (Preview)");
    window.resize(800, 600);
    window.show();

    return a.exec();
}

この例を実行し、任意のアプリケーション(テキストエディタなど)からテキストをこのビューにドラッグすると、マウスカーソルの位置に点線の四角形が描画され、ドロップ時のアイテムの配置場所を視覚的に示します。



QGraphicsScene::dragMoveEvent() を利用する

QGraphicsView は、受け取ったドラッグイベントを内部的に QGraphicsScene に転送します。したがって、QGraphicsView ではなく QGraphicsScene を継承し、その dragMoveEvent() をオーバーライドすることで、シーン全体でのドラッグ操作を処理できます。

特徴

  • 座標系の違い
    QGraphicsViewdragMoveEvent がビューポート座標(ウィジェットのピクセル座標)でイベントを受け取るのに対し、QGraphicsScene::dragMoveEvent はシーン座標(QGraphicsScene内の論理座標)でイベントを受け取ります。これは、シーン内のアイテムとのインタラクションを考慮する際に便利です。
  • アイテムへのイベント転送
    QGraphicsScene は、イベントがアイテムに到達するようにデフォルトで処理します。もし特定のアイテムがイベントを受け入れる設定になっていれば、そのアイテムの dragMoveEvent が呼び出されます。
  • シーン全体への適用
    QGraphicsView が複数ある場合でも、同じ QGraphicsScene を共有していれば、全てのビューで同じドラッグ操作のロジックを適用できます。

いつ使うか

  • 複数のビューで同じドラッグ&ドロップのロジックを再利用したい場合。
  • ドラッグ&ドロップの処理が、ビューの表示方法ではなく、シーン内のアイテムの配置や関係性に強く依存する場合。

例 (概念)

// MyGraphicsScene.h
#include <QGraphicsScene>
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QDebug>

class MyGraphicsScene : public QGraphicsScene
{
    Q_OBJECT
public:
    MyGraphicsScene(QObject *parent = nullptr) : QGraphicsScene(parent) {}

protected:
    void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override {
        if (event->mimeData()->hasFormat("my/custom/data")) {
            // シーン座標で処理
            QPointF scenePos = event->scenePos();
            qDebug() << "Scene: Drag moving at" << scenePos;
            event->acceptProposedAction();
            // シーンの背景に一時的な視覚フィードバックを描画するなど
        } else {
            event->ignore();
        }
        // 親クラスの dragMoveEvent を呼び出し、イベントをアイテムに伝播させる
        QGraphicsScene::dragMoveEvent(event);
    }
    // dragEnterEvent, dragLeaveEvent, dropEvent も同様にオーバーライド
};

// main.cpp
// ...
MyGraphicsScene *scene = new MyGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setAcceptDrops(true); // QGraphicsView もドロップを受け入れる必要あり
// ...

QGraphicsItem::dragMoveEvent() を利用する

最も粒度の細かいドラッグ&ドロップ処理は、個々の QGraphicsItem がイベントを処理することです。QGraphicsItem を継承したカスタムクラスで dragMoveEvent() をオーバーライドし、そのアイテムがドラッグ操作を受け入れるかどうかを判断します。

特徴

  • setAcceptDrops(true) が必須
    アイテムがドロップイベントを受け入れるには、そのアイテムで setAcceptDrops(true) を呼び出す必要があります。
  • 階層的な処理
    イベントは QGraphicsView から QGraphicsScene を経由して、最前面にあるアイテムに伝播されます。アイテムがイベントを accept() しなければ、親アイテムやシーン、ビューへとイベントが戻ります。
  • アイテムごとのロジック
    各アイテムが独自のドラッグ&ドロップロジックを持つことができます。例えば、特定の種類のデータしか受け入れないアイテムや、特定の位置にしかドロップできないアイテムなどを作成できます。

いつ使うか

  • オブジェクト指向的に、各アイテムが自身のドラッグ&ドロップの挙動をカプセル化したい場合。
  • グラフィックスシーン内の特定のオブジェクトに対してドラッグ&ドロップを実装する場合。

例 (概念)

// MyGraphicsItem.h
#include <QGraphicsRectItem>
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QDebug>

class MyGraphicsItem : public QGraphicsRectItem
{
public:
    MyGraphicsItem(const QRectF &rect, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(rect, parent) {
        setAcceptDrops(true); // アイテムがドロップを受け入れるように設定
    }

protected:
    void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override {
        if (event->mimeData()->hasText()) {
            qDebug() << "Item: Drag moving on myself at" << event->pos(); // アイテムローカル座標
            event->acceptProposedAction();
            // アイテムの見た目を更新してフィードバック
            setPen(QPen(Qt::blue, 3));
        } else {
            event->ignore();
            setPen(QPen(Qt::black, 1)); // 受け入れない場合はデフォルトに戻す
        }
    }
    // dragEnterEvent, dragLeaveEvent, dropEvent も同様にオーバーライド
    void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override {
        setPen(QPen(Qt::black, 1)); // ドラッグが離れたら元に戻す
        QGraphicsRectItem::dragLeaveEvent(event);
    }
};

// main.cpp
// ...
// QGraphicsScene *scene = ...;
// MyGraphicsItem *item = new MyGraphicsItem(QRectF(0,0,100,100));
// scene->addItem(item);
// QGraphicsView *view = new QGraphicsView(scene);
// view->setAcceptDrops(true); // ビューも受け入れる必要あり
// ...

イベントフィルター

ドラッグ&ドロップイベントは、通常のイベントと同様にイベントフィルターを使用して処理することも可能です。これは、特定のウィジェットやオブジェクトのイベントを、そのオブジェクト自身を変更せずに(サブクラス化せずに)傍受したい場合に便利です。

特徴

  • 柔軟な制御
    eventFilter() メソッド内でイベントを消費するか (true を返す)、それとも通常のイベント処理に進めるか (false を返す) を柔軟に制御できます。
  • 非侵入的
    既存のクラスをサブクラス化する必要がないため、Qtの提供する標準ウィジェットに対してドラッグ&ドロップ機能を追加するのに適しています。

いつ使うか

  • アプリケーション全体でドラッグ&ドロップの挙動を中央集権的に管理したい場合(ただし、これは複雑になる可能性があります)。
  • サードパーティ製のウィジェットや、変更したくない既存の QGraphicsView のインスタンスに対してドラッグ&ドロップ機能を追加したい場合。

例 (概念)

// MyEventFilter.h
#ifndef MYEVENTFILTER_H
#define MYEVENTFILTER_H

#include <QObject>
#include <QEvent>
#include <QDragMoveEvent>
#include <QMimeData>
#include <QDebug>

class MyEventFilter : public QObject
{
    Q_OBJECT
public:
    MyEventFilter(QObject *parent = nullptr) : QObject(parent) {}

protected:
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event->type() == QEvent::DragMove) {
            QDragMoveEvent *dragMoveEvent = static_cast<QDragMoveEvent*>(event);
            if (dragMoveEvent->mimeData()->hasText()) {
                qDebug() << "Event Filter: Drag moving over " << obj->objectName();
                dragMoveEvent->acceptProposedAction();
                return true; // イベントを消費し、それ以上処理させない
            } else {
                dragMoveEvent->ignore();
                return false; // イベントを消費せず、通常のイベント処理に進める
            }
        }
        return QObject::eventFilter(obj, event);
    }
};

#endif // MYEVENTFILTER_H

// main.cpp
// ...
#include "MyEventFilter.h"
// ...
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene *scene = new QGraphicsScene();
    QGraphicsView *view = new QGraphicsView(scene);
    view->setObjectName("myGraphicsView"); // オブジェクト名を設定
    view->setAcceptDrops(true);

    MyEventFilter *filter = new MyEventFilter(view);
    view->installEventFilter(filter); // ビューにイベントフィルターをインストール

    // ...
    return a.exec();
}

QApplication または QCoreApplication レベルでのイベント監視

非常に特殊なケースですが、アプリケーション全体で全てのドラッグ&ドロップイベントを監視する必要がある場合は、QApplication (または QCoreApplication ) にイベントフィルターをインストールすることも可能です。これは通常、デバッグやロギングの目的で使用され、特定のウィジェットのドラッグ&ドロップ挙動を直接制御するためにはあまり推奨されません。

  • 非常に低レベルでグローバルなドラッグ&ドロップの挙動を変更したい場合(ただし、慎重に行う必要があります)。
  • アプリケーション全体のドラッグ&ドロップのフローを追跡したい場合。
  • 既存のクラスを変更したくない、または複数クラスに共通のロジックを適用したい場合
    イベントフィルター。
  • 特定のアイテムごとに挙動を変えたい場合
    QGraphicsItem::dragMoveEvent()
  • シーン全体に影響を与えたい場合
    QGraphicsScene::dragMoveEvent()
  • 最も一般的なシナリオ
    QGraphicsView 自体または QGraphicsItemdragMoveEvent() をオーバーライドするのが一般的です。