Qt Graphics Viewで画像をドラッグ&ドロップ!QGraphicsView::dropEvent()活用術

2025-05-27

QGraphicsView とは?

まず、QGraphicsView は、QGraphicsScene の内容を表示するためのウィジェットです。QGraphicsScene は、図形アイテム(QGraphicsItem)を管理し、その描画やイベント処理を行います。QGraphicsView は、このシーンをスクロール可能なビューポートとして表示し、ユーザーからのマウスやキーボードイベントをシーン内のアイテムに変換して伝達します。

dropEvent() の役割

dropEvent() は、ユーザーがドラッグしているデータを QGraphicsView 上でマウスボタンを離した際に発生するイベントです。このイベントをオーバーライド(再実装)することで、アプリケーションはドロップされたデータの内容を読み取り、それに応じた処理を行うことができます。

具体的には、以下のようなことを実現するために使われます。

  • カスタムデータのドロップ: アプリケーション独自のデータ形式をドラッグ&ドロップで受け取り、それに応じた処理を行う。
  • アプリケーション内でのドラッグ&ドロップ: 同じアプリケーション内の別のウィジェットや、QGraphicsScene 内の別の場所からアイテムをドラッグし、QGraphicsView の特定の場所にドロップして配置を変える。
  • ファイルのドロップ: 外部からファイル(画像、テキストファイルなど)を QGraphicsView にドラッグ&ドロップし、その内容をシーン内に表示する。

dropEvent() の動作フロー

  1. ドラッグ開始: ユーザーがデータをドラッグし始めると、ドラッグ操作が開始されます。
  2. dragEnterEvent(): ドラッグされたデータが QGraphicsView の領域に入ると、dragEnterEvent() が発生します。ここで、event->acceptProposedAction() を呼び出すことで、この QGraphicsView がそのデータのドロップを受け入れる意図があることをシステムに伝えます。
  3. dragMoveEvent(): ドラッグされたデータが QGraphicsView の領域内で移動している間、繰り返し dragMoveEvent() が発生します。ここでも、ドロップを受け入れるかどうかを判断し、event->acceptProposedAction() を呼び出します。また、ドロップ位置のプレビュー表示などを行うこともできます。
  4. dropEvent(): ユーザーが QGraphicsView 上でマウスボタンを離すと、dropEvent() が発生します。この関数内で、QDropEvent オブジェクトに含まれるデータ(event->mimeData() を通じてアクセス)を取得し、実際にデータを処理します。例えば、画像データであればそれを読み込んで QGraphicsPixmapItem としてシーンに追加する、といった処理を行います。
    • event->pos(): QGraphicsView のローカル座標系でのドロップ位置。
    • event->scenePos(): QGraphicsScene 座標系でのドロップ位置。通常、シーン内のアイテム配置にはこちらを使います。
    • event->mimeData(): ドロップされたデータのMIMEタイプと実際のデータを含む QMimeData オブジェクト。

dropEvent() をオーバーライドする際は、通常以下のような手順を踏みます。

  1. QGraphicsView のサブクラスを作成: QGraphicsView を継承したカスタムクラスを作成します。
  2. setAcceptDrops(true) の呼び出し: カスタム QGraphicsView のコンストラクタなどで setAcceptDrops(true) を呼び出し、このウィジェットがドロップイベントを受け入れるように設定します。
  3. dropEvent() のオーバーライド:
    #include <QGraphicsView>
    #include <QDropEvent>
    #include <QMimeData>
    #include <QDebug> // デバッグ用
    
    class MyGraphicsView : public QGraphicsView
    {
    public:
        MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
            : QGraphicsView(scene, parent)
        {
            setAcceptDrops(true); // ドロップイベントを受け入れる
        }
    
    protected:
        void dragEnterEvent(QDragEnterEvent *event) override
        {
            // ドロップされるデータが処理可能な形式かチェック
            if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
                event->acceptProposedAction(); // ドロップを受け入れる意思を示す
            } else {
                event->ignore(); // 受け入れない
            }
        }
    
        void dragMoveEvent(QDragMoveEvent *event) override
        {
            // dragEnterEvent と同様に、受け入れ可能なデータであれば受け入れる
            if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
                event->acceptProposedAction();
            } else {
                event->ignore();
            }
        }
    
        void dropEvent(QDropEvent *event) override
        {
            if (event->mimeData()->hasUrls()) {
                // ファイルがドロップされた場合
                QList<QUrl> urls = event->mimeData()->urls();
                for (const QUrl &url : urls) {
                    qDebug() << "Dropped file:" << url.toLocalFile();
                    // ここでファイルを読み込み、シーンにアイテムを追加するなどの処理を行う
                    // 例: 画像ファイルであればQGraphicsPixmapItemとして追加
                }
                event->acceptProposedAction(); // ドロップ処理が完了したことを示す
            } else if (event->mimeData()->hasText()) {
                // テキストがドロップされた場合
                QString text = event->mimeData()->text();
                qDebug() << "Dropped text:" << text;
                // ここでテキストを処理する(例: QGraphicsTextItemとして追加)
                event->acceptProposedAction();
            } else {
                event->ignore(); // 処理できないデータ
            }
        }
    };
    


QGraphicsView::dropEvent() の一般的なエラーとトラブルシューティング

QGraphicsView でのドラッグ&ドロップ機能の実装は、複数のイベント(dragEnterEventdragMoveEventdropEvent)が連携して動作するため、どこかで設定やロジックに誤りがあると期待通りに動作しないことがあります。

dropEvent が全く呼ばれない

最も一般的な問題は、dropEvent 自体が呼び出されないことです。

原因とトラブルシューティング

  • イベントフィルタの使用

    • 原因
      もし QGraphicsView に対してイベントフィルタを設定している場合、そのフィルタがドロップイベントを横取りしてしまっている可能性があります。
    • 解決策
      イベントフィルタのロジックを確認し、ドロップイベントが適切に QGraphicsView に渡されるようにするか、フィルタリングの順序を見直します。
  • QDrag オブジェクトで exec() の呼び出し忘れ (ドラッグ元の場合)

    • 原因
      これは dropEvent の受信側ではなく、ドラッグを開始する側(startDrag() を呼び出す側)の問題です。QDrag オブジェクトの exec() メソッドを呼び出していないと、ドラッグ操作自体が開始されません。
    • 解決策
      ドラッグ開始時に QDragexec() を呼び出すことを確認してください。
  • dragEnterEvent または dragMoveEvent で event->acceptProposedAction() を呼び出していない

    • 原因
      ドラッグされたデータが QGraphicsView の領域に入った際(dragEnterEvent)や、領域内で移動している間(dragMoveEvent)に、そのドロップを受け入れる意思を示す event->acceptProposedAction() が呼び出されていない場合、Qt はドロップを許可しません。
    • 解決策
      これらのイベントハンドラ内で、ドロップしたいデータのMIMEタイプをチェックし、問題なければ event->acceptProposedAction() を呼び出すようにします。
      void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
      {
          if (event->mimeData()->hasUrls()) { // 例: ファイルのドロップを受け入れる
              event->acceptProposedAction();
          } else {
              event->ignore(); // それ以外のデータは無視
          }
      }
      
      void MyGraphicsView::dragMoveEvent(QDragMoveEvent *event)
      {
          if (event->mimeData()->hasUrls()) {
              event->acceptProposedAction();
          } else {
              event->ignore();
          }
      }
      
      dragEnterEventacceptProposedAction() を呼び出すことで、ドロップカーソルが許可を示すものに変わります。dragMoveEvent も同様に適切に処理しないと、カーソルが変化しなかったり、ドロップが最終的に拒否されたりします。
    • 原因
      QGraphicsView またはそのサブクラスで setAcceptDrops(true) を呼び出していない場合、ドロップイベントを受け付けません。
    • 解決策
      QGraphicsView のコンストラクタ内で setAcceptDrops(true) を呼び出すことを確認してください。
      MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
          : QGraphicsView(scene, parent)
      {
          setAcceptDrops(true); // これが重要!
      }
      

ドロップされたデータが正しく取得できない

dropEvent は呼び出されるものの、期待するデータ(ファイルパス、テキストなど)が取得できない場合があります。

原因とトラブルシューティング

  • データエンコーディングの問題

    • 原因
      テキストデータをドロップした場合、エンコーディングの問題で文字化けが発生することがあります。
    • 解決策
      QTextCodec を使用して、明示的にエンコーディングを指定してテキストをデコードすることを検討します。ただし、多くの場合はQtが自動的に処理してくれます。
  • 外部アプリケーションからのドロップと内部アプリケーションからのドロップの違い

    • 原因
      外部アプリケーション(例: エクスプローラ、ブラウザ)からファイルをドラッグした場合、QMimeData は通常 text/uri-list 形式でURLを提供します。一方、Qtアプリケーション内で独自のドラッグ&ドロップを実装した場合、カスタムMIMEタイプを使用していることがあります。
    • 解決策
      外部からのドロップを想定する場合は hasUrls()urls() を主に使い、内部からのドロップでカスタムデータを使用する場合は、hasFormat("your/custom-mime-type")data("your/custom-mime-type") を使ってデータを取得します。

ドロップされたアイテムの配置位置がずれる

ドロップした場所にアイテムが表示されない、または想定と異なる位置に表示されることがあります。

原因とトラブルシューティング

  • シーンのコンテンツとビューポートのズレ

    • 原因
      QGraphicsScene の原点と QGraphicsView の表示領域が一致していない場合、ドロップ位置の計算が複雑になることがあります。
    • 解決策
      QGraphicsView::centerOn()fitInView() などでシーンを適切に表示しているか確認し、シーンの座標系を正確に理解しておくことが重要です。
  • 座標系の変換忘れ

    • 原因
      dropEvent->pos()QGraphicsView のローカル座標(ウィジェット上のピクセル座標)を返します。しかし、QGraphicsScene 上にアイテムを配置するには、シーン座標系に変換する必要があります。
    • 解決策
      event->scenePos() を使用するか、mapToScene() を使用してビュー座標からシーン座標に変換します。
      void MyGraphicsView::dropEvent(QDropEvent *event)
      {
          QPointF dropPos = event->scenePos(); // シーン座標系でのドロップ位置
      
          // 例: ドロップ位置にテキストアイテムを追加
          QGraphicsTextItem *textItem = new QGraphicsTextItem("Dropped Text");
          textItem->setPos(dropPos);
          scene()->addItem(textItem);
      
          event->acceptProposedAction();
      }
      
      QGraphicsItem をドロップ位置に正確に配置したい場合は、そのアイテムの**ローカル座標の原点(通常は左上隅)**が dropPos になるように設定されます。アイテムの中心にドロップしたい場合は、アイテムのサイズを考慮して位置を調整する必要があります。

ドロップ後にアプリケーションがクラッシュする、またはハングする

  • 解決策
    • デバッグログの追加
      dropEvent 内で、処理の各段階で qDebug() を使ってログを出力し、どこで問題が発生しているかを特定します。
    • エラーハンドリング
      ファイル操作やネットワーク操作など、失敗する可能性のある処理には適切なエラーハンドリング(try-catch ブロックや戻り値のチェック)を追加します。
    • リソースの解放
      取得したデータ(QMimeData からのデータなど)や、生成したオブジェクトが適切に解放されているか確認します。
    • 大きなファイルの処理
      非常に大きなファイルをドロップした場合、一度にすべてを読み込もうとするとメモリ不足になる可能性があります。非同期処理やチャンクごとの読み込みを検討します。
  • 原因
    ドロップされたデータの処理中に、無効なメモリアクセス、ファイルの読み書きエラー、または無限ループなどが発生している可能性があります。
  1. 最小限の再現コード
    問題が発生している部分だけを切り出し、できるだけシンプルなコードで再現を試みます。余計な要素を排除することで、問題の特定が容易になります。
  2. デバッグ出力
    qDebug() を多用し、イベントがいつ呼び出されたか、QMimeData の内容、計算された座標など、重要な情報をログに出力します。
  3. イベントの連鎖を理解する
    dragEnterEventdragMoveEventdropEvent の順序と、それぞれのイベントで acceptProposedAction() を呼び出すことの重要性を再確認します。
  4. Qt のドキュメントを参照する
    公式ドキュメントの QGraphicsViewQDropEventQMimeDataQDrag のページを読み、それぞれのメソッドの役割と正しい使い方を再確認します。

dropEvent() が呼ばれない

最も一般的な問題の一つです。ドロップイベントが期待通りに発生しない場合、いくつかの原因が考えられます。

考えられる原因と解決策

  • MIMEデータ形式の不一致

    • 原因
      ドラッグされているデータのMIMEタイプと、dragEnterEvent()dragMoveEvent()acceptProposedAction() を呼び出す際の条件が一致していない。
    • 解決策
      QDropEvent::mimeData() から取得できる QMimeData オブジェクトを使って、本当に処理したいデータが含まれているかを確認します。例えば、ファイルであれば event->mimeData()->hasUrls()、テキストであれば event->mimeData()->hasText() など。
  • QGraphicsScene や QGraphicsItem のドロップ処理との競合

    • 原因
      QGraphicsView は受け取ったドラッグ&ドロップイベントを QGraphicsScene に転送し、さらにシーンはそのイベントを適切な QGraphicsItem に転送しようとします。もし QGraphicsScene または特定の QGraphicsItem がイベントを処理して event->accept() してしまった場合、QGraphicsViewdropEvent() が意図通りに呼ばれない、または後から呼ばれてもすでに処理済みとみなされる可能性があります。
    • 解決策
      • View でのみ処理したい場合
        QGraphicsViewdragEnterEvent()dragMoveEvent()dropEvent() 内で、QGraphicsView::dragEnterEvent(event); のように基底クラスの関数を呼ばないようにします。これにより、イベントがシーンやアイテムに伝播するのを防ぎます。ただし、これはシーン内のアイテムが独自のD&Dを受け入れる必要がある場合には適していません。
      • シーンやアイテムにもD&Dを許可しつつ、Viewでも特定の処理を行いたい場合
        各イベントハンドラで event->acceptProposedAction() を呼び出した後、QGraphicsView::dragEnterEvent(event); のように基底クラスの関数を呼び出すことで、イベントをシーンに伝播させます。そして、シーンやアイテム側でも適切にイベントを処理するようにします。どちらのレベルでイベントを処理するか(または両方で処理するか)を明確にする必要があります。
  • dragEnterEvent() または dragMoveEvent() でイベントを受け入れていない

    • 原因
      dropEvent() が呼ばれるためには、それ以前に dragEnterEvent()dragMoveEvent() の両方でイベントを「受け入れる」必要があります。具体的には、event->acceptProposedAction() または event->accept() を呼び出す必要があります。もしこれらの関数で event->ignore() が呼ばれているか、何も呼ばれていない(デフォルトで ignore() と同じ動作)場合、ドロップは許可されません。
    • 解決策
      dragEnterEvent()dragMoveEvent() の両方をオーバーライドし、ドロップしたいデータのMIMEタイプをチェックして適切に event->acceptProposedAction() を呼び出すようにしてください。
      protected:
          void dragEnterEvent(QDragEnterEvent *event) override
          {
              if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
                  event->acceptProposedAction(); // ここで受け入れる
              } else {
                  event->ignore();
              }
          }
      
          void dragMoveEvent(QDragMoveEvent *event) override
          {
              if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
                  event->acceptProposedAction(); // ここでも受け入れる
              } else {
                  event->ignore();
              }
          }
      
  • setAcceptDrops(true) の呼び忘れ

    • 原因
      QGraphicsView はデフォルトではドロップイベントを受け入れません。明示的に setAcceptDrops(true) を呼び出す必要があります。
    • 解決策
      MyGraphicsView クラスのコンストラクタ内で setAcceptDrops(true); を呼び出しているか確認してください。
      class MyGraphicsView : public QGraphicsView
      {
      public:
          MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
              : QGraphicsView(scene, parent)
          {
              setAcceptDrops(true); // これが重要!
          }
          // ...
      };
      

ドロップされたデータの取得に失敗する / データが期待通りではない

dropEvent() は呼ばれるが、ドロップされたデータの内容が正しく取得できない、または予想と異なるという問題。

考えられる原因と解決策

  • データが大きすぎる / 無効なデータ

    • 原因
      ドロップされたデータが非常に大きい、または破損している場合に、読み込みに失敗することがあります。
    • 解決策
      データサイズを考慮したり、データ読み込み時のエラーハンドリングをしっかり行ったりすることが重要です。
  • 外部アプリケーションからのドロップと内部ドラッグ&ドロップの違い

    • 原因
      外部アプリケーション(例: エクスプローラー、Webブラウザ)からのドラッグ&ドロップと、自身のアプリケーション内でのドラッグ&ドロップでは、QMimeData の内容が異なる場合があります。特にカスタムデータ形式を扱う場合は注意が必要です。
    • 解決策
      外部からのドロップと内部からのドロップで、それぞれ異なるMIMEタイプを想定するか、共通の処理ロジックを確立します。アプリケーション内部で独自のデータをドラッグ&ドロップする場合は、QMimeData::setData() を使ってカスタムMIMEタイプ (application/x-my-custom-data など) とデータを設定すると良いでしょう。
  • MIMEタイプの誤解

    • 原因
      ドロップされたデータのMIMEタイプを誤って解釈している。例えば、画像ファイルをドロップしたのにテキストとして読み取ろうとしている、など。
    • 解決策
      QMimeData クラスの様々なメソッド (hasUrls(), hasText(), hasImage(), hasHtml(), hasColor() など) を使って、どのようなデータが実際に利用可能かを確認します。複数の形式を持つデータの場合、最も適切なものを選んで処理します。
      void dropEvent(QDropEvent *event) override
      {
          const QMimeData *mimeData = event->mimeData();
      
          if (mimeData->hasUrls()) {
              QList<QUrl> urls = mimeData->urls();
              // ファイルURLの処理
          } else if (mimeData->hasText()) {
              QString text = mimeData->text();
              // テキストデータの処理
          } else if (mimeData->hasImage()) {
              QImage image = qvariant_cast<QImage>(mimeData->imageData());
              // 画像データの処理
          }
          // ...
          event->acceptProposedAction();
      }
      

ドロップ位置のずれ / マッピングの問題

ドロップされたアイテムが、意図した場所に配置されない。

考えられる原因と解決策

  • ビューの変換(ズーム、回転など)

    • 原因
      QGraphicsView にズームや回転などの変換が適用されている場合、ビューポート座標からシーン座標へのマッピングが複雑になります。
    • 解決策
      event->scenePos() はこれらの変換を考慮して正しいシーン座標を返してくれるため、これを使用することが推奨されます。もし event->pos() を使って手動で変換する必要がある場合は、QGraphicsView::mapToScene() を使用します。
      void dropEvent(QDropEvent *event) override
      {
          // ...
          QPointF scenePos = mapToScene(event->pos()); // event->pos() をシーン座標にマッピング
          // ...
          event->acceptProposedAction();
      }
      
  • ビューポート座標とシーン座標の混同

    • 原因
      QDropEvent::pos()QGraphicsView のビューポート座標系でのドロップ位置を返しますが、QGraphicsScene 内のアイテムを配置するにはシーン座標が必要です。
    • 解決策
      event->scenePos() を使用して、シーン座標でのドロップ位置を取得します。
      void dropEvent(QDropEvent *event) override
      {
          // ...
          QPointF scenePos = event->scenePos(); // シーン座標で取得
      
          // 例: ドロップされた画像アイテムをシーンに追加し、ドロップ位置に配置
          // QGraphicsPixmapItem *pixmapItem = new QGraphicsPixmapItem(QPixmap("path/to/image.png"));
          // pixmapItem->setPos(scenePos);
          // scene()->addItem(pixmapItem);
      
          event->acceptProposedAction();
      }
      

ドラッグカーソルが「許可しない」表示になる

ドラッグ中にカーソルが「許可しない」マーク(斜線入りの円)になる場合。

考えられる原因と解決策

  • 許可するアクションの不一致

    • 原因
      ドラッグ操作が許可しているアクション (Qt::CopyAction, Qt::MoveAction, Qt::LinkAction など) と、ドロップ側が受け入れるアクションが一致していない場合。
    • 解決策
      dragEnterEvent()dragMoveEvent() 内で event->proposedAction()event->possibleActions() を確認し、適切なアクションを受け入れるようにします。
      void dragEnterEvent(QDragEnterEvent *event) override
      {
          if (event->mimeData()->hasUrls() && event->proposedAction() & Qt::CopyAction) {
              event->acceptProposedAction();
          } else {
              event->ignore();
          }
      }
      
  • dragEnterEvent() または dragMoveEvent() で acceptProposedAction() を呼び出していない

    • 原因
      上述の「dropEvent() が呼ばれない」と同じ原因で、システムがドロップを許可しないと判断しているためです。
    • 解決策
      dragEnterEvent()dragMoveEvent() で適切なMIMEタイプをチェックし、event->acceptProposedAction() を呼び出すようにします。
  • 最小限の再現コードを作成する
    問題が発生した場合は、その問題を再現できる最小限のコードを作成してみてください。これにより、問題の原因を特定しやすくなります。
  • イベントの伝播を理解する
    QGraphicsViewQGraphicsSceneQGraphicsItem の間でドラッグ&ドロップイベントがどのように伝播するかを理解することが重要です。それぞれのレベルでイベントをオーバーライドし、どの関数が呼ばれているかを qDebug() で追跡してみてください。
  • qDebug() を多用する
    各イベントハンドラ (dragEnterEvent, dragMoveEvent, dropEvent) の最初で qDebug() を使ってメッセージを出力し、どのイベントが、どのような順序で、どの引数で呼ばれているかを確認します。特に event->mimeData()->formats() で利用可能なMIMEタイプを確認すると良いでしょう。


例1: ファイルをドラッグ&ドロップして画像を表示する

最も一般的なユースケースの一つです。外部から画像ファイルを QGraphicsView にドラッグ&ドロップし、それを QGraphicsPixmapItem としてシーンに追加します。

MyGraphicsView.h (カスタム QGraphicsView のヘッダファイル)

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QDropEvent> // QDropEvent を使うために必要
#include <QDragEnterEvent> // QDragEnterEvent を使うために必要
#include <QDragMoveEvent> // QDragMoveEvent を使うために必要
#include <QMimeData> // QMimeData を使うために必要
#include <QGraphicsScene> // シーンを扱うために必要
#include <QGraphicsPixmapItem> // 画像アイテムのために必要
#include <QUrl> // ファイルパスを扱うために必要
#include <QImage> // 画像を読み込むために必要
#include <QFileInfo> // ファイル情報を得るために必要
#include <QDebug> // デバッグ出力用

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT // シグナル/スロットを使用する場合に必要

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

protected:
    // ドロップイベントを受け入れるために、以下の仮想関数をオーバーライドします。
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dragMoveEvent(QDragMoveEvent *event) override;
    void dropEvent(QDropEvent *event) override;
};

#endif // MYGRAPHICSVIEW_H

MyGraphicsView.cpp (カスタム QGraphicsView のソースファイル)

#include "MyGraphicsView.h"

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

void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "dragEnterEvent called.";
    // ドロップされるデータがURL(ファイルパス)を持っているかチェック
    if (event->mimeData()->hasUrls()) {
        // 全てのURLが画像ファイルかどうかをチェックすることもできますが、
        // ここでは単純にURLがあれば受け入れるとします。
        event->acceptProposedAction(); // ドロップを受け入れる意思を示す
        qDebug() << "Accepted proposed action in dragEnterEvent.";
    } else {
        event->ignore(); // ドロップを受け入れない
        qDebug() << "Ignored in dragEnterEvent (no URLs).";
    }
}

void MyGraphicsView::dragMoveEvent(QDragMoveEvent *event)
{
    // dragEnterEvent と同様に、ここでもドロップを受け入れるか判断
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
        // ドロップ位置の視覚的なフィードバック(例: 枠の表示など)をここで行うこともできます
        // qWarning() << "Drag move at:" << event->pos(); // デバッグ用
    } else {
        event->ignore();
    }
}

void MyGraphicsView::dropEvent(QDropEvent *event)
{
    qDebug() << "dropEvent called.";
    if (event->mimeData()->hasUrls()) {
        QList<QUrl> urls = event->mimeData()->urls();
        for (const QUrl &url : urls) {
            QString filePath = url.toLocalFile();
            qDebug() << "Dropped file:" << filePath;

            // ファイルが画像かどうかを簡単な方法でチェック
            QFileInfo fileInfo(filePath);
            QString suffix = fileInfo.suffix().toLower();
            if (suffix == "png" || suffix == "jpg" || suffix == "jpeg" || suffix == "gif" || suffix == "bmp") {
                QImage image(filePath);
                if (!image.isNull()) {
                    QGraphicsPixmapItem *pixmapItem = new QGraphicsPixmapItem(QPixmap::fromImage(image));
                    // ドロップされた位置をシーン座標に変換してアイテムを配置
                    pixmapItem->setPos(mapToScene(event->pos()));
                    scene()->addItem(pixmapItem);
                    qDebug() << "Added image item at scene pos:" << pixmapItem->pos();
                } else {
                    qWarning() << "Failed to load image:" << filePath;
                }
            } else {
                qWarning() << "Dropped file is not a supported image format:" << filePath;
            }
        }
        event->acceptProposedAction(); // ドロップ処理が完了したことを示す
        qDebug() << "Accepted proposed action in dropEvent.";
    } else {
        event->ignore(); // 処理できないデータ
        qDebug() << "Ignored in dropEvent (no URLs).";
    }
}

main.cpp (アプリケーションのエントリポイント)

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

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

    QMainWindow mainWindow;
    mainWindow.setWindowTitle("Qt GraphicsView Drop Image Example");
    mainWindow.resize(800, 600);

    QGraphicsScene *scene = new QGraphicsScene(&mainWindow);
    scene->setSceneRect(0, 0, 1000, 800); // シーンのサイズを設定
    scene->setBackgroundBrush(Qt::lightGray); // シーンの背景色を設定

    MyGraphicsView *view = new MyGraphicsView(scene, &mainWindow);
    // ビューの配置(中央に表示など)
    view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    mainWindow.setCentralWidget(view);
    mainWindow.show();

    return a.exec();
}

解説

  • dropEvent() が実際にデータを処理する場所です。
    • event->mimeData()->urls() でドロップされたファイルのURLリストを取得します。
    • それぞれのURLをローカルファイルパスに変換し (toLocalFile())、ファイルの種類をチェックします。
    • 画像ファイルであれば QImage で読み込み、QGraphicsPixmapItem を作成します。
    • mapToScene(event->pos()) を使って、ビューのドロップ位置(ウィジェット座標)をシーンの座標に変換し、画像アイテムをその位置に配置します。
    • 最後に scene()->addItem(pixmapItem) でシーンにアイテムを追加します。
    • event->acceptProposedAction() を呼び出して、ドロップが正常に処理されたことを示します。
  • dragMoveEvent() も同様に、ドラッグ中もドロップが許可されることを継続して示します。
  • dragEnterEvent() では、ドラッグされたデータがURL (event->mimeData()->hasUrls()) を含んでいるかを確認し、もしそうであれば event->acceptProposedAction() を呼び出します。これにより、ドラッグカーソルが「ドロップ可能」な表示に変わります。
  • コンストラクタで setAcceptDrops(true) を呼び出し、このビューがドロップイベントを受け入れるように設定します。
  • MyGraphicsView クラスは QGraphicsView を継承しています。

例2: テキストをドラッグ&ドロップして QGraphicsTextItem を作成する

Webブラウザやテキストエディタなどからテキストをドラッグ&ドロップし、QGraphicsTextItem としてシーンに追加する例です。

MyGraphicsView.h は例1と同じで構いませんが、MyGraphicsView.cppdragEnterEventdropEvent を以下のように変更します。

MyGraphicsView.cpp (テキストドロップ対応部分のみ)

#include "MyGraphicsView.h"
#include <QGraphicsTextItem> // テキストアイテムのために必要

// ... (コンストラクタとdragMoveEventは例1と同じ)

void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "dragEnterEvent called.";
    // URLまたはテキストを持っているかチェック
    if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
        event->acceptProposedAction();
        qDebug() << "Accepted proposed action in dragEnterEvent.";
    } else {
        event->ignore();
        qDebug() << "Ignored in dragEnterEvent (no URLs or text).";
    }
}

void MyGraphicsView::dropEvent(QDropEvent *event)
{
    qDebug() << "dropEvent called.";
    const QMimeData *mimeData = event->mimeData();

    if (mimeData->hasUrls()) {
        // 例1と同じファイルドロップ処理
        QList<QUrl> urls = mimeData->urls();
        for (const QUrl &url : urls) {
            QString filePath = url.toLocalFile();
            qDebug() << "Dropped file:" << filePath;
            // ... (画像ファイル処理など、例1の続き)
        }
        event->acceptProposedAction();
    } else if (mimeData->hasText()) {
        // テキストがドロップされた場合
        QString droppedText = mimeData->text();
        qDebug() << "Dropped text:" << droppedText;

        QGraphicsTextItem *textItem = new QGraphicsTextItem(droppedText);
        textItem->setPos(mapToScene(event->pos())); // ドロップ位置に配置
        scene()->addItem(textItem);
        qDebug() << "Added text item at scene pos:" << textItem->pos();

        event->acceptProposedAction();
    } else {
        event->ignore();
        qDebug() << "Ignored in dropEvent (unsupported MIME data).";
    }
}

解説

  • 取得した文字列を使って QGraphicsTextItem を作成し、シーンに追加します。
  • 次に else if (mimeData->hasText()) でテキストデータがあるかをチェックし、mimeData->text() で文字列を取得します。
  • dropEvent() では、まず hasUrls() をチェックし、ファイルドロップであれば例1と同じ処理をします。
  • dragEnterEvent()event->mimeData()->hasText() の条件も追加し、テキストドロップを受け入れるようにします。

QGraphicsViewdropEvent は、外部からのドロップだけでなく、アプリケーション内部の QGraphicsItem のドラッグ&ドロップでも利用できます。この例では、QGraphicsItem をドラッグし、別の QGraphicsView にドロップして移動させることを考えます。

この場合は、通常 QGraphicsItem 自体でドラッグを開始し、QGraphicsView がそのドロップを受け入れる形になります。

MyMovableItem.h (ドラッグ可能な QGraphicsItem の定義)

#ifndef MYMOVABLEITEM_H
#define MYMOVABLEITEM_H

#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QApplication> // QApplication::startDragDistance() のため

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

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;

private:
    QPointF m_dragStartPos; // ドラッグ開始時のマウス位置 (アイテムローカル座標)
};

#endif // MYMOVABLEITEM_H

MyMovableItem.cpp (ドラッグ可能な QGraphicsItem の実装)

#include "MyMovableItem.h"
#include <QDebug>

MyMovableItem::MyMovableItem(const QRectF &rect, QGraphicsItem *parent)
    : QGraphicsRectItem(rect, parent)
{
    setFlags(ItemIsMovable | ItemSendsGeometryChanges); // 移動可能に設定
    setBrush(Qt::blue); // 適当な色
    setAcceptDrops(false); // アイテム自体ではドロップを受け入れない (ビューで処理するため)
}

void MyMovableItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        m_dragStartPos = event->pos(); // アイテムローカル座標でのドラッグ開始位置を記録
    }
    QGraphicsRectItem::mousePressEvent(event); // 基底クラスのイベントハンドラを呼び出す
}

void MyMovableItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        // ドラッグ開始距離を超えたらドラッグ操作を開始
        if ((event->pos() - m_dragStartPos).manhattanLength() > QApplication::startDragDistance()) {
            QDrag *drag = new QDrag(event->widget()); // ドラッグを開始するウィジェット (ビュー)
            QMimeData *mimeData = new QMimeData;

            // アイテムのユニークなIDやタイプをMIMEデータに設定
            // 例えば、アイテムのポインタを直接渡すのは安全ではないので、IDなどを使う
            // ここでは簡易的に、アイテムのポインタを文字列化して渡す(実用では推奨されない)
            mimeData->setText(QString("MyMovableItem_ID:%1").arg(reinterpret_cast<quintptr>(this)));
            drag->setMimeData(mimeData);

            // ドラッグ開始時の画像を設定することも可能
            QPixmap pixmap(boundingRect().size().toSize());
            pixmap.fill(Qt::transparent);
            QPainter painter(&pixmap);
            paint(&painter, nullptr, nullptr); // アイテム自身を描画
            drag->setPixmap(pixmap);

            // ドラッグ操作を開始
            // Qt::MoveAction を推奨。ドロップ成功後、元のアイテムを削除するため
            Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);

            if (dropAction == Qt::MoveAction) {
                // ドロップが成功し、移動アクションが選択された場合、元のアイテムを削除
                qDebug() << "Item moved, deleting original.";
                delete this; // シーンからアイテムを削除
            } else {
                qDebug() << "Drag cancelled or copied.";
            }
        }
    }
    QGraphicsRectItem::mouseMoveEvent(event);
}

MyGraphicsView.cpp (QGraphicsView 側でアイテムドロップを受け入れる)

#include "MyGraphicsView.h"
#include "MyMovableItem.h" // MyMovableItem をインクルード
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QUrl>
#include <QImage>
#include <QFileInfo>
#include <QDebug>
#include <QRegExp> // ID抽出用

MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    setAcceptDrops(true);
    qDebug() << "MyGraphicsView initialized, setAcceptDrops(true).";
}

void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "dragEnterEvent called.";
    if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
        event->acceptProposedAction();
        qDebug() << "Accepted proposed action in dragEnterEvent.";
    } else {
        event->ignore();
        qDebug() << "Ignored in dragEnterEvent (no URLs or text).";
    }
}

void MyGraphicsView::dragMoveEvent(QDragMoveEvent *event)
{
    if (event->mimeData()->hasUrls() || event->mimeData()->hasText()) {
        event->acceptProposedAction();
    } else {
        event->ignore();
    }
}

void MyGraphicsView::dropEvent(QDropEvent *event)
{
    qDebug() << "dropEvent called.";
    const QMimeData *mimeData = event->mimeData();

    if (mimeData->hasUrls()) {
        // 例1と同じファイルドロップ処理
        // ... (省略)
        event->acceptProposedAction();
    } else if (mimeData->hasText()) {
        QString droppedText = mimeData->text();
        qDebug() << "Dropped text:" << droppedText;

        // ドロップされたテキストがカスタムアイテムのID形式かチェック
        QRegExp rx("^MyMovableItem_ID:(\\d+)$");
        if (rx.indexIn(droppedText) != -1) {
            quintptr originalItemId = rx.cap(1).toULongLong();
            qDebug() << "Detected dropped MyMovableItem with ID:" << originalItemId;

            // 新しいアイテムをドロップ位置に作成
            MyMovableItem *newItem = new MyMovableItem(QRectF(0, 0, 50, 50));
            newItem->setPos(mapToScene(event->pos()));
            scene()->addItem(newItem);
            event->setDropAction(Qt::MoveAction); // 元のアイテムを削除させるためにMoveActionを設定
            event->acceptProposedAction();
            qDebug() << "Created new MyMovableItem at scene pos:" << newItem->pos();
        } else {
            // 通常のテキストとして処理
            QGraphicsTextItem *textItem = new QGraphicsTextItem(droppedText);
            textItem->setPos(mapToScene(event->pos()));
            scene()->addItem(textItem);
            qDebug() << "Added text item at scene pos:" << textItem->pos();
            event->acceptProposedAction();
        }
    } else {
        event->ignore();
    }
}

main.cpp (シーンに初期アイテムを追加)

#include <QApplication>
#include <QMainWindow>
#include "MyGraphicsView.h"
#include "MyMovableItem.h" // MyMovableItem をインクルード

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

    QMainWindow mainWindow;
    mainWindow.setWindowTitle("Qt GraphicsView Internal Drag&Drop Example");
    mainWindow.resize(800, 600);

    QGraphicsScene *scene = new QGraphicsScene(&mainWindow);
    scene->setSceneRect(0, 0, 1000, 800);
    scene->setBackgroundBrush(Qt::lightGray);

    MyGraphicsView *view = new MyGraphicsView(scene, &mainWindow);
    view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    // シーンに移動可能なアイテムをいくつか追加
    MyMovableItem *item1 = new MyMovableItem(QRectF(50, 50, 80, 80));
    scene->addItem(item1);
    MyMovableItem *item2 = new MyMovableItem(QRectF(200, 100, 60, 60));
    scene->addItem(item2);

    mainWindow.setCentralWidget(view);
    mainWindow.show();

    return a.exec();
}
  • MyGraphicsView の dropEvent()
    • mimeData()->hasText() でテキストデータがあるかを確認します。
    • 正規表現などを使って、ドロップされたテキストが「アイテムID」形式であるかを判別します。
    • もしアイテムIDであれば、新しい MyMovableItem を作成し、ドロップされたシーン位置に配置します。
    • event->setDropAction(Qt::MoveAction); を呼び出すことで、ドラッグ元(MyMovableItemdrag->exec())に「移動アクションが完了した」ことを伝えます。これにより、ドラッグ元のアイテムは自身を削除します。
    • 通常のテキストドロップも引き続き処理できるように else 句で QGraphicsTextItem を作成する処理も残しています。
  • MyMovableItem
    • QGraphicsRectItem を継承し、ItemIsMovable フラグを設定してデフォルトの移動機能を有効にします。
    • mousePressEvent() でドラッグ開始位置を記録します。
    • mouseMoveEvent() で、マウスが一定距離移動したら QDrag オブジェクトを作成し、ドラッグを開始します。
    • QMimeData には、ドロップ時に識別するための情報(ここではアイテムのポインタを文字列化した簡易的なID)を設定します。実用では、より堅牢なIDやシリアライズされたデータを使用します。
    • drag->exec() でドラッグ操作を実行します。戻り値が Qt::MoveAction であれば、ドロップが成功し、アイテムが移動されたと判断して元のアイテムを delete this; で削除します。


QGraphicsScene::dropEvent() で処理する

QGraphicsSceneQGraphicsView と同様に dragEnterEvent(), dragMoveEvent(), dropEvent() といった仮想関数を持っています。これらの関数は、QGraphicsView がイベントを受け取った後にシーンに転送される QGraphicsSceneDragDropEvent として処理されます。

使い分けのポイント

  • 実装の注意点
    • QGraphicsViewsetAcceptDrops(true) を設定することを忘れないでください。
    • QGraphicsView のイベントハンドラで event->acceptProposedAction() を呼び出すだけでなく、QGraphicsView::dragEnterEvent(event);QGraphicsView::dropEvent(event); のように基底クラスのメソッドを呼び出すことで、イベントがシーンに伝播するようにします。これにより、シーンのイベントハンドラが呼ばれるようになります。
  • イベント伝播の順序
    1. まず QGraphicsView のイベントハンドラが呼ばれます。
    2. QGraphicsView のイベントハンドラで event->ignore() が呼ばれない限り、イベントは関連付けられた QGraphicsScene に転送されます。
    3. QGraphicsScene のイベントハンドラで event->ignore() が呼ばれない限り、イベントはシーン内のアイテムに転送されます。
  • シーン全体に影響するドロップの場合
    例えば、背景画像としてファイルをドロップしたり、シーンの空白部分に新しいアイテムを作成したりする場合など、特定のアイテムではなくシーン全体に関わるドロップ処理に適しています。

コード例(QGraphicsScene::dropEvent() を使用)

// MyGraphicsScene.h
#ifndef MYGRAPHICSSCENE_H
#define MYGRAPHICSSCENE_H

#include <QGraphicsScene>
#include <QGraphicsSceneDragDropEvent> // シーンのD&Dイベントはこれを使う
#include <QMimeData>
#include <QUrl>
#include <QGraphicsPixmapItem>
#include <QDebug>

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

protected:
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
    void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override;
    void dropEvent(QGraphicsSceneDragDropEvent *event) override;
};

#endif // MYGRAPHICSSCENE_H

// MyGraphicsScene.cpp
#include "MyGraphicsScene.h"

MyGraphicsScene::MyGraphicsScene(QObject *parent)
    : QGraphicsScene(parent)
{
    // QGraphicsScene には setAcceptDrops() がない
    // ドロップは QGraphicsView 経由で伝播されるか、
    // QGraphicsItem が直接受け入れる。
    // シーンで処理する場合、ビュー側で setAcceptDrops(true) が必要。
    qDebug() << "MyGraphicsScene initialized.";
}

void MyGraphicsScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
    qDebug() << "MyGraphicsScene::dragEnterEvent called.";
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
        qDebug() << "Scene accepted proposed action in dragEnterEvent.";
    } else {
        event->ignore();
        qDebug() << "Scene ignored in dragEnterEvent.";
    }
}

void MyGraphicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
    } else {
        event->ignore();
    }
}

void MyGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent *event)
{
    qDebug() << "MyGraphicsScene::dropEvent called.";
    if (event->mimeData()->hasUrls()) {
        QList<QUrl> urls = event->mimeData()->urls();
        for (const QUrl &url : urls) {
            QString filePath = url.toLocalFile();
            qDebug() << "Scene dropped file:" << filePath;
            QPixmap pixmap(filePath);
            if (!pixmap.isNull()) {
                QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
                item->setPos(event->scenePos()); // シーン座標で配置
                addItem(item); // シーンにアイテムを追加
            } else {
                qWarning() << "Scene failed to load image:" << filePath;
            }
        }
        event->acceptProposedAction();
        qDebug() << "Scene accepted proposed action in dropEvent.";
    } else {
        event->ignore();
        qDebug() << "Scene ignored in dropEvent.";
    }
}

// main.cpp (MyGraphicsView で MyGraphicsScene を使う)
#include <QApplication>
#include <QMainWindow>
#include "MyGraphicsView.h" // MyGraphicsView は setAcceptDrops(true) を持つ
#include "MyGraphicsScene.h"

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

    QMainWindow mainWindow;
    mainWindow.setWindowTitle("Qt GraphicsScene Drop Example");
    mainWindow.resize(800, 600);

    MyGraphicsScene *scene = new MyGraphicsScene(&mainWindow);
    scene->setSceneRect(0, 0, 1000, 800);
    scene->setBackgroundBrush(Qt::lightGray);

    MyGraphicsView *view = new MyGraphicsView(scene, &mainWindow); // MyGraphicsView は setAcceptDrops(true) を呼び出す
    // MyGraphicsView の dragEnterEvent/dropEvent で基底クラスの呼び出しを忘れない
    // もしMyGraphicsViewのドロップ処理がなければ、単にQGraphicsViewを直接使う
    // view->setAcceptDrops(true); // もしMyGraphicsViewをカスタムしないならここ
    
    // ここで MyGraphicsView の dragEnterEvent/dropEvent をオーバーライドして、
    // シーンにイベントを渡すために QGraphicsView::dragEnterEvent(event); を呼び出す必要があります。
    // 例:
    /*
    // MyGraphicsView.h (例1と同じでOK)
    class MyGraphicsView : public QGraphicsView {
        // ...
    protected:
        void dragEnterEvent(QDragEnterEvent *event) override {
            QGraphicsView::dragEnterEvent(event); // シーンにイベントを転送
        }
        void dragMoveEvent(QDragMoveEvent *event) override {
            QGraphicsView::dragMoveEvent(event); // シーンにイベントを転送
        }
        void dropEvent(QDropEvent *event) override {
            QGraphicsView::dropEvent(event); // シーンにイベントを転送
        }
    };
    */
    // 上記のように MyGraphicsView を実装しない場合、QGraphicsView はデフォルトでイベントをシーンに転送します。

    mainWindow.setCentralWidget(view);
    mainWindow.show();

    return a.exec();
}

QGraphicsItem::dropEvent() で処理する

特定の QGraphicsItem 上にドロップされた場合にのみ処理を行いたい場合、そのアイテムのサブクラスでドラッグ&ドロップイベントハンドラをオーバーライドします。

使い分けのポイント

  • 実装の注意点
    • QGraphicsItem のコンストラクタで setAcceptDrops(true) を呼び出します。
    • QGraphicsSceneQGraphicsView のイベントハンドラでは、イベントを ignore() せずに、基底クラスのイベントハンドラを呼び出すなどして、イベントがアイテムに伝播するようにします。
  • イベント伝播の順序
    1. QGraphicsView のイベントハンドラが呼ばれる。
    2. QGraphicsScene のイベントハンドラが呼ばれる。
    3. 最後に、イベントがドロップされた位置にある最も上位の(手前の)アイテムのイベントハンドラが呼ばれます。もしそのアイテムがイベントを受け入れた場合 (event->accept())、それ以上のイベント伝播は停止します。
  • 特定のアイテムにドロップされた場合
    アイテムを「コンテナ」として機能させたり、ドロップされたデータでアイテムのプロパティを変更したりする場合に最適です。例えば、画像アイテムに別の画像をドロップして画像を置き換えたり、フォルダを表すアイテムにファイルをドロップしてそのフォルダに格納したりするような場合です。

コード例(QGraphicsItem::dropEvent() を使用)

// MyDroppableItem.h
#ifndef MYDROPPABLEITEM_H
#define MYDROPPABLEITEM_H

#include <QGraphicsRectItem>
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QUrl>
#include <QGraphicsPixmapItem>
#include <QDebug>
#include <QPainter> // 描画のため

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

protected:
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
    void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override;
    void dropEvent(QGraphicsSceneDragDropEvent *event) override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;

private:
    bool m_dragOver; // ドラッグ中かどうかを示すフラグ
    QPixmap m_pixmap; // ドロップされた画像
};

#endif // MYDROPPABLEITEM_H

// MyDroppableItem.cpp
#include "MyDroppableItem.h"

MyDroppableItem::MyDroppableItem(const QRectF &rect, QGraphicsItem *parent)
    : QGraphicsRectItem(rect, parent), m_dragOver(false)
{
    setAcceptDrops(true); // このアイテムがドロップを受け入れるように設定
    setPen(QPen(Qt::black, 2));
    setBrush(Qt::white);
    qDebug() << "MyDroppableItem initialized, setAcceptDrops(true).";
}

void MyDroppableItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
    qDebug() << "MyDroppableItem::dragEnterEvent called.";
    if (event->mimeData()->hasUrls() && event->mimeData()->urls().first().isLocalFile()) {
        m_dragOver = true;
        update(); // アイテムの見た目を更新(例: ハイライト)
        event->acceptProposedAction(); // ドロップを受け入れる意思を示す
        qDebug() << "Item accepted proposed action in dragEnterEvent.";
    } else {
        event->ignore(); // ドロップを受け入れない
        qDebug() << "Item ignored in dragEnterEvent.";
    }
}

void MyDroppableItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
{
    qDebug() << "MyDroppableItem::dragLeaveEvent called.";
    m_dragOver = false;
    update(); // ハイライトを解除
    QGraphicsRectItem::dragLeaveEvent(event); // 基底クラスを呼び出す
}

void MyDroppableItem::dropEvent(QGraphicsSceneDragDropEvent *event)
{
    qDebug() << "MyDroppableItem::dropEvent called.";
    m_dragOver = false; // ドラッグ状態をリセット
    if (event->mimeData()->hasUrls()) {
        QUrl url = event->mimeData()->urls().first();
        if (url.isLocalFile()) {
            QString filePath = url.toLocalFile();
            qDebug() << "Item dropped file:" << filePath;
            QPixmap newPixmap(filePath);
            if (!newPixmap.isNull()) {
                m_pixmap = newPixmap.scaled(boundingRect().size().toSize(),
                                            Qt::KeepAspectRatio,
                                            Qt::SmoothTransformation);
                update(); // 画像でアイテムの描画を更新
                event->acceptProposedAction(); // ドロップ処理が完了したことを示す
                qDebug() << "Item accepted proposed action in dropEvent.";
            } else {
                qWarning() << "Item failed to load image:" << filePath;
                event->ignore();
            }
        } else {
            event->ignore();
        }
    } else {
        event->ignore();
    }
}

void MyDroppableItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QGraphicsRectItem::paint(painter, option, widget); // 基本の四角形を描画

    if (m_dragOver) {
        // ドラッグ中のハイライト表示
        painter->setBrush(QBrush(QColor(0, 255, 0, 50))); // 半透明の緑
        painter->drawRect(boundingRect());
    }

    if (!m_pixmap.isNull()) {
        // ドロップされた画像を描画
        painter->drawPixmap(boundingRect().toRect(), m_pixmap, m_pixmap.rect());
    }
}

// main.cpp (QGraphicsView と QGraphicsScene はデフォルトで、アイテムを扱う)
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include "MyDroppableItem.h"

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

    QMainWindow mainWindow;
    mainWindow.setWindowTitle("Qt GraphicsItem Drop Example");
    mainWindow.resize(800, 600);

    QGraphicsScene *scene = new QGraphicsScene(&mainWindow);
    scene->setSceneRect(0, 0, 1000, 800);
    scene->setBackgroundBrush(Qt::lightGray);

    QGraphicsView *view = new QGraphicsView(scene, &mainWindow);
    view->setAcceptDrops(true); // ビュー全体でドロップを受け入れる設定(重要!)

    // シーンにドロップ可能なアイテムを追加
    MyDroppableItem *droppableItem1 = new MyDroppableItem(QRectF(100, 100, 150, 150));
    scene->addItem(droppableItem1);

    MyDroppableItem *droppableItem2 = new MyDroppableItem(QRectF(300, 200, 100, 100));
    scene->addItem(droppableItem2);

    mainWindow.setCentralWidget(view);
    mainWindow.show();

    return a.exec();
}
  • もし、QGraphicsViewQGraphicsScene でドロップイベントを処理しつつ、特定のアイテムにもドロップイベントを処理させたい場合は、上位レベルのイベントハンドラ内で event->acceptProposedAction() を呼び出した後に、必ず基底クラスのイベントハンドラを呼び出して、イベントが下位レベルに伝播するようにしてください。
  • 各レベルで event->accept()event->acceptProposedAction() を呼び出すと、それ以降のレベルへのイベント伝播は停止します。
  • イベントは、QGraphicsView から QGraphicsScene へ、そして最後にシーン内の最も上位の(Z-orderが最も高い)QGraphicsItem へと伝播します。
  • QGraphicsView, QGraphicsScene, QGraphicsItem のそれぞれで dragEnterEvent(), dragMoveEvent(), dropEvent() が存在し、イベントの伝播順序があります。