QGraphicsScene::wheelEvent()で実現する拡大縮小・スクロール:Qtプログラミング実践ガイド

2025-04-26

  • QWheelEventオブジェクト
    • QWheelEventオブジェクトには、ホイールの回転量、回転方向、マウスカーソルの位置などの情報が含まれています。
    • これらの情報を使用して、ホイールの回転に応じた適切な動作を決定できます。
  • イベントの発生
    • ユーザーがグラフィックスビュー内でマウスホイールを回転させると、QGraphicsSceneにホイールイベントが発生します。
    • このイベントはQWheelEventオブジェクトとして渡されます。

主な情報を含むQWheelEventオブジェクト

  • globalPosition()
    グローバルスクリーン座標でのマウスカーソルの位置を返します。
  • position()
    マウスカーソルの位置を返します。
  • modifiers()
    修飾キー(Shift、Ctrl、Altなど)の状態を返します。
  • angleDelta()
    ホイールの回転量をQPointとして返します。
  • delta()
    ホイールの回転量を度数で返します。正の値はホイールを前方に回転させたことを示し、負の値は後方に回転させたことを示します。

実装例

以下は、QGraphicsScene::wheelEvent()をオーバーライドして、ホイールの回転に応じてシーンを拡大縮小する簡単な例です。

#include <QGraphicsScene>
#include <QWheelEvent>
#include <QGraphicsView>
#include <QDebug>

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

protected:
    void wheelEvent(QGraphicsSceneWheelEvent *event) override {
        qDebug() << "Wheel event received";
        QGraphicsView *view = views().first(); //get the first view
        if(view){
            double scaleFactor = 1.15; // 拡大/縮小率
            if (event->delta() > 0) {
                view->scale(scaleFactor, scaleFactor); //拡大
            } else {
                view->scale(1.0 / scaleFactor, 1.0 / scaleFactor); //縮小
            }
        }
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyScene scene;
    scene.addRect(0,0,100,100);
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

この例では、wheelEvent()関数内でevent->delta()を使用してホイールの回転方向を判断し、QGraphicsView::scale()を使用してシーンを拡大縮小しています。



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

    • 原因
      • QGraphicsViewQGraphicsSceneに正しく関連付けられていない。
      • QGraphicsViewがフォーカスを持っていない。
      • QGraphicsViewviewport()に対するイベントフィルタがホイールイベントを遮断している。
      • QGraphicsSceneeventFilter()でイベントが遮断されている。
    • トラブルシューティング
      • QGraphicsView::setScene()を使用して、シーンがビューに正しく設定されていることを確認してください。
      • QGraphicsView::setFocus()を使用して、ビューにフォーカスを設定してください。
      • ビューまたはシーンにイベントフィルタが設定されている場合は、ホイールイベントがフィルタリングされていないことを確認してください。
      • デバッグのために、qDebug()を使用して、ホイールイベントがビューに到達しているかどうかを確認します。
      • QGraphicsView::setInteractive(true)が設定されているか確認してください。
  1. delta()またはangleDelta()の値が期待どおりでない

    • 原因
      • マウスホイールのハードウェア設定。
      • オペレーティングシステムの設定。
      • Qtのバージョンによる違い。
    • トラブルシューティング
      • 異なるマウスでテストして、ハードウェアの問題を除外します。
      • オペレーティングシステムのホイール設定を確認します。
      • Qtのドキュメントを参照して、使用しているバージョンにおけるdelta()またはangleDelta()の動作を確認します。
      • 値のスケールを調整することで問題を回避する。
  2. 拡大縮小またはスクロールの動作がスムーズでない

    • 原因
      • QGraphicsView::scale()またはQGraphicsView::translate()の呼び出しが頻繁すぎる。
      • シーン内のアイテムの描画が複雑すぎる。
      • ビューの更新が遅延している。
    • トラブルシューティング
      • ホイールイベントの頻度を制限するために、タイマーまたは累積値を使用します。
      • シーン内のアイテムの描画を最適化します。
      • QGraphicsView::update()を適切なタイミングで呼び出して、ビューを更新します。
      • QGraphicsView::setRenderHints()を使用して、レンダリングの品質とパフォーマンスを調整します。
  3. 修飾キー(Shift、Ctrlなど)が正しく検出されない

    • 原因
      • QWheelEvent::modifiers()の解釈が間違っている。
      • OSによるキーの解釈の差。
    • トラブルシューティング
      • QWheelEvent::modifiers()の戻り値をビットマスクとして扱い、Qt::ShiftModifierQt::ControlModifierなどの定数とビット演算を使用して修飾キーの状態を確認します。
      • OSごとのキーの解釈の差を考慮し、OSごとの条件分岐で対処する。
  4. ビューの座標系とシーンの座標系の混同

    • 原因
      • QWheelEvent::position()QGraphicsView::mapToScene()の使い方の誤り。
    • トラブルシューティング
      • QWheelEvent::position()はビューの座標系における座標を返すため、シーンの座標系に変換する必要がある場合は、QGraphicsView::mapToScene()を使用します。
      • 座標系を明確に意識したコードを書く。

デバッグのヒント

  • グラフィックスビューのキャッシュの利用方法、描画の最適化などグラフィックスパフォーマンスに関する情報も確認する。
  • ステップ実行を使用して、コードの実行フローを追跡します。
  • qDebug()を使用して、wheelEvent()が呼び出されているかどうか、およびQWheelEventオブジェクトの値を確認します。


例1: ホイール回転によるアイテムの拡大縮小

この例では、ホイールの回転に応じてシーン内のアイテムを拡大縮小します。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        rectItem = addRect(QRectF(0, 0, 100, 100));
    }

protected:
    void wheelEvent(QGraphicsSceneWheelEvent *event) override {
        qDebug() << "Wheel event received";
        double scaleFactor = 1.15;
        if (event->delta() > 0) {
            rectItem->setScale(rectItem->scale() * scaleFactor); // 拡大
        } else {
            rectItem->setScale(rectItem->scale() / scaleFactor); // 縮小
        }
    }

private:
    QGraphicsRectItem *rectItem;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

説明

  • QGraphicsRectItem::setScale()を使用して、アイテムのスケールを変更します。
  • event->delta()を使用して、ホイールの回転方向を取得します。
  • wheelEvent()関数をオーバーライドして、ホイールイベントを処理します。
  • MySceneクラスはQGraphicsSceneを継承しています。

例2: ホイール回転によるビューのスクロール

この例では、ホイールの回転に応じてビューをスクロールします。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        addRect(QRectF(0, 0, 500, 500));
    }

protected:
    void wheelEvent(QGraphicsSceneWheelEvent *event) override {
        qDebug() << "Wheel event received";
        QGraphicsView *view = views().first();
        if (view) {
            QPointF scrollAmount = event->delta() > 0 ? QPointF(0, -20) : QPointF(0, 20); //スクロール量
            view->verticalScrollBar()->setValue(view->verticalScrollBar()->value() + scrollAmount.y());
        }
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

説明
 

  • 水平スクロールも同様にQGraphicsView::horizontalScrollBar()を使用します。
  • QGraphicsView::verticalScrollBar()を使用して、垂直スクロールバーの値を変更します。
  • event->delta()を使用して、ホイールの回転方向を取得します。
  • wheelEvent()関数をオーバーライドして、ホイールイベントを処理します。
  • MySceneクラスはQGraphicsSceneを継承しています。

例3: 修飾キーによる動作の変更

この例では、Ctrlキーが押されている場合に拡大縮小、それ以外の場合にスクロールします。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        rectItem = addRect(QRectF(0, 0, 100, 100));
        addRect(QRectF(0, 0, 500, 500));
    }

protected:
    void wheelEvent(QGraphicsSceneWheelEvent *event) override {
        qDebug() << "Wheel event received";
        QGraphicsView *view = views().first();
        if (event->modifiers() & Qt::ControlModifier) {
            double scaleFactor = 1.15;
            if (event->delta() > 0) {
                rectItem->setScale(rectItem->scale() * scaleFactor);
            } else {
                rectItem->setScale(rectItem->scale() / scaleFactor);
            }
        } else if (view) {
            QPointF scrollAmount = event->delta() > 0 ? QPointF(0, -20) : QPointF(0, 20);
            view->verticalScrollBar()->setValue(view->verticalScrollBar()->value() + scrollAmount.y());
        }
    }

private:
    QGraphicsRectItem *rectItem;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}
  • Ctrlキーが押されている場合は拡大縮小、それ以外の場合はスクロールを実行します。
  • ビット演算を使用して、Ctrlキーが押されているかどうかを判断します。
  • event->modifiers()を使用して、修飾キーの状態を取得します。


QGraphicsViewのイベントフィルタを使用する

QGraphicsViewviewport()にイベントフィルタをインストールすることで、ホイールイベントをビューレベルで処理できます。これにより、シーンレベルでの処理よりも前にイベントを捕捉できます。

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

class MyView : public QGraphicsView {
public:
    MyView(QGraphicsScene *scene, QWidget *parent = nullptr) : QGraphicsView(scene, parent) {
        viewport()->installEventFilter(this);
    }

protected:
    bool eventFilter(QObject *watched, QEvent *event) override {
        if (watched == viewport() && event->type() == QEvent::Wheel) {
            QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
            qDebug() << "View Wheel event received";
            // ビューレベルでのホイールイベント処理
            // ...
            return true; // イベントを処理済みとしてマーク
        }
        return QGraphicsView::eventFilter(watched, event);
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QGraphicsScene scene;
    scene.addRect(QRectF(0, 0, 100, 100));
    MyView view(&scene);
    view.show();
    return app.exec();
}

利点

  • シーンレベルでの処理よりも前にイベントを捕捉できるため、より細かい制御が可能。
  • ビューレベルでのイベント処理が可能。

欠点

  • ビュー固有の処理に限定される。
  • シーンレベルのイベント処理と重複する可能性がある。

QAbstractScrollAreaのスクロールバーを使用する

QGraphicsViewQAbstractScrollAreaを継承しているため、スクロールバーのシグナルを使用してホイールイベントに似た動作を実現できます。ただし、これはホイールイベントの直接的な代替手段ではなく、スクロール動作を制御するためのものです。

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

class MyView : public QGraphicsView {
public:
    MyView(QGraphicsScene *scene, QWidget *parent = nullptr) : QGraphicsView(scene, parent) {
        connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &MyView::onVerticalScroll);
    }

public slots:
    void onVerticalScroll(int value) {
        qDebug() << "Vertical scroll value changed:" << value;
        // スクロールバーの値に応じた処理
        // ...
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QGraphicsScene scene;
    scene.addRect(QRectF(0, 0, 500, 500));
    MyView view(&scene);
    view.show();
    return app.exec();
}

利点

  • ホイールイベントに依存しないスクロール動作を実装できる。
  • スクロールバーの値を直接制御できる。

欠点

  • ホイール回転による拡大縮小などの他の動作には適さない。
  • ホイールイベントの直接的な代替手段ではない。

QTimerを使用してホイールイベントの頻度を制御する

ホイールイベントが頻繁に発生する場合、QTimerを使用してイベントの処理頻度を制限することで、パフォーマンスを向上させることができます。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        rectItem = addRect(QRectF(0, 0, 100, 100));
        timer.setInterval(100);
        connect(&timer, &QTimer::timeout, this, &MyScene::processWheel);
    }

protected:
    void wheelEvent(QGraphicsSceneWheelEvent *event) override {
        wheelDelta += event->delta();
        if (!timer.isActive()) {
            timer.start();
        }
    }

public slots:
    void processWheel() {
        if(wheelDelta == 0){
            timer.stop();
            return;
        }
        double scaleFactor = 1.15;
        if (wheelDelta > 0) {
            rectItem->setScale(rectItem->scale() * scaleFactor);
        } else {
            rectItem->setScale(rectItem->scale() / scaleFactor);
        }
        wheelDelta = 0;
    }

private:
    QGraphicsRectItem *rectItem;
    QTimer timer;
    int wheelDelta = 0;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

利点
 

  • パフォーマンスを向上させることができる。
  • イベントの処理頻度を制御できる。
  • イベントの遅延が発生する可能性がある。