QGraphicsView::transformationAnchorでQtアプリの操作性を向上させる方法

2025-05-27

QGraphicsView::transformationAnchor とは

QGraphicsView::transformationAnchorは、QtのGraphics View Frameworkにおいて、ビュー(QGraphicsView)が拡大・縮小(ズーム)や回転といった座標変換を行う際に、どの点を基準にして変換を行うかを設定するプロパティです。

このプロパティは、QGraphicsView::ViewportAnchor型の列挙体で定義される以下のいずれかの値を設定できます。

    • 変換の基準点を特に設定しません。
    • この場合、通常はビューポートの左上(またはビューの内部的な原点)を基準として変換が行われることが多いです。
    • 結果として、ズームイン・アウトすると、ビューポートの左上を基準に画面が移動するように見えます。
  1. QGraphicsView::AnchorViewCenter

    • ビューポートの中心を基準点として変換を行います。
    • ズームイン・アウトすると、ビューポートの中心が常に画面の中心に維持されるように見えます。これは、ユーザーがビューポートの全体的な中心を見ながら操作したい場合に非常に便利です。
  2. QGraphicsView::AnchorUnderMouse

    • マウスカーソルの位置を基準点として変換を行います。
    • ズームイン・アウトすると、マウスカーソルの下の内容が常に同じ位置に維持されるように見えます。CADソフトウェアや地図アプリケーションなど、ユーザーが特定の場所を細かく拡大・縮小したい場合に非常に有効な設定です。

なぜ重要なのか

QGraphicsViewは、QGraphicsSceneの内容をユーザーに表示するためのウィジェットです。ユーザーがマウスホイールを使ってズームしたり、ドラッグしてパンしたりする場合、その操作の挙動はtransformationAnchorの設定によって大きく変わります。

例えば、地図アプリケーションで特定の位置を拡大したい場合、マウスカーソルをその位置に置いてズームすると、意図した通りの場所が拡大される方が直感的です。このような挙動を実現するためにAnchorUnderMouseが使われます。

逆に、常に画面全体の中央を基準にズームしたい場合は、AnchorViewCenterを設定することで、画面が意図せず流れていくことを防ぐことができます。

QGraphicsViewのインスタンスに対して、setTransformationAnchor()メソッドを使って設定します。

#include <QtWidgets>

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

    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400); // シーンの中心を(0,0)に

    // いくつかのアイテムを追加
    scene.addRect(0, 0, 50, 50, QPen(Qt::blue), QBrush(Qt::cyan));
    scene.addEllipse(-100, -100, 80, 80, QPen(Qt::red), QBrush(Qt::magenta));
    scene.addText("Hello Qt!", QFont("Arial", 20));

    QGraphicsView view(&scene);

    // transformationAnchor の設定例
    // view.setTransformationAnchor(QGraphicsView::NoAnchor);        // デフォルト
    // view.setTransformationAnchor(QGraphicsView::AnchorViewCenter); // ビューの中心を基準
    view.setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // マウスカーソル位置を基準

    // ズームできるようにする
    view.setRenderHint(QPainter::Antialiasing); // アンチエイリアシングを有効に
    view.setResizeAnchor(QGraphicsView::AnchorViewCenter); // リサイズ時にビューの中心を維持

    // マウスホイールによるズームイベントハンドラ
    view.setMouseTracking(true); // マウス座標を常に取得できるように
    view.installEventFilter(&a); // アプリケーションレベルでイベントをフィルタリング

    QObject::connect(&a, &QApplication::wheelRequested, [&](QWheelEvent *event) {
        if (event->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) return; // フィルタリングされたイベントを無視
        if (event->angleDelta().y() == 0) return;

        double scaleFactor = 1.15; // ズーム倍率
        if (event->angleDelta().y() < 0) {
            scaleFactor = 1.0 / scaleFactor;
        }
        view.scale(scaleFactor, scaleFactor);
        event->accept();
    });

    view.setWindowTitle("QGraphicsView Transformation Anchor Example");
    view.show();

    return a.exec();
}

上記のコード例では、QGraphicsView::AnchorUnderMouseを設定しており、マウスホイールを回転させると、マウスカーソルのある場所を中心にズームが行われることが確認できます。コメントアウトされている他の設定を試して、その挙動の違いを比較してみてください。



期待通りのズーム・パン挙動にならない

問題点
ユーザーがマウスホイールでズームイン・アウトしたり、ドラッグでパン(画面移動)したりする際に、画面が意図しない方向に移動したり、ズームの中心がずれたりする。

原因

  • ズームやパンのロジック(特にマウスイベントハンドラ内)が、transformationAnchorの意図と一致していない。
  • transformationAnchorの設定が、実現したいユーザーエクスペリエンスと合っていない。

トラブルシューティング

  • setSceneRect()とアイテムの配置
    QGraphicsScenesceneRectが正しく設定されていない場合や、QGraphicsItemがシーンの範囲外に配置されている場合、スクロールバーの挙動や初期表示が意図しないものになることがあります。transformationAnchor自体が直接の原因ではないこともありますが、全体的なズーム・パンの挙動に影響を与える可能性があります。

  • ズーム・パンのロジックの見直し
    wheelEventなどでscale()メソッドを呼び出す際に、transformationAnchorの設定を一時的に変更している場合、その変更が正しく元に戻されているか確認してください。 例:

    void MyGraphicsView::wheelEvent(QWheelEvent *event) {
        // 現在のアンカーを保存
        ViewportAnchor oldAnchor = transformationAnchor();
        // 一時的にマウスカーソル位置をアンカーに設定
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    
        double scaleFactor = 1.15;
        if (event->angleDelta().y() < 0) {
            scaleFactor = 1.0 / scaleFactor;
        }
        scale(scaleFactor, scaleFactor);
    
        // アンカーを元の設定に戻す
        setTransformationAnchor(oldAnchor);
        event->accept();
    }
    

    このように、ズーム操作時にのみ一時的にAnchorUnderMouseを設定し、操作後に元のアンカーに戻すのが一般的なパターンです。これを怠ると、その後のズーム操作で予期せぬ挙動になることがあります。

  • transformationAnchorの確認と調整

    • QGraphicsView::NoAnchor
      ズーム時にビューポートの左上(または内部的な原点)が基準になるため、ズームインするとビューポートの左上が固定され、ズームアウトすると全体が左上に向かって縮小するように見えます。この挙動が望ましいか確認してください。
    • QGraphicsView::AnchorViewCenter
      ズーム時にビューポートの中心が固定されます。ズームイン・アウトしても、常に中央が基準になります。画面全体を均等に拡大・縮小したい場合に適しています。
    • QGraphicsView::AnchorUnderMouse
      マウスカーソルの位置がズームの中心になります。特定の箇所を細かく見たい場合に非常に便利です。多くのCADや画像編集ソフトで採用されている直感的な挙動です。

    あなたのアプリケーションの目的とユーザーの期待に合わせて、適切なtransformationAnchorを設定しているか確認しましょう。

setResizeAnchor()との混同

問題点
ビューのリサイズ時にもtransformationAnchorが影響していると思い込んでいる。

原因
transformationAnchorはズームや回転などの変換の基準点を決定しますが、ビューのリサイズ時の中心点はQGraphicsView::setResizeAnchor()で設定します。これらを混同している場合があります。

トラブルシューティング

  • setResizeAnchor()の利用
    ビューのリサイズ時に、ビューポートの中心を維持したい場合は、setResizeAnchor(QGraphicsView::AnchorViewCenter)を設定します。
    view.setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // ズームのアンカー
    view.setResizeAnchor(QGraphicsView::AnchorViewCenter);       // リサイズのアンカー
    
    それぞれのプロパティが異なる目的を持っていることを理解することが重要です。

スクロールバーの挙動とアンカー

問題点
transformationAnchorを設定しても、スクロールバーを使ってパンすると、アンカーの効果が感じられない。

原因
transformationAnchorQGraphicsViewscale()rotate()といった変換メソッドに影響を与えますが、スクロールバーによるパン操作はビューポートのスクロール位置を直接変更するため、transformationAnchorの影響を受けません。

トラブルシューティング

  • もし、スクロールバーではなく、マウスドラッグによるパンでAnchorUnderMouseのような挙動を期待する場合は、mousePressEventmouseMoveEventmapToScene()centerOn()を組み合わせて、カスタムのパンロジックを実装する必要があります。
  • これは「エラー」ではなく、QGraphicsViewの設計による正常な挙動です。ユーザーがスクロールバーを使用する場合、その操作は直接的なビューポートの移動を意味します。

パフォーマンスの低下(間接的な関連)

問題点
ズームやパンの操作が重い、カクつく。

原因
transformationAnchor自体が直接パフォーマンスに影響を与えることは稀ですが、不適切なアンカー設定により、ユーザーが頻繁にズーム操作を繰り返すことで、結果的にビューの再描画が多発し、パフォーマンス低下に繋がることがあります。

トラブルシューティング

  • QGraphicsView::setViewportUpdateMode()の調整
    ビューポートの更新頻度を調整することで、パフォーマンスを改善できる場合があります。FullViewportUpdateは最も重く、MinimalViewportUpdateは最も軽いです。
  • QGraphicsItem::setCacheMode()の利用
    静的な(あまり動かない)アイテムについては、QGraphicsItem::DeviceCoordinateCacheQGraphicsItem::ItemCoordinateCacheを設定することで、描画パフォーマンスを改善できます。
  • QGraphicsItem::boundingRect()の正確性
    QGraphicsItemboundingRect()が実際の内容よりも大きすぎると、不必要な再描画が発生しやすくなります。正確なバウンディングボックスを返すように実装されているか確認してください。
  • QGraphicsView::setRenderHints()の使用
    QPainter::AntialiasingQPainter::SmoothPixmapTransformなどのレンダーヒントは描画品質を向上させますが、その分パフォーマンスコストが増大します。必要に応じて調整または無効化を検討してください。


基本的なセットアップ

まず、すべての例で共通して使用する基本的なQGraphicsSceneQGraphicsViewのセットアップコードを示します。

// main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
#include <QWheelEvent>
#include <QDebug> // デバッグ出力用

// 独自のQGraphicsViewクラスを定義
class CustomGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit CustomGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        // アンチエイリアシングを有効にして描画を滑らかにする
        setRenderHint(QPainter::Antialiasing);
        // ビューのリサイズ時にビューの中心を維持する
        setResizeAnchor(QGraphicsView::AnchorViewCenter);
        // マウスホイールイベントをキャッチするためにsetMouseTrackingを有効にする
        setMouseTracking(true);
    }

protected:
    // マウスホイールイベントをオーバーライドしてズームを実装
    void wheelEvent(QWheelEvent *event) override
    {
        // Ctrlキーが押されている場合のみズーム
        if (event->modifiers() & Qt::ControlModifier) {
            double scaleFactor = 1.15; // ズーム倍率

            // ホイールの回転方向に応じて拡大・縮小
            if (event->angleDelta().y() > 0) { // 上向き(奥方向)に回転 -> 拡大
                scale(scaleFactor, scaleFactor);
            } else { // 下向き(手前方向)に回転 -> 縮小
                scale(1.0 / scaleFactor, 1.0 / scaleFactor);
            }
            event->accept(); // イベントを処理済みとしてマーク
        } else {
            // Ctrlキーが押されていない場合は、デフォルトのスクロール挙動を呼び出す
            QGraphicsView::wheelEvent(event);
        }
    }
};

// CustomGraphicsViewのメタオブジェクトシステムに必要なマクロ
#include "main.moc"

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

    // シーンの作成
    QGraphicsScene scene;
    // シーンの範囲を定義 (中心を(0,0)にする)
    scene.setSceneRect(-200, -200, 400, 400);

    // シーンにいくつかのアイテムを追加
    QGraphicsRectItem *rect = scene.addRect(-50, -50, 100, 100, QPen(Qt::blue, 2), QBrush(Qt::cyan));
    rect->setFlags(QGraphicsItem::ItemIsMovable); // ドラッグで移動可能にする
    QGraphicsEllipseItem *ellipse = scene.addEllipse(50, 50, 80, 80, QPen(Qt::red, 2), QBrush(Qt::magenta));
    ellipse->setFlags(QGraphicsItem::ItemIsMovable);
    QGraphicsTextItem *text = scene.addText("Hello Qt GraphicsView!", QFont("Arial", 20));
    text->setPos(-150, -180);
    text->setFlags(QGraphicsItem::ItemIsMovable);

    CustomGraphicsView view(&scene);

    // 以下の例で transformationAnchor を設定します

    view.setWindowTitle("QGraphicsView Transformation Anchor Example");
    view.resize(600, 400); // ウィンドウサイズを設定
    view.show();

    return a.exec();
}

コンパイル時の注意点
CustomGraphicsViewクラスを定義しているため、main.cppを直接コンパイルするだけでなく、mocツールで処理する必要があります。Qtプロジェクトファイル(.pro)を使用している場合、Qtのビルドシステムが自動的にこれを行います。手動でコンパイルする場合は、以下の手順に準じます。

  1. moc main.cpp -o main.moc
  2. g++ -c main.cpp main.moc -I<PathToQt>/include -I<PathToQt>/include/QtWidgets -I<PathToQt>/include/QtGui -I<PathToQt>/include/QtCore -fPIC
  3. g++ main.o main.moc -o your_app -L<PathToQt>/lib -lQt5Widgets -lQt5Gui -lQt5Core<PathToQt>はQtのインストールパスに置き換えてください。WindowsではMinGWなどのコンパイラが必要です。)

例1: QGraphicsView::NoAnchor (デフォルト)

この設定では、ズーム操作時にビューポートの原点(通常は左上)を基準に変換が行われます。

コードの変更
main.cppmain関数内で、CustomGraphicsViewのインスタンス化後に以下の行を追加します。

    CustomGraphicsView view(&scene);
    view.setTransformationAnchor(QGraphicsView::NoAnchor); // これを追加
    // ... (残りのコード)

挙動
マウスホイールでズームイン・アウトすると、ビューポートの左上隅が固定されるように見えます。ズームインすると、シーンが左上隅から拡大し、ズームアウトすると左上隅に向かって縮小します。これは直感的ではないと感じることが多いです。

例2: QGraphicsView::AnchorViewCenter

この設定では、ズーム操作時にビューポートの中心を基準に変換が行われます。

コードの変更
main.cppmain関数内で、CustomGraphicsViewのインスタンス化後に以下の行を追加します。

    CustomGraphicsView view(&scene);
    view.setTransformationAnchor(QGraphicsView::AnchorViewCenter); // これを追加
    // ... (残りのコード)

挙動
マウスホイールでズームイン・アウトすると、ビューポートの中心が常に画面の中心に維持されるように見えます。これは、画面全体を均等に拡大・縮小したい場合に適しています。

例3: QGraphicsView::AnchorUnderMouse

この設定は、マウスカーソルの位置を基準に変換を行います。最も直感的なズーム挙動を提供します。

コードの変更
main.cppmain関数内で、CustomGraphicsViewのインスタンス化後に以下の行を追加します。

    CustomGraphicsView view(&scene);
    view.setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // これを追加
    // ... (残りのコード)

挙動
マウスホイールでズームイン・アウトすると、マウスカーソルの位置が常に固定され、その場所を中心に拡大・縮小が行われます。CADソフトウェアや地図アプリケーションなどでよく見られる挙動で、ユーザーが特定の場所を細かく見たい場合に非常に便利です。

transformationAnchorは主にズームや回転のようなアフィン変換に影響を与えますが、マウスドラッグによるパン(画面移動)のロジックにも間接的に関連します。ここでは、AnchorUnderMouseと組み合わせた簡単なパンの実装例を示します。

コードの変更
CustomGraphicsViewクラスに以下のメンバー変数とメソッドを追加します。

// CustomGraphicsView クラスの定義内
class CustomGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit CustomGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        setResizeAnchor(QGraphicsView::AnchorViewCenter);
        setMouseTracking(true); // mouseMoveEvent が常に発生するようにする

        // transformationAnchor を AnchorUnderMouse に設定(ズーム用)
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    }

protected:
    QPointF lastPanPoint; // パン操作の開始点を記録

    void wheelEvent(QWheelEvent *event) override
    {
        // Ctrlキーが押されている場合のみズーム
        if (event->modifiers() & Qt::ControlModifier) {
            double scaleFactor = 1.15;
            if (event->angleDelta().y() > 0) {
                scale(scaleFactor, scaleFactor);
            } else {
                scale(1.0 / scaleFactor, 1.0 / scaleFactor);
            }
            event->accept();
        } else {
            QGraphicsView::wheelEvent(event);
        }
    }

    void mousePressEvent(QMouseEvent *event) override
    {
        // 中央ボタン(マウスホイール)が押されたらパンを開始
        if (event->button() == Qt::MiddleButton) {
            lastPanPoint = mapToScene(event->pos()); // シーン座標で開始点を記録
            setCursor(Qt::ClosedHandCursor); // カーソルを手の形にする
            event->accept();
        } else {
            QGraphicsView::mousePressEvent(event); // デフォルトの処理を呼び出す
        }
    }

    void mouseMoveEvent(QMouseEvent *event) override
    {
        // 中央ボタンが押されている間はパンを継続
        if (event->buttons() & Qt::MiddleButton) {
            QPointF currentPanPoint = mapToScene(event->pos()); // 現在のマウス位置をシーン座標で取得
            QPointF delta = currentPanPoint - lastPanPoint; // 移動量(シーン座標系)

            // シーンを移動
            // view->translate() はビューを移動させるが、ここではシーンの表示領域を移動させたい
            // centerOn() を使うと特定の点を中心に表示できる
            // または、スクロールバーを直接操作する方法もある
            // view->horizontalScrollBar()->setValue(view->horizontalScrollBar()->value() - delta.x());
            // view->verticalScrollBar()->setValue(view->verticalScrollBar()->value() - delta.y());

            // よりシンプルなパン(QGraphicsViewのtransleteメソッドはビューの変換行列を変更する)
            // 注意:QGraphicsView::translate()は、transformationAnchorの影響を受けるため、
            // ここでは直接的なスクロールバーの操作の方が直感的かもしれません。
            // あるいは、centerOn()を使用してビューポートの中心を移動させる。

            // この例では、QGraphicsViewの移動を直接利用するのではなく、
            // 仮想的にシーンを移動させるために centerOn を利用します。
            // 現在ビューの中心にあるシーンの点から、マウス移動量分だけオフセットした点を中心に表示する。
            QPointF viewCenterScenePos = mapToScene(viewport()->rect().center());
            centerOn(viewCenterScenePos - delta);

            lastPanPoint = currentPanPoint; // 次の移動のために現在の点を保存
            event->accept();
        } else {
            QGraphicsView::mouseMoveEvent(event); // デフォルトの処理を呼び出す
        }
    }

    void mouseReleaseEvent(QMouseEvent *event) override
    {
        // 中央ボタンが離されたらパンを終了
        if (event->button() == Qt::MiddleButton) {
            setCursor(Qt::ArrowCursor); // カーソルを元に戻す
            event->accept();
        } else {
            QGraphicsView::mouseReleaseEvent(event); // デフォルトの処理を呼び出す
        }
    }
};

挙動
Ctrlキーを押しながらマウスホイールを回転させると、マウスカーソルの位置を中心にズームイン・アウトします(AnchorUnderMouseの効果)。 マウスホイールを中央ボタンとしてクリックし、ドラッグすると、手のひらカーソルが表示され、画面が移動します。このパン操作は、transformationAnchorではなく、centerOn()またはスクロールバーの直接操作によって実現されます。



以下に、QGraphicsView::transformationAnchorを使用しない、あるいは補完する代替方法をいくつか説明します。

QGraphicsView::setTransform() を直接操作する

QGraphicsViewは内部的にQTransformオブジェクトを保持しており、これを使ってシーンの表示を変換します。setTransformationAnchor()は、scale()rotate()といった簡易メソッドがこの変換行列をどのように変更するかを制御しますが、setTransform()メソッドを使えば、この変換行列を完全に手動で制御できます。

利点

  • アニメーション
    QPropertyAnimationなどと組み合わせて、滑らかなズームやパンのアニメーションを実装できます。
  • 複雑な変換
    複数の変換(例:ズームと回転を同時に、特定の中心点で)を組み合わせたカスタムの変換を実装できます。
  • 絶対的な制御
    ズームの中心、回転の軸、パンの挙動など、あらゆる変換の側面をピクセル単位で正確に制御できます。

欠点

  • デバッグの困難さ
    予期せぬ挙動が発生した場合のデバッグが難しくなることがあります。
  • 複雑性
    変換行列の数学を理解し、自分で計算する必要があるため、実装が複雑になります。

使用例(カスタムズーム): マウスカーソルの位置を中心にズームする例をtransformationAnchorを使わずに実装します。

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

class CustomTransformView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit CustomTransformView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        setMouseTracking(true); // wheelEventでマウス位置が必要なため
    }

protected:
    void wheelEvent(QWheelEvent *event) override
    {
        if (event->modifiers() & Qt::ControlModifier) {
            // 1. マウス位置をシーン座標に変換
            QPointF mousePosInScene = mapToScene(event->pos());

            // 2. 現在のスケールファクターを取得
            qreal currentScale = transform().m11(); // X方向のスケールファクター

            double scaleFactor = 1.15;
            if (event->angleDelta().y() < 0) { // 縮小
                scaleFactor = 1.0 / scaleFactor;
            }

            // 3. 新しいスケールファクターを計算
            qreal newScale = currentScale * scaleFactor;

            // 4. 新しい変換行列を作成
            QTransform newTransform;
            // ズームの中心をマウス位置に設定するために、以下のステップを踏む
            // a. シーンをマウス位置に移動
            newTransform.translate(mousePosInScene.x(), mousePosInScene.y());
            // b. 拡大・縮小
            newTransform.scale(newScale, newScale);
            // c. シーンを元の位置に戻す(ズームの中心がマウス位置になるように調整)
            newTransform.translate(-mousePosInScene.x(), -mousePosInScene.y());

            // 5. ビューに新しい変換行列を設定
            setTransform(newTransform);

            event->accept();
        } else {
            QGraphicsView::wheelEvent(event);
        }
    }
};

#include "main.moc" // mocファイルへのインクルード

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

    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400);

    scene.addRect(-50, -50, 100, 100, QPen(Qt::blue, 2), QBrush(Qt::cyan));
    scene.addEllipse(50, 50, 80, 80, QPen(Qt::red, 2), QBrush(Qt::magenta));
    scene.addText("Manual Zoom Control", QFont("Arial", 20))->setPos(-150, -180);

    CustomTransformView view(&scene);
    view.setWindowTitle("Manual Transform Zoom Example");
    view.resize(600, 400);
    view.show();

    return a.exec();
}

このコードでは、scale()メソッドの代わりにsetTransform()を使ってズームを実装しています。mapToScene()でマウス位置をシーン座標に変換し、その点を中心に拡大・縮小するための変換行列を計算しています。これにより、QGraphicsView::AnchorUnderMouseと同様の挙動を、より低レベルで実現できます。

centerOn() と組み合わせる

QGraphicsView::centerOn()メソッドは、指定されたシーン上の点をビューポートの中心に配置します。これをパン操作やズーム操作と組み合わせることで、特定のアンカー挙動をシミュレートできます。

利点

  • ズームとの組み合わせ
    ズーム後に特定のアイテムや領域にフォーカスしたい場合に便利です。
  • 直感的なパン
    シーンの特定の点を簡単にビューの中心に持ってくることができます。

欠点

  • ズームの中心点を完璧に制御するには、ズームとcenterOnの組み合わせロジックを自分で設計する必要があります。

使用例(カスタムパン): マウスドラッグによるパンをcenterOn()を使って実装します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsTextItem>
#include <QMouseEvent>
#include <QDebug>

class CustomPanView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit CustomPanView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        // setMouseTracking(true); // 必要に応じて有効にする
    }

protected:
    QPoint lastMousePos; // マウス押下時のビュー座標を記録

    void mousePressEvent(QMouseEvent *event) override
    {
        if (event->button() == Qt::LeftButton) { // 左ボタンでパンを開始
            lastMousePos = event->pos();
            setCursor(Qt::ClosedHandCursor); // カーソルを手の形にする
            event->accept();
        } else {
            QGraphicsView::mousePressEvent(event);
        }
    }

    void mouseMoveEvent(QMouseEvent *event) override
    {
        if (event->buttons() & Qt::LeftButton) { // 左ボタンが押されている間
            QPoint delta = event->pos() - lastMousePos; // ビュー座標での移動量

            // 現在のビューの中心をシーン座標で取得
            QPointF currentViewCenterInScene = mapToScene(viewport()->rect().center());

            // 移動量分だけ中心をオフセットした新しいシーン座標を計算
            // 注意: delta.x(), delta.y() はビュー座標での移動量なので、
            // シーン座標での移動量に変換する必要があります。
            // QGraphicsView::mapToScene(QPoint) を利用
            QPointF deltaInScene = mapToScene(delta) - mapToScene(QPoint(0, 0));

            // 新しい中心位置にビューを移動
            centerOn(currentViewCenterInScene - deltaInScene);

            lastMousePos = event->pos(); // 次の移動のために現在の位置を保存
            event->accept();
        } else {
            QGraphicsView::mouseMoveEvent(event);
        }
    }

    void mouseReleaseEvent(QMouseEvent *event) override
    {
        if (event->button() == Qt::LeftButton) {
            setCursor(Qt::ArrowCursor); // カーソルを元に戻す
            event->accept();
        } else {
            QGraphicsView::mouseReleaseEvent(event);
        }
    }
    // wheelEventはデフォルトのものをそのまま使用、またはカスタムズームを実装
};

#include "main.moc" // mocファイルへのインクルード

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

    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400);

    scene.addRect(-50, -50, 100, 100, QPen(Qt::blue, 2), QBrush(Qt::cyan));
    scene.addEllipse(50, 50, 80, 80, QPen(Qt::red, 2), QBrush(Qt::magenta));
    scene.addText("Custom Pan Example", QFont("Arial", 20))->setPos(-150, -180);

    CustomPanView view(&scene);
    view.setWindowTitle("Custom Pan Example");
    view.resize(600, 400);
    view.show();

    return a.exec();
}

このパンの実装では、centerOn()を使ってビューの中心を移動させています。マウスの移動量をシーン座標に変換し、現在のビューの中心からその分だけずらした点を新しい中心としてcenterOn()に渡しています。

QGraphicsView::DragMode を利用する

QGraphicsViewには、組み込みのドラッグモードがあります。これはパン(画面移動)に特化したもので、transformationAnchorとは直接関係しませんが、代替的なパンの挙動を提供します。

利点

  • 安定した挙動
    Qtが提供する組み込み機能であるため、安定しています。
  • 実装が非常に簡単
    数行のコードでパン機能を実現できます。

欠点

  • マウスボタンのカスタマイズなど、細かい制御はできません。
  • ズームのアンカーとは独立しているため、ズームとパンの組み合わせで特定の挙動をさせるには、やはりカスタムロジックが必要になる場合があります。

使用例

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

class DragModeView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit DragModeView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        setResizeAnchor(QGraphicsView::AnchorViewCenter);

        // スクロールバーを常に表示しないと、DragMode::ScrollHandDrag が機能しない場合があります
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
        setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

        // これが肝心: ドラッグモードを ScrollHandDrag に設定
        setDragMode(QGraphicsView::ScrollHandDrag);

        // 必要に応じて、transformationAnchor を設定(この例では AnchorUnderMouse)
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    }

protected:
    void wheelEvent(QWheelEvent *event) override
    {
        if (event->modifiers() & Qt::ControlModifier) {
            double scaleFactor = 1.15;
            if (event->angleDelta().y() < 0) {
                scaleFactor = 1.0 / scaleFactor;
            }
            scale(scaleFactor, scaleFactor); // transformationAnchor の影響を受ける
            event->accept();
        } else {
            QGraphicsView::wheelEvent(event); // デフォルトのスクロール動作
        }
    }
};

#include "main.moc" // mocファイルへのインクルード

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

    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400);

    scene.addRect(-50, -50, 100, 100, QPen(Qt::blue, 2), QBrush(Qt::cyan));
    scene.addEllipse(50, 50, 80, 80, QPen(Qt::red, 2), QBrush(Qt::magenta));
    scene.addText("Drag Mode Example", QFont("Arial", 20))->setPos(-150, -180);

    DragModeView view(&scene);
    view.setWindowTitle("Drag Mode Example");
    view.resize(600, 400);
    view.show();

    return a.exec();
}

この例では、setDragMode(QGraphicsView::ScrollHandDrag)を呼び出すだけで、マウスの左ボタン(またはQGraphicsView::setDragModeのドキュメントで指定されているデフォルトのボタン)でのドラッグによるパン機能が有効になります。マウスカーソルが手のひらの形に変わり、ビューポートをドラッグして移動させることができます。

  • 特定のアイテムへのフォーカス
    centerOn()は、特定のアイテムやシーンの領域にビューポートを移動させる際に非常に便利です。ズームと組み合わせて使うこともできます。
  • 細かい制御やカスタムアニメーション
    QGraphicsView::setTransform()を直接操作する方法が最も柔軟性を提供します。アニメーションフレームごとに変換行列を計算し、setTransform()で適用することで、非常に滑らかなカスタムアニメーションを実現できます。
  • 最も簡単で一般的なズーム・パン
    QGraphicsView::transformationAnchorQGraphicsView::ScrollHandDragの組み合わせが、多くの場合に推奨されます。直感的で簡単に実装できます。