【Qt入門】QGraphicsView::dragMode徹底解説!マウス操作を自由自在に制御
このプロパティはQGraphicsView::DragMode
という列挙型(enum)で定義されており、以下の3つの主要な値を取ります。
-
- このモードでは、マウスドラッグ操作は特別な意味を持ちません。
- ビューのスクロールやアイテムの選択など、組み込みのドラッグ機能は無効になります。
- カスタムのドラッグ動作を実装したい場合に、このモードを選択してマウスイベントを自分で処理することが一般的です。
-
QGraphicsView::ScrollHandDrag
- このモードでは、マウスカーソルが手の形に変わり、ビューポート内をドラッグすると、ビューがスクロールします。
- まるで紙を掴んで動かすように、表示されているシーン全体をパン(移動)させることができます。
- 地図アプリケーションや大きな図面を表示するアプリケーションなどで、ビューの移動によく利用されます。
-
QGraphicsView::RubberBandDrag
- このモードでは、マウスをドラッグすると、矩形の「ラバーバンド」(破線で囲まれた領域)が表示されます。
- このラバーバンドは、通常、その中に含まれるアイテムを選択するために使用されます。
- Windowsのエクスプローラーなどでファイルを選択するときにマウスをドラッグするのと同様の動作です。
QGraphicsView
のrubberBandSelectionMode
プロパティと組み合わせて、アイテムの選択方法(部分的に重なっているだけでも選択するかなど)を細かく制御できます。
QGraphicsView
のsetDragMode()
メソッドを使って、これらのモードを設定します。
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 600); // シーンのサイズを設定
// シーンにいくつかのアイテムを追加
scene.addRect(100, 100, 50, 50, QPen(Qt::black), QBrush(Qt::red));
scene.addEllipse(200, 200, 70, 70, QPen(Qt::black), QBrush(Qt::blue));
QGraphicsView view(&scene);
// ドラグモードをScrollHandDragに設定
// これにより、マウスドラッグでビューがパン(移動)可能になります
view.setDragMode(QGraphicsView::ScrollHandDrag);
// または、アイテムを選択したい場合はRubberBandDragに設定
// view.setDragMode(QGraphicsView::RubberBandDrag);
view.setWindowTitle("QGraphicsView Drag Mode Example");
view.resize(600, 400);
view.show();
return a.exec();
}
上記の例では、view.setDragMode(QGraphicsView::ScrollHandDrag);
の行で、ビューのマウスドラッグ動作をスクロールモードに設定しています。これをコメントアウトして view.setDragMode(QGraphicsView::RubberBandDrag);
に変更すると、マウスドラッグでアイテム選択用のラバーバンドが表示されるようになります。
dragModeが全く機能しない、または期待通りに動かない
考えられる原因と解決策
-
間違ったDragMode値の使用
ScrollHandDrag
を期待しているのにNoDrag
が設定されていた、といった単純なミスも考えられます。QGraphicsView::ScrollHandDrag
やQGraphicsView::RubberBandDrag
のように、QGraphicsView::
スコープを付けているか確認してください。 -
マウスイベントをオーバーライドしている場合
QGraphicsView
を継承し、mousePressEvent
、mouseMoveEvent
、mouseReleaseEvent
などのマウスイベントを独自にオーバーライドしている場合、基底クラスのイベントハンドラを呼び出していないと、dragMode
の機能が無効になることがあります。 カスタムのドラッグ動作とQGraphicsView::dragMode
の機能を両立させたい場合は、基底クラスのメソッドを必ず呼び出すようにしてください。void MyGraphicsView::mousePressEvent(QMouseEvent *event) { // 独自の処理 // ... QGraphicsView::mousePressEvent(event); // 非常に重要! }
-
QGraphicsView::setInteractive(false) が呼び出されている
setInteractive(false)
を呼び出すと、QGraphicsView
のユーザーインタラクション(ドラッグ、選択、アイテムの移動など)がすべて無効になります。dragMode
が機能しない場合は、この設定がされていないか確認してください。デフォルトはtrue
です。view->setInteractive(true); // 明示的に有効にする
-
シーンのサイズ (sceneRect) が適切でない
ScrollHandDrag
モードでビューをパンしようとしても、シーンがビューポートよりも小さい場合、スクロールする余地がないため動きません。QGraphicsScene::setSceneRect()
を呼び出して、シーンの有効範囲を十分に大きく設定しているか確認してください。scene->setSceneRect(0, 0, 1000, 800); // 例えば、ビューポートよりも大きく設定
あるいは、シーン内のアイテムの
boundingRect()
の合計によってシーンの範囲が自動的に決定されるため、何もアイテムがない場合もスクロールする余地がありません。 -
setScene() の呼び出し忘れ
QGraphicsView
はQGraphicsScene
の内容を表示するものです。setScene()
を呼び出してシーンを設定していない場合、dragMode
は機能しません。QGraphicsScene *scene = new QGraphicsScene(this); QGraphicsView *view = new QGraphicsView(scene, this); // あるいは view->setScene(scene); view->setDragMode(QGraphicsView::ScrollHandDrag);
RubberBandDragでアイテムが選択されない
考えられる原因と解決策
-
シーンにアイテムがない
選択するアイテムが存在しない場合、当然ながら何も選択されません。シーンにテスト用のアイテムをいくつか追加して試してみてください。 -
rubberBandSelectionModeが適切でない
QGraphicsView::rubberBandSelectionMode
プロパティは、ラバーバンドとアイテムの重なり方によってアイテムが選択されるかを制御します。デフォルトはQt::IntersectsItemShape
ですが、場合によってはQt::ContainsItemShape
(アイテムが完全にラバーバンドに含まれる場合のみ選択)などに設定されていると、部分的にしか選択されません。view->setRubberBandSelectionMode(Qt::IntersectsItemShape); // デフォルトに戻すか確認
-
アイテムが選択可能 (ItemIsSelectable) でない
RubberBandDrag
モードは、アイテムを選択するために使用されます。選択対象のQGraphicsItem
がQGraphicsItem::ItemIsSelectable
フラグを持っていることを確認してください。myGraphicsItem->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
ScrollHandDragでスクロールバーが表示されない、またはスクロールできない
考えられる原因と解決策
-
スクロールバーポリシーがAlwaysOffになっている
QGraphicsView::setHorizontalScrollBarPolicy()
やQGraphicsView::setVerticalScrollBarPolicy()
がQt::ScrollBarAlwaysOff
に設定されている場合、スクロールバーは表示されず、ScrollHandDrag
でのパンも視覚的に分かりにくくなる可能性があります。view->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 必要に応じて表示 view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
-
シーンの範囲がビューポートより小さい
「1.dragMode
が全く機能しない」の項目で述べたように、sceneRect
がビューポートよりも小さい場合、スクロールバーが表示されなかったり、スクロールできなかったりします。シーンの範囲を適切に設定してください。
考えられる原因と解決策
-
カスタムのドラッグ操作とQtの組み込み機能の重複
QGraphicsView
をサブクラス化して独自のマウスイベントハンドリングを実装している場合、Qtの組み込みdragMode
と競合することがあります。例えば、マウスのドラッグでカスタムの描画を行いたい場合などです。 この場合、setDragMode(QGraphicsView::NoDrag)
を設定し、完全に自分でマウスイベントを処理するのが最も安全です。必要に応じて、mousePressEvent
で特定の条件が満たされたときにのみ、setDragMode(QGraphicsView::ScrollHandDrag)
を一時的に設定し、マウスボタンが離されたらsetDragMode(QGraphicsView::NoDrag)
に戻すといったテクニックも考えられます。 -
QGraphicsItem::ItemIsMovableフラグとQGraphicsView::dragMode
QGraphicsItem::ItemIsMovable
フラグを持つアイテムは、デフォルトで個別にドラッグして移動できます。QGraphicsView::ScrollHandDrag
モードの場合、ビュー全体がパンされますが、ビューに加えてアイテムも同時に動かしたいといった特殊なケースでは、QGraphicsView::dragMode
の標準動作とアイテムの個別のドラッグ動作が競合する可能性があります。 通常、これらの機能は排他的に使用されます。ビューをパンしたい場合はScrollHandDrag
、個々のアイテムを移動したい場合はItemIsMovable
フラグを設定し、dragMode
はNoDrag
にするのが一般的です。
QGraphicsView::dragMode
のトラブルシューティングのポイントは以下の通りです。
- 複数のドラッグ機能を同時に期待していないか確認する。 (
QGraphicsItem
の移動とビューのパンなど) - カスタムイベントハンドラを実装している場合は、基底クラスの呼び出し忘れがないか確認する。
- スクロールバーポリシーを確認する。
- アイテムの設定を確認する
ItemIsSelectable
、ItemIsMovable
。 - シーンとビューの設定を確認する
setScene()
、setSceneRect()
、setInteractive()
。
QGraphicsView::NoDrag
: ドラッグ操作が特に意味を持ちません。カスタムの動作を実装する場合に利用します。QGraphicsView::ScrollHandDrag
: マウスカーソルが手の形に変わり、ビューポートをドラッグするとシーンがパン(移動)します。QGraphicsView::RubberBandDrag
: マウスをドラッグすると、選択範囲を示すラバーバンドが表示され、その中にあるアイテムが選択されます。
それぞれのモードの基本的な使用例と、関連する設定、さらに一般的な応用例をコードで示します。
基本的な使用例 (Minimal Example)
この例では、QGraphicsView
、QGraphicsScene
、およびいくつかの基本的なQGraphicsItem
を作成し、異なるdragMode
を設定した場合の動作を示します。
main.cpp
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
#include <QVBoxLayout>
#include <QRadioButton>
#include <QGroupBox>
class GraphicsViewExample : public QMainWindow
{
Q_OBJECT
public:
GraphicsViewExample(QWidget *parent = nullptr) : QMainWindow(parent)
{
setWindowTitle("QGraphicsView Drag Mode Example");
resize(800, 600);
// QGraphicsSceneの作成と設定
scene = new QGraphicsScene(this);
// シーンの有効範囲を設定。これがないとScrollHandDragが効かない場合がある
scene->setSceneRect(-400, -300, 800, 600); // センターを(0,0)にするために負の値を設定
// シーンにアイテムを追加
QGraphicsRectItem *rect1 = new QGraphicsRectItem(-150, -100, 100, 80);
rect1->setBrush(Qt::red);
rect1->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene->addItem(rect1);
QGraphicsEllipseItem *ellipse1 = new QGraphicsEllipseItem(50, 50, 70, 70);
ellipse1->setBrush(Qt::blue);
ellipse1->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene->addItem(ellipse1);
QGraphicsTextItem *text1 = new QGraphicsTextItem("Hello Qt!");
text1->setPos(-50, -50);
text1->setDefaultTextColor(Qt::darkGreen);
text1->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene->addItem(text1);
// QGraphicsViewの作成
view = new QGraphicsView(scene, this);
// スクロールバーの表示ポリシーを設定
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
// ドラッグモード選択用のUI
QGroupBox *dragModeGroup = new QGroupBox("Drag Mode");
QVBoxLayout *groupLayout = new QVBoxLayout;
rbNoDrag = new QRadioButton("NoDrag");
rbScrollHandDrag = new QRadioButton("ScrollHandDrag");
rbRubberBandDrag = new QRadioButton("RubberBandDrag");
groupLayout->addWidget(rbNoDrag);
groupLayout->addWidget(rbScrollHandDrag);
groupLayout->addWidget(rbRubberBandDrag);
dragModeGroup->setLayout(groupLayout);
// シグナル・スロット接続
connect(rbNoDrag, &QRadioButton::toggled, this, &GraphicsViewExample::setNoDragMode);
connect(rbScrollHandDrag, &QRadioButton::toggled, this, &GraphicsViewExample::setScrollHandDragMode);
connect(rbRubberBandDrag, &QRadioButton::toggled, this, &GraphicsViewExample::setRubberBandDragMode);
// レイアウト設定
QWidget *centralWidget = new QWidget;
QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget);
mainLayout->addWidget(view, 1); // Viewを大きく表示
mainLayout->addWidget(dragModeGroup);
setCentralWidget(centralWidget);
// 初期ドラッグモードを設定
rbScrollHandDrag->setChecked(true); // デフォルトでScrollHandDragを有効にする
}
private slots:
void setNoDragMode(bool checked)
{
if (checked) {
view->setDragMode(QGraphicsView::NoDrag);
qDebug() << "Drag Mode: NoDrag";
}
}
void setScrollHandDragMode(bool checked)
{
if (checked) {
view->setDragMode(QGraphicsView::ScrollHandDrag);
qDebug() << "Drag Mode: ScrollHandDrag";
// ScrollHandDragではアイテムの移動はビューのパンと競合するため、ここではアイテムのMovableフラグは影響しない
}
}
void setRubberBandDragMode(bool checked)
{
if (checked) {
view->setDragMode(QGraphicsView::RubberBandDrag);
qDebug() << "Drag Mode: RubberBandDrag";
// RubberBandDragでアイテムを選択するには、ItemIsSelectableフラグが必要
// また、選択モードを制御する rubberBandSelectionMode も重要
view->setRubberBandSelectionMode(Qt::IntersectsItemShape); // デフォルト
// view->setRubberBandSelectionMode(Qt::ContainsItemShape); // アイテムが完全に含まれる場合のみ選択
}
}
private:
QGraphicsScene *scene;
QGraphicsView *view;
QRadioButton *rbNoDrag;
QRadioButton *rbScrollHandDrag;
QRadioButton *rbRubberBandDrag;
};
#include "main.moc" // mocファイルをインクルード
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GraphicsViewExample w;
w.show();
return a.exec();
}
解説
NoDrag
モードでは、ビューに対する特別なドラッグ動作は発生しません。アイテムを個別に移動したい場合は、このモードがデフォルトの動作として機能します。RubberBandDrag
モードを選択すると、ドラッグで矩形の選択範囲が表示され、その中にあるアイテムが選択されます。ItemIsSelectable
フラグが設定されていないアイテムは選択されません。ScrollHandDrag
モードを選択すると、マウスでドラッグするたびにビュー全体が移動します。この際、個々のアイテムは移動しません。- 3つのラジオボタンによって、
QGraphicsView::NoDrag
、QGraphicsView::ScrollHandDrag
、QGraphicsView::RubberBandDrag
のいずれかのドラッグモードを選択できます。 - これらのアイテムには
QGraphicsItem::ItemIsMovable
とQGraphicsItem::ItemIsSelectable
フラグが設定されており、個別に移動したり選択したりする準備ができています。 - このコードは、
QGraphicsScene
上に複数の図形とテキストアイテムを配置します。
QGraphicsItem::ItemIsMovable と QGraphicsView::dragMode の相互作用
この例では、QGraphicsItem
のItemIsMovable
フラグとQGraphicsView
のdragMode
がどのように影響し合うかを示します。
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QLabel>
#include <QVBoxLayout>
#include <QCheckBox>
class InteractiveView : public QMainWindow
{
Q_OBJECT
public:
InteractiveView(QWidget *parent = nullptr) : QMainWindow(parent)
{
setWindowTitle("DragMode & ItemIsMovable Example");
resize(700, 500);
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 600, 400); // シーンサイズ設定
// 動かせるアイテム
movableItem = new QGraphicsRectItem(50, 50, 100, 80);
movableItem->setBrush(Qt::green);
movableItem->setFlag(QGraphicsItem::ItemIsMovable); // 動かせるように設定
movableItem->setFlag(QGraphicsItem::ItemIsSelectable); // 選択可能にも設定
scene->addItem(movableItem);
// 動かせないアイテム
QGraphicsRectItem *staticItem = new QGraphicsRectItem(200, 150, 120, 90);
staticItem->setBrush(Qt::darkYellow);
scene->addItem(staticItem); // Movableフラグは設定しない
view = new QGraphicsView(scene, this);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
// UIコントロール
QGroupBox *controlsGroup = new QGroupBox("Controls");
QVBoxLayout *controlLayout = new QVBoxLayout;
QLabel *dragModeLabel = new QLabel("View Drag Mode:");
cbNoDrag = new QRadioButton("NoDrag (Default Item Drag)");
cbScrollHandDrag = new QRadioButton("ScrollHandDrag (Pan View)");
cbRubberBandDrag = new QRadioButton("RubberBandDrag (Select Items)");
controlLayout->addWidget(dragModeLabel);
controlLayout->addWidget(cbNoDrag);
controlLayout->addWidget(cbScrollHandDrag);
controlLayout->addWidget(cbRubberBandDrag);
QCheckBox *itemMovableCheckbox = new QCheckBox("ItemIsMovable on Green Rect");
itemMovableCheckbox->setChecked(true); // 最初はMovable
controlLayout->addWidget(itemMovableCheckbox);
controlsGroup->setLayout(controlLayout);
// シグナル・スロット接続
connect(cbNoDrag, &QRadioButton::toggled, this, [this](bool checked) {
if (checked) view->setDragMode(QGraphicsView::NoDrag);
updateStatus();
});
connect(cbScrollHandDrag, &QRadioButton::toggled, this, [this](bool checked) {
if (checked) view->setDragMode(QGraphicsView::ScrollHandDrag);
updateStatus();
});
connect(cbRubberBandDrag, &QRadioButton::toggled, this, [this](bool checked) {
if (checked) view->setDragMode(QGraphicsView::RubberBandDrag);
updateStatus();
});
connect(itemMovableCheckbox, &QCheckBox::toggled, this, [this](bool checked) {
movableItem->setFlag(QGraphicsItem::ItemIsMovable, checked);
updateStatus();
});
// 状態表示ラベル
statusLabel = new QLabel("Current Status:");
controlLayout->addWidget(statusLabel);
QWidget *centralWidget = new QWidget;
QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget);
mainLayout->addWidget(view, 1);
mainLayout->addWidget(controlsGroup);
setCentralWidget(centralWidget);
// 初期設定
cbNoDrag->setChecked(true);
updateStatus(); // 初期状態の表示
}
private slots:
void updateStatus()
{
QString statusText = "Current View Drag Mode: ";
switch (view->dragMode()) {
case QGraphicsView::NoDrag: statusText += "NoDrag"; break;
case QGraphicsView::ScrollHandDrag: statusText += "ScrollHandDrag"; break;
case QGraphicsView::RubberBandDrag: statusText += "RubberBandDrag"; break;
}
statusText += "\nGreen Rect ItemIsMovable: " + QString(movableItem->flags().testFlag(QGraphicsItem::ItemIsMovable) ? "True" : "False");
statusLabel->setText(statusText);
}
private:
QGraphicsScene *scene;
QGraphicsView *view;
QGraphicsRectItem *movableItem;
QRadioButton *cbNoDrag;
QRadioButton *cbScrollHandDrag;
QRadioButton *cbRubberBandDrag;
QLabel *statusLabel;
};
#include "main.moc" // mocファイルをインクルード
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
InteractiveView w;
w.show();
return a.exec();
}
解説
- QGraphicsView::RubberBandDragモードの場合
- ラバーバンド選択が行われます。緑色の矩形が
ItemIsSelectable
であるため、ラバーバンドで囲むと選択されます。ItemIsMovable
フラグは、このモードでのドラッグには影響しません。
- ラバーバンド選択が行われます。緑色の矩形が
- QGraphicsView::ScrollHandDragモードの場合
- ビュー全体がパンされます。緑色の矩形に
ItemIsMovable
が設定されていても、ビューのパンが優先され、アイテムは移動しません。
- ビュー全体がパンされます。緑色の矩形に
- QGraphicsView::NoDragモードの場合
- 緑色の矩形に
ItemIsMovable
が設定されていれば、直接ドラッグして移動できます。 ItemIsMovable
が解除されていれば、ビュー上でのドラッグは何も効果がありません(デフォルトのQt動作)。
- 緑色の矩形に
- この例では、緑色の矩形アイテムに
ItemIsMovable
フラグを動的に設定/解除できるチェックボックスを追加しました。
この例からわかるように、QGraphicsView::dragMode
はビューレベルでのマウスドラッグの振る舞いを制御し、QGraphicsItem::ItemIsMovable
はアイテムレベルでのドラッグ移動を制御します。これらは状況に応じて使い分けたり、競合を避けるように設計したりする必要があります。
QGraphicsView::NoDrag
モードで、独自のドラッグ操作を実装したい場合があります。この例では、マウスドラッグで自由に線を描画するカスタムビューを実装します。
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QGraphicsLineItem>
#include <QDebug> // for qDebug()
// QGraphicsViewを継承してカスタム動作を実装
class CustomDragView : public QGraphicsView
{
Q_OBJECT
public:
CustomDragView(QGraphicsScene *scene, QWidget *parent = nullptr)
: QGraphicsView(scene, parent),
currentLine(nullptr),
isDrawing(false)
{
// NoDragモードに設定し、独自のドラッグ動作を実装
setDragMode(QGraphicsView::NoDrag); // これが重要!
}
protected:
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton) {
isDrawing = true;
startPoint = mapToScene(event->pos()); // ビュー座標をシーン座標に変換
currentLine = new QGraphicsLineItem();
currentLine->setPen(QPen(Qt::blue, 2)); // 青い線
scene()->addItem(currentLine);
qDebug() << "Drawing started at:" << startPoint;
}
// ベースクラスのイベントハンドラを呼び出すのを忘れないこと
// ただし、この場合はQGraphicsView::NoDragなので、特にデフォルト動作は期待しない
QGraphicsView::mousePressEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) override
{
if (isDrawing && currentLine) {
QPointF endPoint = mapToScene(event->pos());
currentLine->setLine(QLineF(startPoint, endPoint));
}
QGraphicsView::mouseMoveEvent(event);
}
void mouseReleaseEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton && isDrawing) {
isDrawing = false;
if (currentLine) {
qDebug() << "Drawing finished. Line from" << startPoint << "to" << mapToScene(event->pos());
// 線が非常に短い場合は削除するなどの処理も考えられる
if (QLineF(startPoint, mapToScene(event->pos())).length() < 5.0) {
scene()->removeItem(currentLine);
delete currentLine;
currentLine = nullptr;
}
}
}
QGraphicsView::mouseReleaseEvent(event);
}
private:
QPointF startPoint;
QGraphicsLineItem *currentLine;
bool isDrawing;
};
// main.cpp (上記とほぼ同じ)
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(-200, -200, 400, 400); // シーンサイズを設定
CustomDragView view(&scene); // カスタムビューを使用
view.setWindowTitle("Custom Drag Drawing Example");
view.resize(500, 400);
view.show();
return a.exec();
}
#include "main.moc" // mocファイルをインクルード (CustomDragViewクラス定義の後に記述)
- 重要な注意点
各イベントハンドラ内で必ずQGraphicsView::mousePressEvent(event);
のように基底クラスの同名メソッドを呼び出すようにしてください。これにより、Qtの他のイベント処理メカニズム(例えば、QGraphicsItem
へのイベント伝播など)が正常に機能します。この例では線描画のみのため、NoDrag
モードでは基底クラス呼び出しは必須ではありませんが、他のQGraphicsItem
がシーンに存在する場合や、将来的に機能を拡張する場合には非常に重要です。 mouseReleaseEvent
でドラッグ終了を処理します。mouseMoveEvent
でマウスの現在の位置まで線を更新します。mousePressEvent
でドラッグ開始点を記録し、QGraphicsLineItem
を作成します。- コンストラクタで
setDragMode(QGraphicsView::NoDrag)
を設定することが重要です。これにより、Qtの組み込みドラッグ機能が抑制され、独自のマウスイベントハンドラが機能するようになります。 CustomDragView
クラスはQGraphicsView
を継承しています。
QGraphicsView をサブクラス化し、マウスイベントをオーバーライドする
これが最も一般的で柔軟な方法です。QGraphicsView
のmousePressEvent()
, mouseMoveEvent()
, mouseReleaseEvent()
といった仮想関数をオーバーライドして、独自のマウスドラッグロジックを実装します。
特徴
- カスタムのジェスチャー、マルチタッチドラッグ、特定のキーが押されている間の特殊なドラッグなど、高度なインタラクションを実装できます。
- マウスの位置をビュー座標からシーン座標に変換する
mapToScene()
関数が非常に役立ちます。 QGraphicsView::NoDrag
モードを設定し、Qtのデフォルトのドラッグ動作を無効にした上で、完全に自由にマウスイベントを処理できます。
利点
- ビュー固有のカスタムロジックを直接組み込める。
- 最も細かく動作を制御できる。
欠点
- 基底クラスのイベントハンドラを適切に呼び出さないと、予期せぬ副作用が発生する可能性がある(例:
QGraphicsItem
へのイベント伝播が止まる)。 - コード量が増える傾向にある。
ScrollHandDrag
やRubberBandDrag
のような既存の便利な機能を自分で再実装する必要がある場合がある。
// CustomDragView.h (抜粋)
class CustomDragView : public QGraphicsView
{
Q_OBJECT
public:
CustomDragView(QGraphicsScene *scene, QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
QPointF startPoint;
QGraphicsLineItem *currentLine;
bool isDrawing;
};
// CustomDragView.cpp (抜粋)
CustomDragView::CustomDragView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent), currentLine(nullptr), isDrawing(false)
{
setDragMode(QGraphicsView::NoDrag); // カスタムイベント処理のためにデフォルトのドラッグを無効化
}
void CustomDragView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
isDrawing = true;
startPoint = mapToScene(event->pos());
currentLine = new QGraphicsLineItem();
currentLine->setPen(QPen(Qt::blue, 2));
scene()->addItem(currentLine);
}
// QGraphicsView::mousePressEvent(event); // 必要に応じて呼び出す
}
// ... mouseMoveEvent, mouseReleaseEvent も同様に実装
QGraphicsScene をサブクラス化し、シーンイベントをオーバーライドする
QGraphicsScene
も、QGraphicsSceneMouseEvent
などのイベントを処理するための仮想関数を持っています。QGraphicsView
の代わりにQGraphicsScene
をサブクラス化してこれらのイベントをオーバーライドすることで、シーンレベルでのドラッグロジックを実装できます。
特徴
- シーン内のアイテムの選択、衝突検出など、シーンの状態に基づいたドラッグ動作に適しています。
- イベントはすでにビュー座標からシーン座標に変換されています。
利点
- 複数のビューが同じシーンを使用している場合、一貫したドラッグ動作を提供できる。
- アイテムの移動や操作に焦点を当てたロジックを実装しやすい。
欠点
QGraphicsView::setDragMode()
の機能とは独立して動作するため、競合しないように注意が必要。- ビュー自体のパンやズームといった、ビューポートに直接影響を与える動作を実装するのはより複雑になる。
コード例 (概念)
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsRectItem>
#include <QDebug>
class MyGraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
MyGraphicsScene(QObject *parent = nullptr) : QGraphicsScene(parent) {}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
{
qDebug() << "Scene: Mouse Press at" << event->scenePos();
// アイテムをクリックした場合のカスタム処理
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (item) {
qDebug() << "Item clicked:" << item;
// アイテムのドラッグを開始するロジックなど
}
QGraphicsScene::mousePressEvent(event); // 基底クラスを呼び出す
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override
{
qDebug() << "Scene: Mouse Move to" << event->scenePos();
// ドラッグ中の描画更新やアイテム移動のロジックなど
QGraphicsScene::mouseMoveEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
{
qDebug() << "Scene: Mouse Release at" << event->scenePos();
QGraphicsScene::mouseReleaseEvent(event);
}
};
// main関数では MyGraphicsScene をインスタンス化して QGraphicsView にセット
// QGraphicsView->setDragMode(QGraphicsView::NoDrag); が推奨される
イベントフィルターを使用する
QObject::installEventFilter()
を使用し、QGraphicsView
(またはそのviewport()
)にイベントフィルターをインストールすることで、ビューのイベントをキャッチして処理できます。これは、既存のクラスをサブクラス化せずにカスタム動作を追加したい場合に便利です。
特徴
- 特定のイベントのみを横取りして処理し、他のイベントはQtのデフォルト処理に任せるといった使い方ができる。
- 既存の
QGraphicsView
インスタンスに動的に動作を追加できる。
利点
- 既存のクラス階層を変更せずに機能を追加できる。
- コードの凝集度が高まり、カスタムロジックを独立したクラスにカプセル化できる。
欠点
- イベントオブジェクトの型キャストが必要になる。
- 誤ってイベントを
return true
でフィルタリングしてしまうと、Qtのデフォルト動作が完全に停止することがある。 - イベント処理の順序や伝播を理解する必要がある。
コード例
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QEvent>
#include <QDebug>
class CustomDragEventFilter : public QObject
{
Q_OBJECT
public:
CustomDragEventFilter(QObject *parent = nullptr) : QObject(parent), isDragging(false) {}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
QGraphicsView *view = qobject_cast<QGraphicsView*>(watched);
if (!view) return false; // QGraphicsViewでなければ処理しない
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
isDragging = true;
lastMousePos = mouseEvent->pos();
qDebug() << "Filter: Mouse Press at" << lastMousePos;
// ここでカスタムドラッグ処理の開始ロジック
// 例: view->setDragMode(QGraphicsView::NoDrag); が設定されている前提
return true; // イベントを消費し、ビューのデフォルト処理を停止
}
} else if (event->type() == QEvent::MouseMove) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (isDragging) {
QPoint delta = mouseEvent->pos() - lastMousePos;
// ビューを移動させる例(ScrollHandDrag相当の処理)
view->horizontalScrollBar()->setValue(view->horizontalScrollBar()->value() - delta.x());
view->verticalScrollBar()->setValue(view->verticalScrollBar()->value() - delta.y());
lastMousePos = mouseEvent->pos();
qDebug() << "Filter: Mouse Move. Delta:" << delta;
return true; // イベントを消費
}
} else if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton && isDragging) {
isDragging = false;
qDebug() << "Filter: Mouse Release";
// カスタムドラッグ処理の終了ロジック
return true; // イベントを消費
}
}
return QObject::eventFilter(watched, event); // 他のイベントは基底クラスに任せる
}
private:
bool isDragging;
QPoint lastMousePos;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(-400, -300, 800, 600);
scene.addRect(0, 0, 50, 50, QPen(Qt::black), QBrush(Qt::green));
QGraphicsView view(&scene);
view.setWindowTitle("Event Filter Drag Example");
view.resize(600, 400);
// イベントフィルターをインストール
CustomDragEventFilter *filter = new CustomDragEventFilter(&view);
view.installEventFilter(filter); // viewそのものにインストール
// またはビューポートにインストールすることもできる
// view.viewport()->installEventFilter(filter);
// デフォルトのドラッグモードは無効にしておくか、NoDragにする
view.setDragMode(QGraphicsView::NoDrag);
view.show();
return a.exec();
}
#include "main.moc" // mocファイルをインクルード (CustomDragEventFilterクラス定義の後に記述)
解説
return true
でイベントを消費すると、それ以上このイベントは伝播しません。return false
だと、イベントは次のイベントフィルターや、最終的にはQGraphicsView
のデフォルトイベントハンドラに渡されます。この例では、カスタムドラッグ処理を行いたいためtrue
を返しています。- マウスが押されたときにドラッグを開始し、移動中にスクロールバーを操作してビューをパンするロジックを実装しています。
eventFilter
内で、QEvent::MouseButtonPress
、QEvent::MouseMove
、QEvent::MouseButtonRelease
イベントを監視します。CustomDragEventFilter
クラスはQObject
を継承し、eventFilter
メソッドをオーバーライドします。
これはQGraphicsView::dragMode
とは少し異なりますが、アイテムやデータのドラッグ&ドロップ操作全般に関連する代替手段です。例えば、リストビューからQGraphicsView
に画像をドラッグ&ドロップして配置したい場合などに使用します。
特徴
- ドロップターゲット側(
QGraphicsView
やQGraphicsScene
)でdragEnterEvent
、dragMoveEvent
、dropEvent
をオーバーライドしてドロップを受け入れます。 QDrag
オブジェクトを使用してドラッグ操作を開始します。QMimeData
を使用して、ドラッグされるデータを表現します。
利点
- 標準的なD&Dインタフェースを提供できる。
- アプリケーション内外でのデータ転送に非常に強力。
欠点
QGraphicsItem
のカスタムドラッグ&ドロップを実装する場合は、アイテムのイベントハンドラをオーバーライドする必要がある。- 純粋なビューのパンや選択には不向き。
QGraphicsView::dragMode
はシンプルで一般的なユースケースに最適ですが、より高度な制御が必要な場合は、以下の代替手段を検討してください。
- アプリケーション間またはUI要素間のデータ転送の場合
Qtの標準D&Dフレームワークを使用。 - 既存のクラスに動的に機能を追加する場合
イベントフィルターを使用。 - シーン内のアイテム操作に焦点を当てる場合
QGraphicsScene
をサブクラス化し、シーンイベントをオーバーライド。 - 最も柔軟性が必要な場合
QGraphicsView
をサブクラス化し、マウスイベントをオーバーライド。