Qtプログラミング:QGraphicsView::dragMoveEventを使ったドラッグ&ドロップの具体例
この関数の役割と動作
QGraphicsView
は QGraphicsScene
の内容を表示するためのウィジェットです。ユーザーが何かをドラッグしている最中に、そのドラッグ操作が QGraphicsView
のビューポート内を移動するたびに、この dragMoveEvent
が呼び出されます。
具体的には、以下の目的でこの関数をオーバーライド(再実装)します。
-
- ユーザーがドラッグしているデータ(MIMEデータ)を、現在のビューポートの位置にドロップできるかどうかを判断します。
- ドロップが可能であれば、イベントの
acceptProposedAction()
を呼び出して、ドロップを受け入れることをQtに伝えます。これにより、通常、マウスカーソルの形状が変更され、ドロップが許可されていることを視覚的にユーザーに示します(例:ドロップ可能な場所では「+」マークが表示されるなど)。 - ドロップが不可能な場合は、イベントの
ignore()
を呼び出し、ドロップを受け入れないことを示します。
-
ドラッグ中の視覚的な変更
- ドラッグ中のアイテムが特定の領域に入ったときに、その領域のハイライト表示を変更したり、ドロップされた場合のプレビューを表示したりするなど、ドラッグ中の視覚的なフィードバックを提供するために使用されます。
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()
を呼び出している場合、QGraphicsView
のdragMoveEvent
に到達しないことがあります。 - ドラッグ操作が開始されていない
ドラッグ&ドロップは、通常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
内で、ドラッグ中の位置に基づいてQGraphicsScene
やQGraphicsView
の表示を更新したい場合、適切なタイミングで再描画をトリガーする必要があります(例:viewport()->update()
やscene()->update()
)。
トラブルシューティング
- プロファイラを使用して、
dragMoveEvent
内でパフォーマンスのボトルネックとなっている処理がないか特定し、最適化してください。 - 複雑な視覚的フィードバックが必要な場合は、
QGraphicsScene
に仮のアイテムを追加したり、QGraphicsItem
のprepareGeometryChange()
を利用したりするなど、Graphics Viewフレームワークの最適化機能を利用することを検討してください。 dragMoveEvent
の中で、描画を更新する必要がある部分に対してupdate()
を呼び出していることを確認してください。
QGraphicsItem へのドロップが機能しない
原因
- アイテムのヒットテストの問題
マウスカーソルが実際にドロップを受け入れたいQGraphicsItem
の上に乗っていることを、QGraphicsView::mapToScene()
やQGraphicsScene::itemAt()
などを使って正確に判断する必要があります。 - QGraphicsScene がイベントを消費している
QGraphicsScene
をオーバーライドしてdragMoveEvent
を実装している場合、そのイベントがQGraphicsItem
に到達しない可能性があります。 - QGraphicsView がイベントを消費している
デフォルトでは、QGraphicsView
はビューポート全体に対するドラッグイベントを処理します。特定のQGraphicsItem
がドロップを受け入れるようにするには、QGraphicsItem
自体にもItemIsSelectable
やItemIsMovable
といったフラグに加え、ItemAcceptsDrops
フラグを設定し、dragMoveEvent
(QGraphicsItem::dragMoveEvent) をオーバーライドする必要があります。
トラブルシューティング
QGraphicsScene
をオーバーライドしている場合、dragMoveEvent
の中でQGraphicsScene::dragMoveEvent(event);
を呼び出して、イベントがアイテムに伝播されるようにする必要があります。QGraphicsView
のdragMoveEvent
内で、event->scenePos()
を使用してマウスカーソルのシーン座標を取得し、scene()->itemAt(event->scenePos(), transform())
などを使って、その位置にあるQGraphicsItem
を特定します。特定したアイテムにイベントを転送するか、そのアイテムがドロップを受け入れられるかどうかを判断するロジックを追加します。QGraphicsItem
のカスタムクラスでdragMoveEvent
をオーバーライドし、そこでevent->acceptProposedAction()
を呼び出すようにします。- ドロップを受け入れたい
QGraphicsItem
のコンストラクタでsetFlags(QGraphicsItem::ItemAcceptsDrops);
を設定していることを確認してください。
ドラッグ&ドロップの座標変換の問題
原因
- ビューポート座標とシーン座標の混同
QGraphicsView
のイベントはビューポート座標(ウィジェットのピクセル座標)で提供されますが、QGraphicsScene
やQGraphicsItem
の位置はシーン座標で扱われます。これらの変換を正しく行わないと、ドロップ位置がずれるなどの問題が発生します。
トラブルシューティング
- 常に適切な座標変換関数(
mapToScene
,mapFromScene
,mapToItem
,mapFromItem
など)を使用していることを確認してください。 QGraphicsScene
のイベントハンドラ(例:QGraphicsSceneDragDropEvent
)では、scenePos()
がシーン座標を返します。QDragMoveEvent
のpos()
はビューポート座標です。これをシーン座標に変換するにはmapToScene(event->pos())
を使用します。
macOS でのドラッグ&ドロップの問題
原因
- macOSでは、Qtのドラッグ&ドロップの挙動が他のOSと若干異なる場合があります。特に、特定のMIMEタイプや、ファイルパスの扱いにおいて問題が発生することが報告されています。
トラブルシューティング
- ファイルパスの場合、
event->mimeData()->urls()
を使用してQUrl
のリストを取得し、toLocalFile()
でローカルファイルパスに変換します。 - macOS固有のMIMEタイプ(例:
text/uri-list
ではなくapplication/x-vnd.qt.url
やtext/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
のインスタンスに対してテキストをドラッグすると、そのアイテムの背景色が緑色に変わり、ドロップを受け入れられることを示します。ドラッグがアイテムから離れると元の色に戻ります。
この例では、QGraphicsView
の dragMoveEvent
を使って、ドラッグ中にビューポート上に仮のプレビュー(ドラッグ中のアイテムがドロップされた場合にどこに配置されるかを示す)を描画する方法を示します。
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()
をオーバーライドすることで、シーン全体でのドラッグ操作を処理できます。
特徴
- 座標系の違い
QGraphicsView
のdragMoveEvent
がビューポート座標(ウィジェットのピクセル座標)でイベントを受け取るのに対し、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
自体またはQGraphicsItem
でdragMoveEvent()
をオーバーライドするのが一般的です。