Qt開発者必見: QGraphicsView::mouseMoveEventのエラーと解決策

2025-05-27

これは、QtフレームワークのQGraphicsViewクラスで提供される**仮想関数(virtual function)**です。QGraphicsViewは、QGraphicsSceneの内容を表示するためのウィジェットであり、グラフィックスアイテム(図形、画像など)を扱う際に使用されます。

mouseMoveEventは、マウスカーソルがQGraphicsViewウィジェット上を移動する際に発生するイベントを処理するためにオーバーライド(再実装)する関数です。

イベントの発生条件

通常、mouseMoveEventは以下の条件で発生します。

  1. setMouseTracking(true)が設定されている場合
    マウスボタンが押されていなくても、マウスカーソルがビュー上を移動するたびにイベントが発生します。これが最も一般的な使い方です。
  2. いずれかのマウスボタンが押されている場合
    マウスボタンが押された状態でマウスをドラッグすると、setMouseTracking(true)が設定されていなくてもイベントが発生します。

関数の引数

  • QMouseEvent *event: このポインタは、マウスイベントに関する情報を含むQMouseEventオブジェクトを指します。このオブジェクトから、以下の重要な情報を取得できます。
    • event->pos(): イベントが発生したビューのローカル座標(ウィジェット内での座標)をQPoint型で取得します。
    • event->scenePos() (非推奨): QGraphicsViewではなくQGraphicsSceneMouseEventで利用されますが、ビューからシーン座標に変換したい場合はmapToScene()を使用します。
    • event->buttons(): イベント発生時に押されていたマウスボタン(複数可)を取得します。例えば、Qt::LeftButtonQt::RightButtonなどをチェックできます。
    • event->modifiers(): イベント発生時に押されていた修飾キー(Ctrl, Alt, Shiftなど)を取得します。

どのような時にmouseMoveEventをオーバーライドするか?

QGraphicsView::mouseMoveEventをオーバーライドする主な目的は、以下のようなカスタム動作を実装することです。

  • カスタムインタラクション
    ユーザーがマウスを動かすことで、ビューの表示を動的に変更したり、特定のアイテムにホバー効果を与えたりする場合。
  • ドラッグ操作の実装
    マウスボタンが押された状態でドラッグする際に、ビューのスクロール、ズーム、アイテムの移動、図形の描画(例: 四角形の選択範囲)などの操作を実装する場合。
  • リアルタイムなマウス位置の追跡
    マウスカーソルの現在位置をビュー内に表示したり、特定の領域にカーソルが入ったことを検出したりする場合。

実装の注意点

mouseMoveEventをカスタムクラスでオーバーライドする際は、以下の点に注意してください。

  1. setMouseTracking(true)の設定
    マウスボタンが押されていない状態でもmouseMoveEventを常に受け取りたい場合は、QGraphicsViewのインスタンスに対してsetMouseTracking(true)を呼び出す必要があります。デフォルトではfalse(無効)です。
  2. QGraphicsViewの親クラスの呼び出し
    カスタムの処理を行った後、通常は親クラスのmouseMoveEventを呼び出すことをお勧めします(QGraphicsView::mouseMoveEvent(event);)。これにより、Qtのデフォルトのマウスイベント処理(例: dragModeの設定によるスクロールやラバーバンド選択など)が適切に機能し続けることができます。もしイベントを完全に独自のロジックで処理し、それ以上伝播させたくない場合は、event->accept()を呼び出すか、親クラスのメソッドを呼び出さないことも可能です(ただし、これにより予期しない動作が発生する可能性もあります)。
  3. 座標変換
    mouseMoveEventで取得するevent->pos()はビューのローカル座標です。シーン内のアイテムの位置を操作したい場合は、mapToScene(event->pos())を使用してビュー座標をシーン座標に変換する必要があります。
  4. イベントの伝播
    QGraphicsViewは、受け取ったマウスイベントをQGraphicsSceneに転送し、さらにそのシーン内のQGraphicsItemに転送します。もし特定のQGraphicsItemがそのイベントを処理したい場合は、そのアイテムのmouseMoveEventをオーバーライドすることもできます。ビューとアイテムのどちらでイベントを処理するかは、実装したい機能によって異なります。ビューでイベントを処理する場合、itemAt(event->pos())などを使って、マウスカーソルの下にあるアイテムを特定することがよくあります。

簡単なコード例(MyGraphicsViewクラスでオーバーライドする場合)

mygraphicsview.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

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

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT

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

protected:
    void mouseMoveEvent(QMouseEvent *event) override; // ここをオーバーライド

signals:
    void mouseMoved(QPointF scenePos); // シーン座標を送信するシグナル

};

#endif // MYGRAPHICSVIEW_H
#include "mygraphicsview.h"

MyGraphicsView::MyGraphicsView(QWidget *parent) : QGraphicsView(parent)
{
    setMouseTracking(true); // マウスボタンが押されていなくてもMouseMoveEventを有効にする
}

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    // ビューのローカル座標を取得
    QPoint viewPos = event->pos();
    qDebug() << "View Local Pos: " << viewPos;

    // シーン座標に変換
    QPointF scenePos = mapToScene(viewPos);
    qDebug() << "Scene Pos: " << scenePos;

    // ここにカスタムの処理を記述
    // 例: マウスの位置に基づいて何かを描画したり、アイテムを操作したりする

    // シグナルを発行して、他のクラスにマウス位置を通知することもできる
    emit mouseMoved(scenePos);

    // デフォルトのQGraphicsViewのイベント処理も実行させる(重要!)
    QGraphicsView::mouseMoveEvent(event);

    // または、イベントを完全に処理してこれ以上伝播させない場合は、
    // event->accept();
    // とし、QGraphicsView::mouseMoveEvent(event); を呼び出さないことも可能ですが、
    // 注意が必要です。
}


QGraphicsView::mouseMoveEvent() はマウス操作に関連する非常に重要なイベントですが、正しく機能しない場合や予期せぬ動作をする場合があります。以下に、よくあるエラーとその解決策を挙げます。

mouseMoveEvent が全く発火しない、または期待通りに発火しない

考えられる原因

  • イベントフィルタの使用
    installEventFilter()を使ってイベントフィルタを適用している場合、そのフィルタがmouseMoveEventを捕捉し、処理を停止させている可能性があります。
    • トラブルシューティング
      イベントフィルタのeventFilter()メソッドでQEvent::MouseMoveをチェックし、return true;している箇所がないか確認してください。必要に応じてreturn false;を返すことで、イベントを本来のレシーバーに伝播させることができます。
  • QGraphicsView::dragMode の設定
    QGraphicsViewdragModeQGraphicsView::RubberBandDragQGraphicsView::ScrollHandDragなどに設定されている場合、Qtが独自にマウスドラッグイベントを処理するため、カスタムのmouseMoveEventが期待通りに動作しないことがあります。特に、マウスボタンが押されている間のmouseMoveEventでこの問題が発生しやすいです。
    • トラブルシューティング
      dragModeを一時的にQGraphicsView::NoDragに設定し、カスタムのドラッグ処理を実装するか、dragModeの動作とカスタムのmouseMoveEventが競合しないようにロジックを調整してください。例えば、mousePressEventで特定の条件が満たされたときにのみdragModeを有効にする、などの方法があります。
  • QGraphicsItemがイベントを消費している
    QGraphicsView上でQGraphicsItemがマウスイベントを処理するように設定されている場合、そのアイテムがイベントを「消費」してしまい、ビューのmouseMoveEventに到達しないことがあります。
    • トラブルシューティング
      QGraphicsItemsetAcceptedMouseButtons()が正しく設定されているか確認し、必要に応じてアイテム側でのイベント処理を調整してください。また、アイテムのmouseMoveEventevent->ignore()を呼び出すことで、イベントをビューに伝播させることも可能です。
  • イベントが親クラスに伝播されていない
    カスタムクラスでmouseMoveEventをオーバーライドしているが、親クラスのQGraphicsView::mouseMoveEvent(event);を呼び出していない場合、Qtの内部的なイベント処理が適切に行われず、予期せぬ動作をしたり、他のイベントハンドラに影響を与えたりすることがあります。
    • トラブルシューティング
      オーバーライドしたmouseMoveEventの最後に、必ず QGraphicsView::mouseMoveEvent(event); を呼び出しているか確認してください。ただし、イベントを完全に「消費」してそれ以上処理させたくない場合は例外です。
  • setMouseTracking(false) のまま
    デフォルトではQGraphicsViewmouseTrackingプロパティはfalseです。この状態では、マウスボタンが押されていない限りmouseMoveEventは発火しません。
    • トラブルシューティング
      コンストラクタや初期化時に、ビューのインスタンスに対して setMouseTracking(true); を呼び出しているか確認してください。

座標変換の誤り

考えられる原因

  • ビュー座標とシーン座標の混同
    event->pos()はビューのローカル座標(ウィジェットの左上を原点とする)を返しますが、QGraphicsScene内のアイテムを操作する場合はシーン座標が必要です。この変換を怠ると、アイテムが予期しない位置に表示されたり、クリックが正しくヒットしなかったりします。
    • トラブルシューティング
      event->pos()で取得したビュー座標を、mapToScene(event->pos())メソッドを使用してシーン座標に変換してから、アイテムの操作に利用してください。

処理が重い、またはパフォーマンスの問題

考えられる原因

  • mouseMoveEvent内での重い処理
    mouseMoveEventはマウスが少しでも動くと頻繁に呼び出されるため、その中で時間のかかる処理(例: 大量の計算、複雑な描画、ファイルI/Oなど)を行うと、アプリケーションの応答性が著しく低下します。
    • トラブルシューティング
      • mouseMoveEvent内で実行する処理を最小限に抑えてください。
      • 重い処理が必要な場合は、タイマー (QTimer) を使用して一定間隔で処理を実行したり、別スレッド (QtConcurrentQThread) で非同期に処理を実行したりすることを検討してください。
      • 不必要なupdate()repaint()の呼び出しを避けてください。画面の更新が必要な場合でも、update()を一度呼び出すだけで十分な場合が多いです。

イベントの競合や予期せぬイベントの発生

  • 特定の条件でのみイベントを処理したい場合
    例: 左クリックしながらドラッグしている時だけ処理したい、など。
    • トラブルシューティング
      event->buttons()event->modifiers() を使用して、イベント発生時のマウスボタンの状態や修飾キーの状態を確認し、条件分岐で必要な処理のみを実行するようにします。
  • 複数のイベントハンドラが同じイベントを処理しようとしている
    QGraphicsViewQGraphicsSceneQGraphicsItemのそれぞれにmouseMoveEventが存在し、それぞれがイベントを処理しようとすると、競合が発生することがあります。
    • トラブルシューティング
      どのレベル(ビュー、シーン、アイテム)でマウスイベントを処理すべきかを明確にし、他のレベルでの不必要なイベントハンドリングを避けてください。event->accept()event->ignore()を適切に使用して、イベントの伝播を制御します。
      • event->accept(): イベントを処理し、それ以上他のイベントハンドラに伝播させない。
      • event->ignore(): イベントを処理せず、他のイベントハンドラに伝播させる。

デバッグのヒント

  • 最小限の再現コードを作成する
    問題が複雑な場合は、関連するコードだけを抜き出して最小限のプロジェクトを作成し、問題を切り分けます。
  • イベントのフローを追跡する
    QGraphicsViewQGraphicsSceneQGraphicsItemの各mouseMoveEventをオーバーライドして、イベントがどの順番でどこに到達しているかをqDebug()で確認します。
  • qDebug() を使う
    mouseMoveEventの最初と最後にqDebug()でメッセージを出力し、イベントがいつ、どれくらいの頻度で発火しているかを確認します。event->pos()event->buttons()などの情報も出力すると役立ちます。


ここでは、QGraphicsView::mouseMoveEvent() をオーバーライドして、様々なマウス操作に基づくカスタム動作を実装する例をいくつか紹介します。

例1: マウスカーソルのビュー座標とシーン座標を追跡表示する

この例では、QGraphicsView の上でマウスを動かすと、現在のビュー座標(QPoint)とシーン座標(QPointF)をステータスバーに表示します。setMouseTracking(true) を設定することで、マウスボタンが押されていなくてもイベントが発生するようにします。

構成ファイル

  • mygraphicsview.cpp
  • mygraphicsview.h
  • mainwindow.cpp
  • mainwindow.h

mygraphicsview.h (カスタム QGraphicsView クラスの定義)

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QMouseEvent> // QMouseEvent を使うために必要
#include <QPointF>   // QPointF を使うために必要

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT // シグナルとスロットを使うために必要

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

protected:
    // mouseMoveEvent をオーバーライドする
    void mouseMoveEvent(QMouseEvent *event) override;

signals:
    // マウスが移動したときに、現在のシーン座標を通知するシグナル
    void mouseMoved(QPointF scenePos);
};

#endif // MYGRAPHICSVIEW_H

mygraphicsview.cpp (カスタム QGraphicsView クラスの実装)

#include "mygraphicsview.h"
#include <QGraphicsScene> // QGraphicsScene が必要になる場合がある
#include <QDebug>         // デバッグ出力用

MyGraphicsView::MyGraphicsView(QWidget *parent) : QGraphicsView(parent)
{
    // マウスボタンが押されていなくても mouseMoveEvent を発生させる
    setMouseTracking(true);

    // シーンを設定(これはMainWindowで行うことが多いですが、デモ用にここで設定することも可能)
    // setScene(new QGraphicsScene(this)); // 必要に応じて
}

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    // 1. ビューのローカル座標を取得 (QPoint)
    QPoint viewPos = event->pos();

    // 2. ビュー座標をシーン座標に変換 (QPointF)
    QPointF scenePos = mapToScene(viewPos);

    // デバッグ出力(コンソールに表示)
    qDebug() << "Mouse Move - View Pos:" << viewPos << ", Scene Pos:" << scenePos;

    // シグナルを発行して、接続されたスロットにシーン座標を渡す
    emit mouseMoved(scenePos);

    // 親クラスの mouseMoveEvent を呼び出す(重要!)
    // これにより、デフォルトのQGraphicsViewの動作(例: dragModeによるスクロールなど)が
    // 損なわれないようにします。
    QGraphicsView::mouseMoveEvent(event);
}

mainwindow.h (メインウィンドウクラスの定義)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

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

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // MyGraphicsView からの mouseMoved シグナルを受け取るスロット
    void updateMousePosition(QPointF scenePos);

private:
    Ui::MainWindow *ui;
    QGraphicsScene *scene;
    MyGraphicsView *graphicsView; // MyGraphicsView のインスタンス
};
#endif // MAINWINDOW_H

mainwindow.cpp (メインウィンドウクラスの実装)

#include "mainwindow.h"
#include "./ui_mainwindow.h" // UIファイルから生成されるヘッダー

#include <QStatusBar> // ステータスバーを使うために必要

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // QGraphicsScene を作成
    scene = new QGraphicsScene(this);
    scene->setSceneRect(-200, -150, 400, 300); // 例としてシーンの矩形を設定

    // MyGraphicsView のインスタンスを作成し、QGraphicsScene を設定
    graphicsView = new MyGraphicsView(this);
    graphicsView->setScene(scene);

    // UIのセントラルウィジェットに MyGraphicsView を設定
    // (Qt DesignerでQGraphicsViewウィジェットを配置している場合は不要で、
    //  ui->graphicsView に直接アクセスします)
    // 例えば、ui->graphicsView->setScene(scene); のように使えます。
    // ここではデモ用に手動でインスタンス化しています。
    setCentralWidget(graphicsView);

    // MyGraphicsView の mouseMoved シグナルを MainWindow のスロットに接続
    connect(graphicsView, &MyGraphicsView::mouseMoved,
            this, &MainWindow::updateMousePosition);

    // ステータスバーの準備
    statusBar()->showMessage("Mouse Position: (N/A)");
}

MainWindow::~MainWindow()
{
    delete ui;
}

// mouseMoved シグナルを受け取り、ステータスバーを更新するスロット
void MainWindow::updateMousePosition(QPointF scenePos)
{
    statusBar()->showMessage(QString("Mouse Position: X=%.1f, Y=%.1f")
                             .arg(scenePos.x())
                             .arg(scenePos.y()));
}

例2: マウスドラッグで矩形を描画する (簡易ペイントツール)

この例では、マウスの左ボタンを押しながらドラッグすることで、動的に矩形を描画します。mousePressEventで描画開始点を記録し、mouseMoveEventで矩形を更新、mouseReleaseEventで確定します。

mygraphicsview.h (変更点のみ)

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QMouseEvent>
#include <QGraphicsRectItem> // 矩形アイテム用

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT

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

protected:
    void mousePressEvent(QMouseEvent *event) override;   // マウスプレスイベント
    void mouseMoveEvent(QMouseEvent *event) override;    // マウスムーブイベント
    void mouseReleaseEvent(QMouseEvent *event) override; // マウスリリースイベント

private:
    QPointF startScenePos;        // ドラッグ開始時のシーン座標
    QGraphicsRectItem *currentRectItem; // 現在描画中の矩形アイテム
};

#endif // MYGRAPHICSVIEW_H

mygraphicsview.cpp (変更点のみ)

#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QPen> // 描画用のペン

MyGraphicsView::MyGraphicsView(QWidget *parent)
    : QGraphicsView(parent),
      currentRectItem(nullptr) // 初期化
{
    setRenderHint(QPainter::Antialiasing); // アンチエイリアシングを有効にする
    // setMouseTracking(true); // 今回の例ではドラッグ中のみ必要なので、不要な場合は設定しない
    // setDragMode(QGraphicsView::NoDrag); // カスタムドラッグ実装のため、Qtのドラッグモードを無効化
}

void MyGraphicsView::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        // ドラッグ開始点をシーン座標で記録
        startScenePos = mapToScene(event->pos());

        // 新しい矩形アイテムを作成し、シーンに追加
        currentRectItem = new QGraphicsRectItem();
        currentRectItem->setPen(QPen(Qt::blue, 2)); // 青色の実線、太さ2
        scene()->addItem(currentRectItem); // シーンにアイテムを追加
    }

    QGraphicsView::mousePressEvent(event); // 親クラスのイベントを呼び出す
}

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) { // 左ボタンが押されている場合のみ
        if (currentRectItem) {
            // 現在のマウス位置をシーン座標に変換
            QPointF currentScenePos = mapToScene(event->pos());

            // 矩形の左上点とサイズを計算
            qreal x = qMin(startScenePos.x(), currentScenePos.x());
            qreal y = qMin(startScenePos.y(), currentScenePos.y());
            qreal width = qAbs(startScenePos.x() - currentScenePos.x());
            qreal height = qAbs(startScenePos.y() - currentScenePos.y());

            // 矩形アイテムの形状を更新
            currentRectItem->setRect(x, y, width, height);
        }
    }

    QGraphicsView::mouseMoveEvent(event); // 親クラスのイベントを呼び出す
}

void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        // ドラッグ終了
        currentRectItem = nullptr; // 次の描画のためにポインタをリセット
    }

    QGraphicsView::mouseReleaseEvent(event); // 親クラスのイベントを呼び出す
}

mainwindow.cpp (変更点のみ)

MainWindow のコンストラクタで、graphicsView->setScene(scene); を呼び出す前に scene = new QGraphicsScene(this); を行うことを忘れないでください。この例では、setMouseTracking(true) は不要で、代わりにmousePressEventでドラッグが開始されることを検出しています。また、setDragMode(QGraphicsView::NoDrag)MyGraphicsView のコンストラクタに追加すると、Qt標準のドラッグ動作を無効にして、独自のドラッグ処理が干渉されないようにすることができます。

例3: マウスオーバーでアイテムをハイライト表示する

この例では、マウスカーソルがQGraphicsView上のQGraphicsItemにホバーすると、そのアイテムの色が変わる(ハイライトされる)ようにします。

mygraphicsview.h (変更点のみ)

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QMouseEvent>
#include <QGraphicsItem> // QGraphicsItem を使うために必要

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT

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

protected:
    void mouseMoveEvent(QMouseEvent *event) override;

private:
    QGraphicsItem *hoveredItem; // 現在ホバー中のアイテムを保持
};

#endif // MYGRAPHVIEW_H

mygraphicsview.cpp (変更点のみ)

#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QBrush> // QBrush を使うために必要

MyGraphicsView::MyGraphicsView(QWidget *parent)
    : QGraphicsView(parent),
      hoveredItem(nullptr) // 初期化
{
    setMouseTracking(true); // マウスボタンが押されていなくてもイベントを発生させる
}

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    // 現在のマウス位置にあるアイテムを取得
    QGraphicsItem *item = itemAt(event->pos());

    // 前にホバーしていたアイテムと現在のアイテムが異なる場合
    if (item != hoveredItem) {
        // 前のアイテムが存在すれば、元の色に戻す
        if (hoveredItem) {
            // 例: QGraphicsRectItemを想定。元の色に戻す処理
            if (QGraphicsRectItem *rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(hoveredItem)) {
                rectItem->setBrush(Qt::red); // 元の色に戻す例
            }
            // 他のアイテムタイプの場合は、それぞれの元の状態に戻す処理を記述
        }

        // 新しいアイテムが存在すれば、ハイライトする
        if (item) {
            // 例: QGraphicsRectItemを想定。ハイライト色に設定
            if (QGraphicsRectItem *rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item)) {
                rectItem->setBrush(Qt::green); // ハイライト色にする例
            }
            // 他のアイテムタイプの場合は、それぞれのハイライト処理を記述
        }

        // ホバー中のアイテムを更新
        hoveredItem = item;
    }

    QGraphicsView::mouseMoveEvent(event); // 親クラスのイベントを呼び出す
}

mainwindow.cpp (追加)

MainWindow のコンストラクタで、QGraphicsScene にいくつかのアイテムを追加しておくと動作を確認できます。

// ... (MainWindow::MainWindow コンストラクタ内) ...

    scene = new QGraphicsScene(this);
    scene->setSceneRect(-200, -150, 400, 300);

    // テスト用の矩形アイテムをいくつか追加
    QGraphicsRectItem *rect1 = new QGraphicsRectItem(-100, -50, 50, 50);
    rect1->setBrush(Qt::red);
    scene->addItem(rect1);

    QGraphicsRectItem *rect2 = new QGraphicsRectItem(50, 20, 70, 40);
    rect2->setBrush(Qt::red);
    scene->addItem(rect2);

    graphicsView = new MyGraphicsView(this);
    graphicsView->setScene(scene);
    setCentralWidget(graphicsView);

// ...


QGraphicsScene::mouseMoveEvent() をオーバーライドする

QGraphicsView はユーザーからのマウスイベントを受け取ると、それをQGraphicsSceneに変換して転送します。したがって、シーン全体でのマウス移動イベントを処理したい場合は、QGraphicsSceneをサブクラス化してmouseMoveEventをオーバーライドすることができます。

  • 注意点
    • イベントはQGraphicsSceneMouseEvent型で渡されます。これにはscenePos()(シーン座標)、screenPos()(スクリーン座標)、buttonDownScenePos()などの情報が含まれます。
    • QGraphicsViewsetMouseTracking(true)とは異なり、QGraphicsSceneではmouseMoveEventは通常、マウスボタンが押されている間(ドラッグ中)にのみ発生します。マウスボタンが押されていない状態でも常にシーンのイベントを捕捉したい場合は、ビュー側でsetMouseTracking(true)を設定し、ビューのmouseMoveEventからシーンのメソッドを呼び出すか、イベントフィルタを使用する必要があります。
  • いつ使うか
    • 特定のアイテムに依存せず、シーン全体の背景に対してマウス移動イベントを処理したい場合。
    • ユーザーがシーン内のどこにマウスを動かしても、その位置情報を取得したい場合。
    • ビューのズームやパン操作など、ビューレベルの動作と連動してシーンの表示を変更したい場合。

コード例(MyGraphicsSceneクラスでオーバーライド)

mygraphicsscene.h

#ifndef MYGRAPHICSSCENE_H
#define MYGRAPHICSSCENE_H

#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent> // QGraphicsSceneMouseEvent を使うために必要
#include <QDebug>

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

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

#endif // MYGRAPHICSSCENE_H

mygraphicsscene.cpp

#include "mygraphicsscene.h"

MyGraphicsScene::MyGraphicsScene(QObject *parent) : QGraphicsScene(parent)
{
    // シーンのイベントはデフォルトでインタラクティブです
}

void MyGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    qDebug() << "Scene Mouse Move - Scene Pos:" << event->scenePos()
             << ", Screen Pos:" << event->screenPos();

    // デフォルトのイベント処理も実行させる(重要!)
    QGraphicsScene::mouseMoveEvent(event);
}

QGraphicsItem::hoverMoveEvent() をオーバーライドする

個々のQGraphicsItem(矩形、円、画像など)がマウスカーソルの下にあるときに反応させたい場合は、そのアイテムのhoverMoveEvent()をオーバーライドするのが適切です。

  • 注意点
    • hoverMoveEventを有効にするには、アイテムのsetAcceptHoverEvents(true)を呼び出す必要があります。デフォルトでは無効です。
    • このイベントは、マウスボタンが押されていなくても発生します。
    • イベントはQGraphicsSceneHoverEvent型で渡され、pos()(アイテムのローカル座標)、scenePos()(シーン座標)、screenPos()(スクリーン座標)などの情報が含まれます。
    • このイベントは、マウスカーソルがアイテムのboundingRect()内に留まっている間、またはshape()で定義された形状内に留まっている間にのみ発生します。

コード例(カスタムQGraphicsRectItemでオーバーライド)

myrectitem.h

#ifndef MYRECTITEM_H
#define MYRECTITEM_H

#include <QGraphicsRectItem>
#include <QGraphicsSceneHoverEvent> // QGraphicsSceneHoverEvent を使うために必要
#include <QBrush>
#include <QDebug>

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

protected:
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; // マウスがアイテムに入った時
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; // マウスがアイテムから出た時
    void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;  // マウスがアイテム上を移動する時

private:
    QBrush originalBrush; // 元のブラシを保存
};

#endif // MYRECTITEM_H

myrectitem.cpp

#include "myrectitem.h"

MyRectItem::MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent)
    : QGraphicsRectItem(x, y, width, height, parent)
{
    setAcceptHoverEvents(true); // ホバーイベントを有効にする
    originalBrush = brush(); // 元のブラシを保存
    setBrush(Qt::red); // デフォルトの色を設定
}

void MyRectItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    setBrush(Qt::green); // マウスが入ったら緑色に
    qDebug() << "Item Hover Enter: " << mapToScene(event->pos());
    QGraphicsRectItem::hoverEnterEvent(event);
}

void MyRectItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    setBrush(originalBrush); // マウスが出たら元の色に戻す
    qDebug() << "Item Hover Leave";
    QGraphicsRectItem::hoverLeaveEvent(event);
}

void MyRectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    qDebug() << "Item Hover Move (Local): " << event->pos(); // アイテムのローカル座標
    qDebug() << "Item Hover Move (Scene): " << event->scenePos(); // シーン座標
    QGraphicsRectItem::hoverMoveEvent(event);
}

イベントフィルタ (QObject::eventFilter()) を使用する

イベントフィルタは、特定のウィジェットやオブジェクトに送られるイベントを、そのオブジェクト自身が処理する前に横取りして処理するための強力なメカニズムです。

  • 注意点
    • イベントフィルタは、installEventFilter()を呼び出して対象のオブジェクトにインストールする必要があります。
    • eventFilter()メソッドは、QEvent型のイベントを受け取ります。イベントの種類をチェックし、必要に応じてqobject_caststatic_castで適切なイベント型にキャストする必要があります。
    • eventFilter()trueを返すと、イベントは「消費され」、それ以上処理されることはありません。falseを返すと、イベントは本来のレシーバーに伝播されます。
  • いつ使うか
    • 既存のQGraphicsViewクラスをサブクラス化せずにイベントを処理したい場合。
    • 複数のウィジェット/オブジェクトのイベントを単一の場所で一元的に処理したい場合。
    • サードパーティのウィジェットの動作を変更したいが、そのソースコードにアクセスできない場合。

コード例(MainWindowでQGraphicsViewのイベントをフィルタリング)

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QEvent> // QEvent を使うために必要
#include <QMouseEvent> // QMouseEvent を使うために必要

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    // イベントフィルタをオーバーライド
    bool eventFilter(QObject *obj, QEvent *event) override;

private:
    Ui::MainWindow *ui;
    QGraphicsScene *scene;
    QGraphicsView *graphicsView;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QStatusBar>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    scene = new QGraphicsScene(this);
    scene->setSceneRect(-200, -150, 400, 300);

    graphicsView = new QGraphicsView(this); // 標準のQGraphicsViewを使用
    graphicsView->setScene(scene);
    setCentralWidget(graphicsView);

    // graphicsView のマウス移動イベントをトラッキングする
    graphicsView->setMouseTracking(true);

    // graphicsView にイベントフィルタをインストール
    graphicsView->installEventFilter(this);

    statusBar()->showMessage("Filtered Mouse Pos: (N/A)");
}

MainWindow::~MainWindow()
{
    delete ui;
}

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    // イベントが発生したオブジェクトが graphicsView で、かつマウス移動イベントの場合
    if (obj == graphicsView && event->type() == QEvent::MouseMove) {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
        QPointF scenePos = graphicsView->mapToScene(mouseEvent->pos());

        statusBar()->showMessage(QString("Filtered Mouse Pos: X=%.1f, Y=%.1f")
                                 .arg(scenePos.x())
                                 .arg(scenePos.y()));

        // イベントを消費しない (false を返す) ことで、本来の graphicsView の
        // mouseMoveEvent も実行されるようにする
        return false;
    }
    // それ以外のイベントは、親クラスの eventFilter に任せる
    return QMainWindow::eventFilter(obj, event);
}

QGraphicsItemには、移動(ドラッグ)やドロップのための組み込み機能が用意されています。これらを有効にするだけで、mouseMoveEventを直接実装することなくアイテムを移動させることができます。

  • 注意点
    • アイテムの移動を有効にするには、setFlag(QGraphicsItem::ItemIsMovable)を設定します。
    • アイテムが選択可能である必要があれば、setFlag(QGraphicsItem::ItemIsSelectable)も設定します。
    • デフォルトのドラッグ動作で十分な場合、カスタムのmouseMoveEventは不要です。
  • いつ使うか
    • ユーザーがQGraphicsItemをドラッグして移動させたい場合。
    • アイテムをドラッグして、別の場所にドロップしたい場合。

コード例(移動可能な矩形アイテム)

// MainWindowのコンストラクタ内で
QGraphicsRectItem *movableRect = new QGraphicsRectItem(-50, -50, 100, 100);
movableRect->setBrush(Qt::cyan);
movableRect->setFlag(QGraphicsItem::ItemIsMovable); // これを設定するだけ
scene->addItem(movableRect);

QGraphicsView::mouseMoveEvent() は、ビュー全体でのマウス移動処理に直接アクセスする強力な方法ですが、実装したい機能に応じて、より適切な代替手段が存在します。

  • アイテムの移動/ドラッグ&ドロップ
    QGraphicsItem::ItemIsMovableなどのフラグ
  • カスタムのイベント処理/既存クラスの変更なし
    QObject::eventFilter()
  • 個々のアイテムへのホバー
    QGraphicsItem::hoverMoveEvent() (setAcceptHoverEvents(true) と共に)
  • ビュー全体の挙動
    QGraphicsView::mouseMoveEvent() またはQGraphicsScene::mouseMoveEvent()