QGraphicsView::isTransformed()でQt描画を最適化!エラーと対処法

2025-05-27

QGraphicsView::isTransformed() とは?

QGraphicsView は、QGraphicsScene の内容を表示するためのウィジェットです。シーン内のアイテムは、ビュー上で様々な形で表示できます。isTransformed() メソッドは、この QGraphicsView 自体にアフィン変換(affine transformation)が適用されているかどうかをブール値(true または false)で返します。

  • false を返す場合

    • ビューに明示的な変換が適用されていないか、resetTransform() が呼び出されて変換がリセットされている状態です。
    • ビューがシーンをそのままのスケール、回転なしで表示していることを意味します。
    • setTransform() メソッドを使って変換行列が設定されている。
    • rotate(), scale(), translate(), shear() などの便利な変換関数が呼び出されている。
    • つまり、ビューがデフォルトの(単位)変換状態ではないことを意味します。例えば、ズームイン/ズームアウトされている、回転されている、移動されている、せん断されているなどの状態です。

使用例

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QTransform>
#include <QDebug> // for qDebug()

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 200, 200); // シーンの範囲を設定

    QGraphicsRectItem *rect = scene.addRect(50, 50, 100, 100, QPen(Qt::blue), QBrush(Qt::cyan));

    QGraphicsView view(&scene);
    view.setWindowTitle("QGraphicsView Transformation Example");

    // 初期状態の確認
    qDebug() << "Is view transformed initially? " << view.isTransformed(); // false と表示されるはず

    // ビューを拡大縮小してみる
    view.scale(1.5, 1.5); // 1.5倍に拡大

    // 変換後の状態を確認
    qDebug() << "Is view transformed after scaling? " << view.isTransformed(); // true と表示されるはず

    // さらに回転も加えてみる
    view.rotate(45); // 45度回転

    // 変換後の状態を確認
    qDebug() << "Is view transformed after scaling and rotating? " << view.isTransformed(); // true と表示されるはず

    // 変換をリセットする
    view.resetTransform();

    // リセット後の状態を確認
    qDebug() << "Is view transformed after resetTransform? " << view.isTransformed(); // false と表示されるはず

    view.show();

    return a.exec();
}

isTransformed() は、以下のようなシナリオで役立ちます。

  1. UIの状態管理
    ユーザーがビューをズームしたり回転させたりした場合に、特定のUI要素(例えば、ズームレベルを表示するステータスバーや、リセットボタンの有効/無効)を更新するために使用できます。
  2. パフォーマンスの最適化
    ビューが変換されている場合にのみ、特定の描画処理や計算を実行することで、パフォーマンスを向上させることができます。
  3. カスタム描画
    QGraphicsViewdrawBackground()drawForeground() メソッドをオーバーライドする際に、ビューの変換状態に基づいて異なる描画を行うために利用できます。例えば、ズームされている場合は詳細なグリッドを描画し、ズームされていない場合はシンプルなグリッドを描画するなどです。
  4. デバッグ
    現在のビューがどのような変換状態にあるかをデバッグ目的で確認するために使用できます。


意図しない変換状態 (isTransformed() が true または false になる)

問題
isTransformed() の結果が、期待する値と異なる場合があります。例えば、何も変換していないはずなのに true を返したり、変換したはずなのに false を返したりするケースです。

原因とトラブルシューティング

  • fitInView() の使用

    • fitInView() を使用すると、シーン全体がビューポートに収まるように自動的にスケールされます。この場合、ビューは必ず変換された状態になり、isTransformed()true を返します。
    • これはエラーではなく、fitInView() の意図された動作です。もし fitInView() を使用していて isTransformed()true になるのが問題だと感じるなら、それは fitInView() の使い方を見直す必要があります。
  • 初期化の問題

    • QGraphicsView を作成した直後は、通常 isTransformed()false を返します。しかし、何らかの初期設定やレイアウトの調整によって、暗黙的に変換が適用される可能性もゼロではありません。
    • ビューの初期化コードを注意深く確認し、不必要な変換が適用されていないか検証してください。
    • 明示的に view->setTransform(QTransform()) のように単位行列を設定しない限り、以前の変換が残っている可能性があります。
    • 意図しない変換が適用されていないか、コード全体で setTransform(), scale(), rotate(), translate(), shear() などの変換関数が呼び出されていないか確認してください。
    • 特に、ビューをリセットしたい場合は、view->resetTransform() を呼び出すことが重要です。

変換後のアイテムの位置やサイズの不整合

問題
QGraphicsView が変換された後、QGraphicsItem の位置やサイズが期待通りに表示されない、またはマウスイベントの座標変換がおかしい。

原因とトラブルシューティング

  • QGraphicsItem::ItemIgnoresTransformations フラグ

    • 特定のアイテムがビューの変換(ズーム、回転など)の影響を受けないようにしたい場合、QGraphicsItem::setFlag(QGraphicsItem::ItemIgnoresTransformations, true) を設定できます。
    • このフラグが設定されたアイテムは、ビューが変換されてもそのサイズや向きは変化しません。この場合、isTransformed()true を返しますが、アイテムの表示は期待通りになるはずです。もしこのフラグを使っているのにアイテムが変換されてしまう場合は、フラグの設定場所やタイミングを確認してください。
  • マウスイベントの座標変換

    • QGraphicsView でマウスイベントを処理する場合、イベントの座標はビューポートのピクセル座標です。これをシーン座標に変換するには、QGraphicsView::mapToScene() を使用する必要があります。
    • 例えば、mousePressEvent(QMouseEvent *event) の中でクリックされたシーン上の位置を取得するには view->mapToScene(event->pos()) のようにします。
    • isTransformed()true であっても mapToScene() を正しく使えば、マウスイベントの座標は正しく変換されます。
  • 座標系の混同

    • QGraphicsViewQGraphicsSceneQGraphicsItem にはそれぞれ異なる座標系があります。
    • QGraphicsView の変換は、ビューポート(ウィジェットのピクセル座標)に表示されるシーンのどの部分が、どのようなスケールと回転で表示されるかを制御します。これはアイテムのローカル座標やシーン座標とは異なります。
    • QGraphicsView の変換は、シーン全体をビューポートにマッピングするための変換です。
    • アイテムの座標変換には、QGraphicsItem::mapToScene(), mapFromScene(), mapToParent(), mapFromParent(), mapToItem(), mapFromItem() などのメソッドを使用します。
    • isTransformed() はビュー自体の変換状態を示すものであり、アイテムの個々の変換状態(QGraphicsItem::transform())とは直接関係ありません。

パフォーマンスの問題

問題
QGraphicsView を頻繁に変換したり、非常に複雑なシーンを表示している場合に、描画パフォーマンスが低下する。isTransformed() 自体が直接パフォーマンスの問題を引き起こすわけではありませんが、ビューが頻繁に変換される状況では関連する問題が発生しやすくなります。

原因とトラブルシューティング

  • アンチエイリアシング

    • 変換されたビューでアンチエイリアシングを有効にすると、描画品質は向上しますが、パフォーマンスコストが増加します。
    • QGraphicsView::setRenderHint(QPainter::Antialiasing, true) などでアンチエイリアシングを有効にしている場合は、必要な場合のみ有効にするか、パフォーマンス要件に応じて調整してください。
  • 複雑なアイテムの描画

    • isTransformed()true の場合、ビューは拡大縮小されている可能性があります。このとき、小さいアイテムの描画が非常に高密度になったり、逆に非常に大きく表示されることで描画に時間がかかったりすることがあります。
    • LOD (Level of Detail) の活用
      QGraphicsItem::paint() メソッド内で QStyleOptionGraphicsItem::levelOfDetailFromTransform() を使用して、現在のズームレベルに基づいて描画の詳細度を調整します。例えば、ズームアウトされている場合はテキストを表示せず、ズームインされている場合のみ表示するなどです。
    • キャッシュモードの使用
      QGraphicsItem::setCacheMode() を使用して、頻繁に描画されるが内容が変化しないアイテムの描画をキャッシュします。これにより、アイテムが移動しても再描画のコストを削減できます。
  • 頻繁な update() や viewport()->update()

    • ビューが変換されるたびに、またはアイテムが移動するたびに、ビュー全体が再描画されるのは自然な動作です。しかし、これが過度に頻繁に起こるとパフォーマンスに影響します。
    • QGraphicsView::setViewportUpdateMode() を調整して、更新モードを最適化することを検討してください。(例: QGraphicsView::SmartViewportUpdate, QGraphicsView::MinimalViewportUpdate, QGraphicsView::FullViewportUpdate
    • 変換後、不要な update() の呼び出しを避けるようにします。

isTransformed() の誤解釈

問題
isTransformed() が「アイテムが変換されているか」を示すものだと誤解している。

原因とトラブルシューティング



例1: ビューの変換状態をチェックし、UI を更新する

この例では、QGraphicsView の変換状態が変化したときに、ステータスバーのテキストを更新します。

#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QStatusBar>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>

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

public:
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        // 変換が変更されたときに通知を受け取るために、
        // QGraphicsViewのprotectedな仮想関数をオーバーライドするか、
        // 別の方法で変換の変更を検知する必要があります。
        // 単純な例として、ボタンクリックで状態をチェックします。
    }

signals:
    void transformationChanged(bool isTransformed);

public slots:
    // ボタンクリックなどで呼び出されるスロット
    void checkTransformationStatus()
    {
        bool transformed = isTransformed();
        emit transformationChanged(transformed);
        qDebug() << "Current view transformed status: " << transformed;
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr)
        : QMainWindow(parent)
    {
        setWindowTitle("QGraphicsView isTransformed() Example");

        // シーンの作成
        QGraphicsScene *scene = new QGraphicsScene(this);
        scene->setSceneRect(-100, -100, 200, 200); // シーンの範囲を設定

        // アイテムの追加
        QGraphicsRectItem *rect = scene->addRect(-50, -50, 100, 100, QPen(Qt::blue), QBrush(Qt::cyan));
        rect->setFlag(QGraphicsItem::ItemIsMovable); // アイテムを移動可能にする

        // ビューの作成
        MyGraphicsView *view = new MyGraphicsView(scene, this);
        view->setRenderHint(QPainter::Antialiasing); // アンチエイリアシングを有効にする

        // ボタンの作成
        QPushButton *zoomInBtn = new QPushButton("Zoom In");
        connect(zoomInBtn, &QPushButton::clicked, [view]() {
            view->scale(1.2, 1.2); // 1.2倍に拡大
            static_cast<MyGraphicsView*>(view)->checkTransformationStatus(); // 状態をチェック
        });

        QPushButton *zoomOutBtn = new QPushButton("Zoom Out");
        connect(zoomOutBtn, &QPushButton::clicked, [view]() {
            view->scale(1.0 / 1.2, 1.0 / 1.2); // 1.2倍に縮小
            static_cast<MyGraphicsView*>(view)->checkTransformationStatus(); // 状態をチェック
        });

        QPushButton *rotateBtn = new QPushButton("Rotate 45 deg");
        connect(rotateBtn, &QPushButton::clicked, [view]() {
            view->rotate(45); // 45度回転
            static_cast<MyGraphicsView*>(view)->checkTransformationStatus(); // 状態をチェック
        });

        QPushButton *resetBtn = new QPushButton("Reset Transform");
        connect(resetBtn, &QPushButton::clicked, [view]() {
            view->resetTransform(); // 変換をリセット
            static_cast<MyGraphicsView*>(view)->checkTransformationStatus(); // 状態をチェック
        });

        // レイアウトの設定
        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(view);
        layout->addWidget(zoomInBtn);
        layout->addWidget(zoomOutBtn);
        layout->addWidget(rotateBtn);
        layout->addWidget(resetBtn);

        QWidget *centralWidget = new QWidget;
        centralWidget->setLayout(layout);
        setCentralWidget(centralWidget);

        // ステータスバーの設定
        statusBar()->showMessage("View is not transformed.");

        // MyGraphicsViewからのシグナルをStatusBarのスロットに接続
        connect(view, &MyGraphicsView::transformationChanged, this, &MainWindow::updateStatusMessage);

        // 初期状態をチェック
        view->checkTransformationStatus();
    }

private slots:
    void updateStatusMessage(bool transformed)
    {
        if (transformed) {
            statusBar()->showMessage("View IS transformed.");
        } else {
            statusBar()->showMessage("View IS NOT transformed.");
        }
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

#include "main.moc" // mocファイルを含める

説明

  1. MyGraphicsView クラスは QGraphicsView を継承しています。ここでは、変換状態をチェックするためのスロット checkTransformationStatus() と、その結果を通知するためのシグナル transformationChanged(bool) を追加しています。
  2. MainWindow では、ズームイン、ズームアウト、回転、リセットの各ボタンを作成し、それぞれビューの対応する変換メソッドを呼び出しています。
  3. 各変換メソッドの呼び出し後、static_cast<MyGraphicsView*>(view)->checkTransformationStatus(); を呼び出して、現在の変換状態をチェックし、transformationChanged シグナルを発行しています。
  4. MainWindowupdateStatusMessage スロットは、このシグナルを受け取り、isTransformed() の結果に基づいてステータスバーのメッセージを更新します。

この例を実行し、ボタンをクリックしていくと、ステータスバーのメッセージが View IS transformed.View IS NOT transformed. の間で切り替わるのが確認できます。特に「Reset Transform」ボタンをクリックすると、isTransformed()false に戻ることを確認できます。

この例では、QGraphicsView が変換されているかどうかによって、背景の描画内容を変える方法を示します。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QPainter>
#include <QDebug>
#include <QMouseEvent> // マウスホイールイベントを捕捉するため

class CustomGraphicsView : public QGraphicsView
{
public:
    CustomGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        setRenderHint(QPainter::Antialiasing);
        setMouseTracking(true); // マウスイベントを有効にする
        setDragMode(QGraphicsView::ScrollHandDrag); // マウスドラッグでスクロールできるようにする
    }

protected:
    void drawBackground(QPainter *painter, const QRectF &rect) override
    {
        QGraphicsView::drawBackground(painter, rect); // デフォルトの背景描画を呼び出す

        painter->save();

        // ビューが変換されているかどうかをチェック
        if (isTransformed()) {
            // ビューが変換されている場合(ズームされている場合など)
            // より詳細なグリッドを描画する
            qDebug() << "Drawing detailed background (view is transformed)";
            painter->setPen(QPen(Qt::lightGray, 0.5));
            // 細かいグリッドを描画
            for (qreal x = rect.left(); x < rect.right(); x += 10) {
                painter->drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom()));
            }
            for (qreal y = rect.top(); y < rect.bottom(); y += 10) {
                painter->drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y));
            }
        } else {
            // ビューが変換されていない場合(デフォルトのズームレベル)
            // シンプルなグリッドを描画する
            qDebug() << "Drawing simple background (view is NOT transformed)";
            painter->setPen(QPen(Qt::darkGray, 1));
            // 粗いグリッドを描画
            for (qreal x = rect.left(); x < rect.right(); x += 50) {
                painter->drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom()));
            }
            for (qreal y = rect.top(); y < rect.bottom(); y += 50) {
                painter->drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y));
            }
        }
        painter->restore();
    }

    void wheelEvent(QWheelEvent *event) override
    {
        // マウスホイールでズームイン/アウト
        qreal scaleFactor = 1.15;
        if (event->angleDelta().y() > 0) {
            // スクロールアップ (ズームイン)
            scale(scaleFactor, scaleFactor);
        } else {
            // スクロールダウン (ズームアウト)
            scale(1.0 / scaleFactor, 1.0 / scaleFactor);
        }
        // 親クラスのホイールイベント処理も呼び出す (必要であれば)
        // QGraphicsView::wheelEvent(event); // ここでは不要
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400); // シーンの範囲

    QGraphicsRectItem *rect = scene.addRect(-50, -50, 100, 100, QPen(Qt::red), QBrush(Qt::yellow));
    rect->setFlag(QGraphicsItem::ItemIsMovable);

    CustomGraphicsView view(&scene);
    view.setWindowTitle("Conditional Background Drawing");
    view.setFixedSize(600, 400); // ビューのサイズを固定

    view.show();

    return a.exec();
}

説明

  1. CustomGraphicsView を作成し、drawBackground() メソッドをオーバーライドします。
  2. drawBackground() の中で isTransformed() を呼び出し、ビューが変換されているかどうかをチェックします。
  3. isTransformed()true の場合(マウスホイールでズームインした場合など)、より細かいグリッド線を描画します。
  4. isTransformed()false の場合(初期状態や、ズームアウトして元のスケールに戻った場合)、より粗いグリッド線を描画します。
  5. wheelEvent() をオーバーライドして、マウスホイールで簡単にズームイン/アウトできるようにしています。これにより、isTransformed() の結果が変化するのを視覚的に確認できます。

この例を実行し、マウスホイールをスクロールしてズームレベルを変えてみてください。ビューがズームされるとグリッドが細かくなり(isTransformed()true)、ズームアウトして元のサイズに戻るとグリッドが粗くなる(isTransformed()false)のが確認できます。



QGraphicsView::isTransformed() の代替方法

主な代替手段は、ビューの現在の変換行列 (QTransform) を直接取得し、そのプロパティを分析することです。

QGraphicsView::transform() を使用して変換行列を直接調べる

QGraphicsView::transform() メソッドは、ビューに適用されている現在の QTransform オブジェクトを返します。この QTransform オブジェクトを調べることで、ビューの変換に関する詳細な情報を取得できます。

特徴

  • isTransformed() よりも詳細な情報(具体的なスケール値、回転角度、平行移動量など)を取得できます。
  • ビューに適用されているスケール、回転、平行移動、せん断などのすべての変換情報が含まれています。

コード例

#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
#include <QTransform>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr)
        : QMainWindow(parent)
    {
        setWindowTitle("QGraphicsView Transform Analysis Example");

        QGraphicsScene *scene = new QGraphicsScene(this);
        scene->setSceneRect(-100, -100, 200, 200);

        scene->addRect(-50, -50, 100, 100, QPen(Qt::blue), QBrush(Qt::cyan));

        QGraphicsView *view = new QGraphicsView(scene, this);
        view->setRenderHint(QPainter::Antialiasing);

        QPushButton *zoomInBtn = new QPushButton("Zoom In");
        connect(zoomInBtn, &QPushButton::clicked, [view, this]() {
            view->scale(1.2, 1.2);
            analyzeTransform(view);
        });

        QPushButton *rotateBtn = new QPushButton("Rotate 30 deg");
        connect(rotateBtn, &QPushButton::clicked, [view, this]() {
            view->rotate(30);
            analyzeTransform(view);
        });

        QPushButton *translateBtn = new QPushButton("Translate (20, 20)");
        connect(translateBtn, &QPushButton::clicked, [view, this]() {
            view->translate(20, 20);
            analyzeTransform(view);
        });

        QPushButton *resetBtn = new QPushButton("Reset Transform");
        connect(resetBtn, &QPushButton::clicked, [view, this]() {
            view->resetTransform();
            analyzeTransform(view);
        });

        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(view);
        layout->addWidget(zoomInBtn);
        layout->addWidget(rotateBtn);
        layout->addWidget(translateBtn);
        layout->addWidget(resetBtn);

        QWidget *centralWidget = new QWidget;
        centralWidget->setLayout(layout);
        setCentralWidget(centralWidget);

        analyzeTransform(view); // 初期状態を分析
    }

private:
    void analyzeTransform(QGraphicsView *view)
    {
        QTransform currentTransform = view->transform();

        // isIdentity() を使用して、isTransformed() と同様のチェックを行う
        if (currentTransform.isIdentity()) {
            qDebug() << "View is NOT transformed (identity matrix).";
        } else {
            qDebug() << "View IS transformed.";
        }

        // 個別の変換要素を取得する
        qDebug() << "Scale X:" << currentTransform.m11(); // X軸スケール要素
        qDebug() << "Scale Y:" << currentTransform.m22(); // Y軸スケール要素
        qDebug() << "Translation X:" << currentTransform.dx(); // X軸平行移動要素
        qDebug() << "Translation Y:" << currentTransform.dy(); // Y軸平行移動要素

        // 回転角度を計算する (QTransform::rotation() は直接提供されないため)
        // 注意: 回転とスケールが同時に適用されている場合、単純な m11() や m22() は正確なスケール値ではない可能性があります。
        // 回転がある場合の正確なスケール値の取得は少し複雑になります。
        // 例: sqrt(m11*m11 + m12*m12) でX軸スケール、sqrt(m21*m21 + m22*m22) でY軸スケール
        qreal scaleX = std::sqrt(currentTransform.m11() * currentTransform.m11() + currentTransform.m12() * currentTransform.m12());
        qreal scaleY = std::sqrt(currentTransform.m21() * currentTransform.m21() + currentTransform.m22() * currentTransform.m22());
        qDebug() << "Calculated Scale X:" << scaleX;
        qDebug() << "Calculated Scale Y:" << scaleY;

        // 回転角度の取得 (アークタンジェントを使用)
        // rad = atan2(m12, m11) または atan2(-m21, m22)
        // m12 は sin(angle) * scaleX, m11 は cos(angle) * scaleX
        qreal angleRad = std::atan2(currentTransform.m12(), currentTransform.m11());
        qreal angleDeg = qRadiansToDegrees(angleRad);
        qDebug() << "Calculated Rotation Angle (degrees):" << angleDeg;

        qDebug() << "------------------------------------";
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

#include "main.moc"

QTransform の主要なメソッド

  • QTransform::map()QTransform::inverted() など、変換を適用したり逆変換を取得したりするメソッド。
  • QTransform::determinant(): 行列式を計算します。行列式が0でない場合は可逆変換であり、QGraphicsView の変換としては通常0ではありません。
  • QTransform::dx(), dy(): 平行移動 (translation) 成分を取得します。
  • QTransform::m31(), m32(), m33(): 行列の3行目の要素(主に透視変換用。2Dアフィン変換では m31()=dx(), m32()=dy(), m33()=1
  • QTransform::m21(), m22(), m23(): 行列の2行目の要素
  • QTransform::m11(), m12(), m13(): 行列の1行目の要素(スケール、せん断など)
  • QTransform::isIdentity(): 単位行列かどうかを返します (isTransformed() と同じ目的で使える)。

注意点

  • 特に、非等方性スケール(X軸とY軸で異なるスケール)と回転が同時に適用されている場合、単純に m11m22 をスケールとして見ることはできません。上記のコード例では、一般的なケースに対応するための計算方法を示しています。
  • QTransform から回転角度や正確なスケール値を抽出するのは、行列の要素が複数の変換を組み合わせた結果であるため、常に単純ではありません。例えば、X軸とY軸で異なるスケールが適用されている場合や、せん断変換が適用されている場合、回転角度の計算は複雑になります。

独自のフラグまたは状態変数を使用する

QGraphicsView の変換メソッド(scale(), rotate(), translate(), setTransform() など)を呼び出す際に、独自のブール型フラグや列挙型変数、あるいはカスタムのスケールファクターや回転角度をクラスのメンバーとして保持する方法です。

特徴

  • ビューがどの程度変換されているか(例えば、現在のズームレベル)を数値で直接管理できます。
  • isTransformed() の結果が「期待通りではない」と感じるような、特定のアプリケーション固有のロジックを実装したい場合に有用です。

コード例

#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QStatusBar>
#include <QDebug>

class CustomTrackingGraphicsView : public QGraphicsView
{
    Q_OBJECT

public:
    CustomTrackingGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent),
          m_currentScale(1.0),
          m_currentRotation(0.0),
          m_isManuallyTransformed(false) // カスタムフラグ
    {
    }

    // QGraphicsViewの変換メソッドをオーバーライドして状態を更新
    void scale(qreal sx, qreal sy) override
    {
        QGraphicsView::scale(sx, sy);
        m_currentScale *= sx; // スケールファクターを累積
        m_isManuallyTransformed = true; // 変換されたとマーク
        emit viewStateChanged();
    }

    void rotate(qreal angle) override
    {
        QGraphicsView::rotate(angle);
        m_currentRotation += angle; // 回転角度を累積
        // 360度を超えないように正規化することも可能
        while (m_currentRotation >= 360.0) m_currentRotation -= 360.0;
        while (m_currentRotation < 0.0) m_currentRotation += 360.0;

        m_isManuallyTransformed = true;
        emit viewStateChanged();
    }

    void translate(qreal dx, qreal dy) override
    {
        QGraphicsView::translate(dx, dy);
        // 平行移動量は累積することもできますが、ここでは単純化
        m_isManuallyTransformed = true;
        emit viewStateChanged();
    }

    void resetTransform() override
    {
        QGraphicsView::resetTransform();
        m_currentScale = 1.0;
        m_currentRotation = 0.0;
        m_isManuallyTransformed = false; // リセットされたのでfalse
        emit viewStateChanged();
    }

    // カスタムフラグへのアクセス
    bool isManuallyTransformed() const { return m_isManuallyTransformed; }

    // 現在のスケールファクターを取得
    qreal currentScale() const { return m_currentScale; }

    // 現在の回転角度を取得
    qreal currentRotation() const { return m_currentRotation; }

signals:
    void viewStateChanged(); // 状態変更を通知するシグナル

private:
    qreal m_currentScale;
    qreal m_currentRotation;
    bool m_isManuallyTransformed;
};

class MainAppWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainAppWindow(QWidget *parent = nullptr)
        : QMainWindow(parent)
    {
        setWindowTitle("Custom Transform Tracking Example");

        QGraphicsScene *scene = new QGraphicsScene(this);
        scene->setSceneRect(-100, -100, 200, 200);
        scene->addRect(-50, -50, 100, 100, QPen(Qt::blue), QBrush(Qt::cyan));

        CustomTrackingGraphicsView *view = new CustomTrackingGraphicsView(scene, this);
        view->setRenderHint(QPainter::Antialiasing);

        QPushButton *zoomInBtn = new QPushButton("Zoom In");
        connect(zoomInBtn, &QPushButton::clicked, [view]() { view->scale(1.2, 1.2); });

        QPushButton *rotateBtn = new QPushButton("Rotate 30 deg");
        connect(rotateBtn, &QPushButton::clicked, [view]() { view->rotate(30); });

        QPushButton *resetBtn = new QPushButton("Reset Transform");
        connect(resetBtn, &QPushButton::clicked, [view]() { view->resetTransform(); });

        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(view);
        layout->addWidget(zoomInBtn);
        layout->addWidget(rotateBtn);
        layout->addWidget(resetBtn);

        QWidget *centralWidget = new QWidget;
        centralWidget->setLayout(layout);
        setCentralWidget(centralWidget);

        statusBar()->showMessage("Initial View State.");

        // カスタムビューのシグナルを接続
        connect(view, &CustomTrackingGraphicsView::viewStateChanged, this, &MainAppWindow::updateStatusBar);
        view->viewStateChanged(); // 初期状態を反映
    }

private slots:
    void updateStatusBar()
    {
        CustomTrackingGraphicsView *view = qobject_cast<CustomTrackingGraphicsView*>(sender());
        if (view) {
            QString status = "Transformed: " + QString(view->isManuallyTransformed() ? "YES" : "NO");
            status += QString(" | Scale: %1").arg(view->currentScale(), 0, 'f', 2);
            status += QString(" | Rotation: %1 deg").arg(view->currentRotation(), 0, 'f', 2);
            statusBar()->showMessage(status);
        }
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainAppWindow w;
    w.show();
    return a.exec();
}

#include "main.moc"
  • QGraphicsView::fitInView() のような、内部的に複雑な変換を行うメソッドを使用した場合は、これらのカスタム変数を手動で更新する必要があるかもしれません。fitInView() の後で QTransform を取得し、それを元にカスタム変数を再計算するなどのロジックが必要です。
  • この方法は、QGraphicsView の変換を独自に管理する場合にのみ適用されます。
  • 独自のフラグ/変数を使う場合

    • 特定のユースケースで「変換された」という状態をより厳密に定義したい場合(例: ユーザーが手動でズームした場合のみ「変換済み」と見なす)。
    • QGraphicsView の変換メソッドを常に呼び出す場所を制御でき、それらの呼び出しと同時にカスタム状態を更新できる場合。
    • 特定のズームレベルや回転角度をアプリケーションロジック内で頻繁に参照する必要がある場合。
  • QGraphicsView::transform() を使う場合

    • isTransformed() が返す「変換されているか否か」以上の詳細な情報(具体的なスケール値、回転角度、平行移動量など)が必要な場合。
    • ビューの変換を完全に QGraphicsView に任せ、必要に応じてその状態を「読み取る」だけで十分な場合。
    • パフォーマンスがクリティカルで、独自の変数管理のオーバーヘッドを避けたい場合。