QtプログラミングTips:QGraphicsScene::mouseMoveEvent()を使いこなすための実践的テクニック

2025-05-27

以下に詳しく説明します。

QGraphicsScene::mouseMoveEvent()とは?

  • 引数
    この関数はQGraphicsSceneMouseEvent *eventという引数を受け取ります。QGraphicsSceneMouseEventオブジェクトには、マウスの位置、ボタンの状態、修飾キーの状態など、マウスイベントに関する情報が含まれています。
  • 仮想関数
    mouseMoveEvent()は仮想関数なので、QGraphicsSceneを継承した独自のクラスでオーバーライドすることで、マウス移動時の動作を定義できます。
  • イベント処理
    QGraphicsSceneは、グラフィカルアイテム(QGraphicsItem)を管理するキャンバスのようなものです。マウスがシーン内で移動すると、QGraphicsScenemouseMoveEvent()というイベントを生成します。

QGraphicsSceneMouseEventオブジェクトから取得できる情報

  • 修飾キーの状態
    • event->modifiers(): 押されている修飾キー(Shift, Ctrl, Altなど)のビットマスクを取得します。
  • マウスボタンの状態
    • event->buttons(): 押されているマウスボタンのビットマスクを取得します。
  • マウスの位置
    • event->scenePos(): シーン座標系におけるマウスの位置を取得します。
    • event->screenPos(): スクリーン座標系におけるマウスの位置を取得します。
    • event->lastScenePos(): 直前のシーン座標系におけるマウスの位置を取得します。
    • event->lastScreenPos(): 直前のスクリーン座標系におけるマウスの位置を取得します。

mouseMoveEvent()のオーバーライド例

#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

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

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
        // マウスのシーン座標を取得
        QPointF scenePos = event->scenePos();

        // デバッグ出力
        qDebug() << "Mouse moved to:" << scenePos;

        // 任意の処理を記述
        // 例: マウスの位置に基づいてアイテムを移動させるなど
        // ...

        // デフォルトの処理を呼び出す(必要に応じて)
        QGraphicsScene::mouseMoveEvent(event);
    }
};

説明

  1. MyGraphicsSceneクラスはQGraphicsSceneを継承しています。
  2. mouseMoveEvent()関数をオーバーライドし、引数としてQGraphicsSceneMouseEvent *eventを受け取ります。
  3. event->scenePos()を使用してマウスのシーン座標を取得し、qDebug()で出力します。
  4. コメント部分に、マウス移動時に実行したい任意の処理を記述します。
  5. QGraphicsScene::mouseMoveEvent(event)を呼び出すことで、デフォルトの処理も実行できます(必要に応じて)。

使用例

このオーバーライドされたMyGraphicsSceneQGraphicsViewに設定することで、シーン内でマウスが移動した際に、コンソールにマウスの座標が出力されます。



一般的なエラーとトラブルシューティング

    • 原因
      • QGraphicsSceneが正しくQGraphicsViewに設定されていない。
      • アイテムがマウスイベントを受け取れる状態になっていない(QGraphicsItem::setAcceptedMouseButtons()QGraphicsItem::setFlag(QGraphicsItem::ItemIsMovable)などの設定)。
      • アイテムが他のアイテムによって隠されている。
      • QGraphicsViewviewportUpdateModeが原因でイベントが適切に処理されない。
    • トラブルシューティング
      • QGraphicsViewに正しいQGraphicsSceneが設定されているか確認します。
      • アイテムのacceptedMouseButtonsflagsをチェックし、マウスイベントを受け取る設定になっているか確認します。
      • アイテムのZ値を調整し、重ね順を確認します。
      • QGraphicsView::viewportUpdateModeを調整して、適切な更新モードを選択します。例えば、QGraphicsView::FullViewportUpdateなど。
      • QGraphicsViewに対してsetMouseTracking(true)が有効になっているか確認します。この設定がfalseの場合、マウスボタンが押された状態で移動しない限り、mouseMoveEvent()は発生しません。
  1. マウス座標が期待通りに取得できない

    • 原因
      • シーン座標系とビュー座標系(またはスクリーン座標系)の混同。
      • アイテムの座標系とシーン座標系の変換ミス。
    • トラブルシューティング
      • event->scenePos()を使用してシーン座標系を取得し、event->screenPos()を使用してスクリーン座標系を取得します。
      • アイテムの座標系をシーン座標系に変換するには、QGraphicsItem::mapToScene()を使用します。
      • QGraphicsView::mapToScene()を利用してビュー座標をシーン座標に変換します。
      • 座標の変換をするときは、変換の元になる座標がどの座標系かを明確に意識してください。
  2. イベント処理が遅い

    • 原因
      • mouseMoveEvent()内で複雑な計算や描画処理を行っている。
      • 大量のアイテムを更新している。
    • トラブルシューティング
      • 処理を最適化し、不要な計算を避けます。
      • アイテムの更新範囲を最小限に抑えます。
      • スレッド処理を利用して、重い処理をバックグラウンドで実行します。
      • タイマーを利用して、イベント処理の頻度を調整します。
  3. アイテムの移動がスムーズでない

    • 原因
      • アイテムの移動処理が不適切。
      • ビューの更新が遅い。
    • トラブルシューティング
      • アイテムの移動処理を最適化し、QGraphicsItem::setPos()を適切に使用します。
      • QGraphicsView::setViewportUpdateMode()を調整し、適切な更新モードを選択します。
      • QGraphicsView::setRenderHints()を調整し、レンダリング品質を最適化します。
      • アニメーションを利用して、スムーズな移動を実現します。
  4. マウスボタンの状態が正しく取得できない

    • 原因
      • ビットマスクの扱いを間違えている。
      • 複数ボタンの同時押しに対応していない。
    • トラブルシューティング
      • event->buttons()の戻り値をビットマスクとして扱い、Qt::LeftButtonQt::RightButtonなどの定数と論理演算子(&)を使用してボタンの状態を確認します。
      • 複数ボタンの同時押しに対応する場合、ビットマスクを適切に処理します。

デバッグのヒント

  • 単純なテストプロジェクトを作成して、問題の再現性を確認します。
  • グラフィックビューのデバッグモードを有効にする。
  • ブレークポイントを設定し、mouseMoveEvent()の実行をステップ実行して、変数の値を確認します。
  • qDebug()を使用して、マウス座標、ボタンの状態、修飾キーの状態などを出力し、イベントの発生状況を確認します。


例1: マウスの動きに合わせて円を描画する

この例では、マウスの動きに合わせて、シーン上に小さな円を描画します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsEllipseItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

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

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
        // マウスのシーン座標を取得
        QPointF scenePos = event->scenePos();

        // 円を作成し、シーンに追加
        QGraphicsEllipseItem *circle = new QGraphicsEllipseItem(scenePos.x() - 5, scenePos.y() - 5, 10, 10);
        circle->setBrush(Qt::red);
        addItem(circle);

        // デフォルトの処理を呼び出す
        QGraphicsScene::mouseMoveEvent(event);
    }
};

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

    MyGraphicsScene scene;
    QGraphicsView view(&scene);

    view.show();

    return app.exec();
}

説明

  1. MyGraphicsSceneクラスはQGraphicsSceneを継承しています。
  2. mouseMoveEvent()をオーバーライドし、マウスのシーン座標を取得します。
  3. QGraphicsEllipseItemを作成し、マウスの座標を中心とする小さな赤い円を描画します。
  4. addItem()を使用して、円をシーンに追加します。
  5. main()関数で、MyGraphicsSceneQGraphicsViewを作成し、ビューを表示します。

例2: マウスの動きに合わせてアイテムを移動させる

この例では、シーン上にアイテムを配置し、マウスでドラッグして移動できるようにします。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

class MyGraphicsScene : public QGraphicsScene {
public:
    MyGraphicsScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        // 長方形のアイテムを作成し、シーンに追加
        rectItem = new QGraphicsRectItem(0, 0, 50, 50);
        rectItem->setFlag(QGraphicsItem::ItemIsMovable); // 移動可能にする
        addItem(rectItem);
    }

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
        // デフォルトの処理を呼び出す
        QGraphicsScene::mouseMoveEvent(event);
    }

private:
    QGraphicsRectItem *rectItem;
};

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

    MyGraphicsScene scene;
    QGraphicsView view(&scene);

    view.show();

    return app.exec();
}

説明

  1. MyGraphicsSceneクラスはQGraphicsSceneを継承しています。
  2. コンストラクタでQGraphicsRectItemを作成し、setFlag(QGraphicsItem::ItemIsMovable)を設定して移動可能にします。
  3. mouseMoveEvent()をオーバーライドし、デフォルトの処理を呼び出すだけです。ItemIsMovableフラグが設定されているため、Qtが自動的にアイテムの移動を処理します。
  4. main()関数で、MyGraphicsSceneQGraphicsViewを作成し、ビューを表示します。

例3: マウスの動きに合わせて線の描画

この例では、マウスの動きに合わせて、線を描画します。マウスが押された場所から離された場所まで線が引かれます。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsLineItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

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

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
        startPos = event->scenePos();
        QGraphicsScene::mousePressEvent(event);
    }

    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
        endPos = event->scenePos();
        QGraphicsLineItem *line = new QGraphicsLineItem(QLineF(startPos, endPos));
        addItem(line);
        QGraphicsScene::mouseReleaseEvent(event);
    }

private:
    QPointF startPos;
    QPointF endPos;
};

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

    MyGraphicsScene scene;
    QGraphicsView view(&scene);

    view.show();

    return app.exec();
}
  1. MyGraphicsSceneを継承します。
  2. mousePressEvent()をオーバーライドし、クリックされた場所をstartPosに記録します。
  3. mouseReleaseEvent()をオーバーライドし、離された場所をendPosに記録し、QGraphicsLineItemで線を引きます。
  4. main()関数で表示します。


QGraphicsItemのイベント処理をオーバーライドする

QGraphicsSceneで直接mouseMoveEvent()を処理する代わりに、QGraphicsItemを継承した独自のアイテムクラスでイベント処理をオーバーライドできます。これにより、特定のアイテムに対するマウスイベントをより細かく制御できます。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

class MyRectItem : public QGraphicsRectItem {
public:
    MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(x, y, width, height, parent) {}

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
        // アイテム内でのマウスの動きを処理
        qDebug() << "Item mouse moved to:" << event->pos();
        QGraphicsRectItem::mouseMoveEvent(event); // デフォルトの処理
    }
};

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    MyRectItem *rect = new MyRectItem(0, 0, 100, 50);
    rect->setFlag(QGraphicsItem::ItemIsMovable); // 移動可能にする
    scene.addItem(rect);

    view.show();
    return app.exec();
}

説明

  • アイテムごとに個別のマウス処理を実装できるため、複雑なインタラクションを管理しやすくなります。
  • この例では、アイテム内でマウスが動いたときにデバッグ出力を行います。
  • MyRectItemクラスはQGraphicsRectItemを継承し、mouseMoveEvent()をオーバーライドしています。

QGraphicsItem::ItemIsMovableフラグとQGraphicsItem::ItemIsSelectableフラグの利用

単純なドラッグ&ドロップや選択操作の場合、mouseMoveEvent()を直接処理する代わりに、QGraphicsItemのフラグを利用できます。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    QGraphicsRectItem *rect = new QGraphicsRectItem(0, 0, 100, 50);
    rect->setFlag(QGraphicsItem::ItemIsMovable); // 移動可能にする
    rect->setFlag(QGraphicsItem::ItemIsSelectable); // 選択可能にする
    scene.addItem(rect);

    view.show();
    return app.exec();
}

説明

  • これらのフラグを使用することで、基本的な操作を簡単に実装できます。
  • ItemIsSelectableフラグを設定すると、アイテムをクリックして選択できます。
  • ItemIsMovableフラグを設定すると、アイテムをドラッグして移動できます。

QGraphicsItem::grabMouse()とQGraphicsItem::ungrabMouse()の利用

特定のアイテムがマウスイベントを独占的に処理する必要がある場合、grabMouse()ungrabMouse()を使用できます。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

class MyRectItem : public QGraphicsRectItem {
public:
    MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(x, y, width, height, parent) {}

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
        grabMouse(); // マウスイベントを独占的に取得
        QGraphicsRectItem::mousePressEvent(event);
    }

    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
        ungrabMouse(); // マウスイベントの独占を解除
        QGraphicsRectItem::mouseReleaseEvent(event);
    }

    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
        // マウスイベントを独占的に処理
        qDebug() << "Grabbed mouse moved:" << event->pos();
        QGraphicsRectItem::mouseMoveEvent(event);
    }
};

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    MyRectItem *rect = new MyRectItem(0, 0, 100, 50);
    scene.addItem(rect);

    view.show();
    return app.exec();
}

説明

  • これにより、特定のアイテムがマウス操作を完全に制御できます。
  • ungrabMouse()を呼び出すと、独占を解除します。
  • grabMouse()を呼び出すと、アイテムがマウスイベントを独占的に取得します。

QTimerとQPointの利用

マウスの動きを間接的に監視し、タイマーを用いて位置の変化を定期的に処理することもできます。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QTimer>
#include <QPoint>
#include <QDebug>

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    QGraphicsRectItem *rect = new QGraphicsRectItem(0, 0, 100, 50);
    scene.addItem(rect);

    QPoint lastMousePos;
    QTimer timer;

    QObject::connect(&timer, &QTimer::timeout, [&]() {
        QPoint currentMousePos = view.mapFromGlobal(QCursor::pos());
        QPointF scenePos = view.mapToScene(currentMousePos);

        if (lastMousePos != currentMousePos) {
            qDebug() << "Mouse moved (timer):" << scenePos;
            lastMousePos = currentMousePos;
        }
    });

    timer.start(100); // 100ミリ秒ごとにチェック

    view.show();
    return app.exec();
}
  • 直接的なイベント処理ではなく、間接的な監視方法です。
  • 位置が変化した場合に処理を実行します。
  • QCursor::pos()でグローバル座標を取得し、view.mapFromGlobal()view.mapToScene()でシーン座標に変換します。
  • QTimerを使用して、一定間隔でマウスの位置を監視します。