QGraphicsView::setScene()

2025-05-27

Qtの「Graphics View Framework」は、大量の2Dグラフィックアイテムを管理、表示、操作するための強力なシステムです。このフレームワークは主に以下の3つのクラスで構成されます。

  1. QGraphicsScene: これは、描画するグラフィックアイテム(図形、テキスト、画像など)の集合を保持する論理的なコンテナです。アイテムの追加、削除、イベント処理(マウスイベントなど)の管理、アイテム間の衝突検出などを担当します。

  2. QGraphicsView: これは、QGraphicsScene の内容を視覚的に表示するためのウィジェットです。スクロールバー、ズーム、回転などの変換操作を提供し、ユーザーがシーンの内容を操作できるようにします。QGraphicsSceneQGraphicsView は密接に関連していますが、分離されています。一つのシーンを複数のビューで表示したり、一つのビューで異なるシーンを切り替えて表示したりすることが可能です。

  3. QGraphicsItem: シーン内に配置される個々のグラフィックオブジェクトの基本クラスです。例えば、QGraphicsRectItemQGraphicsEllipseItemQGraphicsTextItemQGraphicsPixmapItem などがあります。

QGraphicsView::setScene() の役割

QGraphicsView::setScene(QGraphicsScene *scene) 関数は、特定の QGraphicsView インスタンスがどの QGraphicsScene の内容を表示するかを指定するために使用されます。

具体的には:

  • 初期設定: QGraphicsView を作成した後、表示したい QGraphicsScene を関連付けるために最初に呼び出すことが多いです。
  • 表示内容の切り替え: アプリケーションの実行中に、表示するグラフィック内容を切り替えたい場合(例:異なる図面を表示する、ゲームのレベルを変更する)に、この関数を使って新しい QGraphicsScene オブジェクトを QGraphicsView に設定します。
  • ビューとシーンの関連付け: この関数を呼び出すことで、QGraphicsView は指定された QGraphicsScene の内容をそのビューポート(表示領域)に描画し始めます。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

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

    // 1. シーンを作成
    QGraphicsScene *scene = new QGraphicsScene();
    scene->setSceneRect(0, 0, 200, 200); // シーンの境界を設定

    // 2. シーンにアイテムを追加
    QGraphicsRectItem *rect = new QGraphicsRectItem(50, 50, 100, 100);
    rect->setBrush(Qt::blue);
    scene->addItem(rect);

    // 3. ビューを作成
    QGraphicsView *view = new QGraphicsView();

    // 4. QGraphicsView::setScene() を使ってビューにシーンを設定
    view->setScene(scene);

    // 5. ビューを表示
    view->setWindowTitle("QGraphicsView::setScene() の例");
    view->resize(300, 300);
    view->show();

    // 別のシーンに切り替える例(必要であれば)
    // QGraphicsScene *anotherScene = new QGraphicsScene();
    // anotherScene->addText("Hello from another scene!");
    // view->setScene(anotherScene); // ビューに別のシーンを設定

    return a.exec();
}

この例では、まず QGraphicsScene を作成し、その中に青い四角形を追加しています。次に、QGraphicsView を作成し、view->setScene(scene); を呼び出すことで、このビューが作成したシーンの内容を表示するように設定しています。



シーンが表示されない、または空のビューが表示される

考えられる原因

  • アイテムの描画設定が間違っている
    アイテムが透明になっている、または背景と同じ色になっているなど、描画設定が原因で視認できない場合があります。
  • ビューのサイズが小さすぎる
    ビューが非常に小さく、シーンの内容が表示しきれていない可能性があります。
  • ビューが適切に表示されていない
    QGraphicsView 自体が show() されていない、または他のウィジェットに隠れている可能性があります。
  • アイテムがビューポート外に配置されている
    アイテムがシーン内の表示可能領域(sceneRect)の外に配置されている場合、ビューに表示されません。
  • シーンのサイズ(sceneRect)が適切に設定されていない
    QGraphicsScene::sceneRect() はシーンの境界を定義します。この境界がアイテムの範囲外にある場合、アイテムがビューポートに収まらないことがあります。特に、アイテムを追加する前に sceneRect を設定しないと、QGraphicsScene は自動的にすべてのアイテムをカバーする sceneRect を計算しますが、これが意図しない結果になることがあります。
  • QGraphicsScene が作成されていない、またはアイテムが追加されていない
    setScene() を呼び出しても、設定された QGraphicsScene にアイテムが一つも追加されていなければ、当然ながらビューは何も表示しません。

トラブルシューティング

  • 簡単なアイテムから始める
    まずは QGraphicsRectItem のようなシンプルなアイテムを数個追加して表示されるか確認し、その後複雑なアイテムに進むのが良いでしょう。
  • ビューの表示とサイズを確認
    view->show() が呼び出されているか、view->resize() で十分なサイズが確保されているか確認します。
  • アイテムの可視性を確認
    item->isVisible()true であることを確認します。
  • setSceneRect() の明示的な設定
    QGraphicsScene を作成したら、初期段階で適切な setSceneRect() を設定することをお勧めします。
    QGraphicsScene *scene = new QGraphicsScene();
    scene->setSceneRect(0, 0, 800, 600); // 例: 幅800、高さ600のシーン
    
  • qDebug() を使用して確認
    • qDebug() << "Scene item count: " << scene->items().count(); でシーンにアイテムが追加されているか確認します。
    • qDebug() << "Scene rect: " << scene->sceneRect(); でシーンの境界を確認します。
    • qDebug() << "View rect: " << view->rect(); でビューのサイズを確認します。
    • 各アイテムの pos()boundingRect() を確認し、シーン内で適切な位置にあるか確かめます。

メモリリーク(QGraphicsScene や QGraphicsView の解放忘れ)

考えられる原因

  • QGraphicsViewQGraphicsScene がヒープに作成されたにも関わらず、適切なタイミングで delete されない。
  • setScene() に渡された QGraphicsScene オブジェクトが delete されないまま、アプリケーションが終了したり、別のシーンに切り替えられたりする場合。

トラブルシューティング

  • QObjectの親子関係
    親を持たない QGraphicsSceneQGraphicsView は、アプリケーション終了時に手動で delete するか、QApplication が自動的にクリーンアップするのを待つことになります。しかし、明示的に delete するのが最も安全です。
    // main関数内など
    QGraphicsScene *scene = new QGraphicsScene();
    QGraphicsView *view = new QGraphicsView();
    view->setScene(scene);
    
    // アプリケーション終了時や、不要になった時に
    // delete view; // viewを削除すると、その子ウィジェットも削除されます。
    // delete scene; // シーンはビューの子ではないため、別途削除が必要です。
    
    ただし、QGraphicsView のデストラクタが QGraphicsScene を自動的に削除することはありません。したがって、QGraphicsView に新しいシーンを setScene() で設定し直す場合、古いシーンは手動で delete する必要があります。
  • スマートポインタの利用
    C++11以降の std::unique_ptrstd::shared_ptr を使用して、オブジェクトのライフサイクルを自動で管理することを検討してください。
    std::unique_ptr<QGraphicsScene> scene(new QGraphicsScene());
    // ... scene->addItem(...)
    view->setScene(scene.get()); // rawポインタを渡す
    // sceneはviewが破棄される時に一緒に破棄されるわけではないので注意が必要
    // viewが別のシーンをセットされたり、view自体が破棄されたりしても
    // sceneはunique_ptrがスコープを抜ける時に自動的に解放される
    
  • 所有権の管理
    Qtのオブジェクトは通常、親オブジェクトを設定することで自動的にメモリ管理されます。しかし、QGraphicsSceneQGraphicsView はトップレベルで作成されることが多いため、手動での delete が必要になる場合があります。

シーンのアイテムがクリックイベントなどに反応しない

考えられる原因

  • アイテムが他のアイテムに隠れている
    Z-order(重なり順)が原因で、クリックしたいアイテムが他の透明なアイテムや不透明なアイテムの下に隠れている可能性があります。
  • イベントフィルタリング
    ビューやシーン、または他のアイテムがイベントをフィルタリングしてしまい、目的のアイテムにイベントが届かない。
  • アイテムがフォーカス可能(ItemIsFocusable)でない
    キーボードイベントなどに反応させたい場合。
  • アイテムが選択可能(ItemIsSelectable)でない
    選択状態にしたい場合。
  • アイテムが flags を持っていない
    アイテムがクリックイベントに反応するには、QGraphicsItem::ItemIsSelectableQGraphicsItem::ItemIsMovable などの適切なフラグが設定されている必要があります。

トラブルシューティング

  • itemAt() や items() でクリック位置のアイテムを調査
    QGraphicsScene::itemAt()QGraphicsView::mapToScene() を使って、クリックされたシーン座標に実際にどのアイテムが存在するかを確認します。
    void MyView::mousePressEvent(QMouseEvent *event) {
        QGraphicsItem *item = scene()->itemAt(mapToScene(event->pos()), QTransform());
        if (item) {
            qDebug() << "Clicked on item: " << item;
        }
        QGraphicsView::mousePressEvent(event);
    }
    
  • setZValue() で重なり順を調整
    item->setZValue(1.0); などで、Z値を調整してアイテムの描画順序を変更し、手前に表示されるようにします。
  • setAcceptedMouseButtons() の確認
    アイテムがどのマウスボタンを受け入れるか設定します。デフォルトではすべて受け入れます。
  • 適切なフラグを設定
    アイテムを作成した後、必要なフラグを設定します。
    rect->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
    

シーンの座標系とビューの座標系の混乱

考えられる原因

  • QGraphicsView::mapToScene(), QGraphicsView::mapFromScene(), QGraphicsScene::mapToView(), QGraphicsScene::mapFromView() といった変換関数を誤って使用している。
  • QGraphicsScene は独自の座標系を持ちます。QGraphicsView はこのシーンの座標系を、ビューポートのピクセル座標系に変換して表示します。この変換(ズーム、回転、平行移動)を理解していないと、意図しない表示になることがあります。

トラブルシューティング

  • QGraphicsView::fitInView() の利用
    シーン全体をビューに収めるには fitInView() を使用します。
    view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
    
  • QGraphicsView::setRenderHint()
    アンチエイリアシングなどのレンダリングヒントを設定することで、描画品質が向上し、見た目の問題が解決することがあります。
    view->setRenderHint(QPainter::Antialiasing);
    view->setRenderHint(QPainter::SmoothPixmapTransform);
    
  • 変換関数の理解
    各変換関数の役割を正確に理解します。
    • mapToScene(QPoint viewPoint): ビューのピクセル座標をシーンの座標に変換。
    • mapFromScene(QPointF scenePoint): シーンの座標をビューのピクセル座標に変換。

スレッドセーフティの問題

考えられる原因

  • QtのGUI操作はメインスレッドで行う必要があります。QGraphicsSceneQGraphicsView の操作を、メインスレッド以外のスレッドから直接行おうとすると、クラッシュや未定義動作の原因となります。
  • シグナル/スロットの利用
    バックグラウンドスレッドで計算やデータ処理を行い、GUIの更新が必要になった場合は、QObject::connect() を使ってシグナル/スロット機構でメインスレッドに処理を依頼します。
    // ワーカースレッドから
    emit updateGuiSignal(data);
    
    // メインスレッドのMyWidgetで
    connect(workerThread, &WorkerThread::updateGuiSignal, this, &MyWidget::updateGraphics);
    void MyWidget::updateGraphics(const QVariant &data) {
        // ここでQGraphicsSceneやQGraphicsViewを操作
    }
    


例1:最も基本的な setScene() の使用

この例では、QGraphicsView に単一の QGraphicsScene を設定し、そのシーンに長方形のアイテムを追加して表示します。

// main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem> // 長方形アイテム用
#include <QDebug> // デバッグ出力用

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

    // 1. QGraphicsScene を作成します。
    // シーンはグラフィックアイテムの論理的なコンテナです。
    QGraphicsScene *scene = new QGraphicsScene();

    // シーンの境界を設定します。これにより、スクロールバーの範囲などが決まります。
    // (x, y, width, height)
    scene->setSceneRect(0, 0, 400, 300);

    // 2. シーンにグラフィックアイテムを追加します。
    // ここでは青い長方形を作成します。(x, y, width, height)
    QGraphicsRectItem *rectItem = new QGraphicsRectItem(50, 50, 100, 100);
    rectItem->setBrush(QBrush(Qt::blue)); // 青色で塗りつぶし
    scene->addItem(rectItem); // シーンにアイテムを追加

    // もう一つ、赤い円を作成してみましょう
    QGraphicsEllipseItem *ellipseItem = new QGraphicsEllipseItem(200, 100, 80, 80);
    ellipseItem->setBrush(QBrush(Qt::red));
    scene->addItem(ellipseItem);

    // 3. QGraphicsView を作成します。
    // ビューはシーンの内容を視覚的に表示するウィジェットです。
    QGraphicsView *view = new QGraphicsView();

    // 4. QGraphicsView::setScene() を使って、ビューにシーンを設定します。
    // これにより、ビューがどのシーンの内容を表示するかが決まります。
    view->setScene(scene);

    // 5. ビューの表示設定
    view->setWindowTitle("基本の QGraphicsView::setScene() 例");
    view->resize(600, 400); // ビューのウィンドウサイズを設定
    view->show(); // ウィンドウを表示

    // アプリケーションのイベントループを開始
    return a.exec();
}

解説
このコードは、QGraphicsScene を作成し、その中にいくつかの図形アイテムを追加します。次に QGraphicsView を作成し、view->setScene(scene); を呼び出すことで、このビューが作成したシーンの内容を表示するように設定します。これは QGraphicsView を使う上で最も基本的なパターンです。

例2:実行時にシーンを切り替える

この例では、ボタンをクリックすることで QGraphicsView が表示する QGraphicsScene を切り替える方法を示します。

// mywindow.h
#ifndef MYWINDOW_H
#define MYWINDOW_H

#include <QWidget>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPushButton>
#include <QVBoxLayout>
#include <QGraphicsRectItem>
#include <QGraphicsTextItem>

class MyWindow : public QWidget
{
    Q_OBJECT

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

private slots:
    void switchScene();

private:
    QGraphicsView *m_view;
    QGraphicsScene *m_scene1;
    QGraphicsScene *m_scene2;
    QPushButton *m_switchButton;
    bool m_isScene1Active;

    void setupScene1();
    void setupScene2();
};

#endif // MYWINDOW_H
// mywindow.cpp
#include "mywindow.h"

MyWindow::MyWindow(QWidget *parent)
    : QWidget(parent),
      m_isScene1Active(true) // 初期状態はシーン1を表示
{
    // ビューの作成
    m_view = new QGraphicsView(this);

    // シーン1の作成と設定
    m_scene1 = new QGraphicsScene(this); // 親をMyWindowに設定してメモリ管理を任せる
    m_scene1->setSceneRect(0, 0, 300, 200);
    setupScene1();

    // シーン2の作成と設定
    m_scene2 = new QGraphicsScene(this);
    m_scene2->setSceneRect(0, 0, 300, 200);
    setupScene2();

    // 初期シーンとしてシーン1を設定
    m_view->setScene(m_scene1);

    // 切り替えボタンの作成
    m_switchButton = new QPushButton("シーン切り替え", this);
    connect(m_switchButton, &QPushButton::clicked, this, &MyWindow::switchScene);

    // レイアウトの設定
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(m_view);
    layout->addWidget(m_switchButton);
    setLayout(layout);

    setWindowTitle("QGraphicsView::setScene() 切り替え例");
    resize(400, 400);
}

MyWindow::~MyWindow()
{
    // QGraphicsSceneの親が設定されているので、MyWindowが破棄されるときに自動的に破棄されます。
    // ただし、もし親が設定されていない場合は手動で delete m_scene1; delete m_scene2; が必要です。
}

void MyWindow::setupScene1()
{
    QGraphicsRectItem *rect = new QGraphicsRectItem(50, 50, 100, 100);
    rect->setBrush(Qt::green);
    m_scene1->addItem(rect);
    QGraphicsTextItem *text = new QGraphicsTextItem("これはシーン1です");
    text->setPos(10, 10);
    m_scene1->addItem(text);
}

void MyWindow::setupScene2()
{
    QGraphicsEllipseItem *ellipse = new QGraphicsEllipseItem(80, 80, 120, 120);
    ellipse->setBrush(Qt::magenta);
    m_scene2->addItem(ellipse);
    QGraphicsTextItem *text = new QGraphicsTextItem("これはシーン2です");
    text->setPos(10, 10);
    m_scene2->addItem(text);
}

void MyWindow::switchScene()
{
    if (m_isScene1Active) {
        m_view->setScene(m_scene2);
        m_isScene1Active = false;
        m_switchButton->setText("シーン1へ切り替え");
    } else {
        m_view->setScene(m_scene1);
        m_isScene1Active = true;
        m_switchButton->setText("シーン2へ切り替え");
    }
}
// main.cpp
#include <QApplication>
#include "mywindow.h"

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

解説
この例では、MyWindow クラス内に2つの QGraphicsScene (m_scene1m_scene2) を持ちます。ボタンのクリックイベントに応じて switchScene() スロットが呼び出され、その中で m_view->setScene(m_scene2); または m_view->setScene(m_scene1); を実行して、ビューが現在表示しているシーンを動的に切り替えています。

この例では、QGraphicsView の変換機能 (transform()) を利用して、シーンのズームとパン(ドラッグによる移動)を可能にします。setScene() 自体は直接ズームやパンに関与しませんが、ビューの変換はシーンの表示方法に影響するため、一緒によく使われます。

// zoomableview.h
#ifndef ZOOMABLEVIEW_H
#define ZOOMABLEVIEW_H

#include <QGraphicsView>
#include <QMouseEvent>
#include <QWheelEvent>

class ZoomableGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit ZoomableGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);

protected:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private:
    QPoint m_lastPanPoint; // パン操作の開始点
};

#endif // ZOOMABLEVIEW_H
// zoomableview.cpp
#include "zoomableview.h"
#include <QScrollBar> // スクロールバー制御用
#include <QtMath>     // qPow用

ZoomableGraphicsView::ZoomableGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    // ドラッグによるパンを可能にするために、ドラッグモードを設定
    setDragMode(QGraphicsView::ScrollHandDrag);

    // アンチエイリアシングを有効にして描画品質を向上
    setRenderHint(QPainter::Antialiasing);
    setRenderHint(QPainter::SmoothPixmapTransform);
}

void ZoomableGraphicsView::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        // 左クリックでパンを開始する場合
        m_lastPanPoint = event->pos(); // 現在のマウス位置を記録
        setCursor(Qt::ClosedHandCursor); // カーソルを閉じた手に変更
        event->accept();
    } else {
        QGraphicsView::mousePressEvent(event); // デフォルトの処理
    }
}

void ZoomableGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) { // 左ボタンが押されている場合
        QPoint delta = event->pos() - m_lastPanPoint; // マウスの移動量
        m_lastPanPoint = event->pos(); // 現在のマウス位置を更新

        // シーンを移動
        // 垂直スクロールバーと水平スクロールバーを直接操作
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x());
        verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y());
        event->accept();
    } else {
        QGraphicsView::mouseMoveEvent(event); // デフォルトの処理
    }
}

void ZoomableGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        setCursor(Qt::ArrowCursor); // カーソルを元に戻す
        event->accept();
    } else {
        QGraphicsView::mouseReleaseEvent(event); // デフォルトの処理
    }
}

void ZoomableGraphicsView::wheelEvent(QWheelEvent *event)
{
    // ズームファクターの計算 (スクロール量に基づいて)
    qreal scaleFactor = qPow(1.0015, event->angleDelta().y()); // y方向のスクロール量

    // シーンの中心点を維持したままズームする
    // 1. マウスカーソルの位置をシーン座標に変換
    QPointF oldPos = mapToScene(event->pos());

    // 2. 現在のビューの変換行列を取得
    QTransform matrix = transform();

    // 3. スケール変換を適用
    matrix.scale(scaleFactor, scaleFactor);

    // 4. 新しい変換行列を設定
    setTransform(matrix);

    // 5. 新しいズームレベルでのカーソル位置をシーン座標に変換
    QPointF newPos = mapToScene(event->pos());

    // 6. ズームの中心を維持するために、シーンを調整
    QPointF delta = newPos - oldPos;
    translate(delta.x(), delta.y()); // ビューの中心を移動

    event->accept();
}
// main.cpp (上記ZoomableGraphicsViewを使用)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include "zoomableview.h" // 作成したズーム可能なビュー

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

    QGraphicsScene *scene = new QGraphicsScene();
    scene->setSceneRect(-200, -200, 400, 400); // 中央が(0,0)になるように設定

    // 中央に大きな長方形
    QGraphicsRectItem *centerRect = new QGraphicsRectItem(-50, -50, 100, 100);
    centerRect->setBrush(Qt::green);
    scene->addItem(centerRect);

    // 左右に小さな円
    QGraphicsEllipseItem *leftCircle = new QGraphicsEllipseItem(-150, -20, 40, 40);
    leftCircle->setBrush(Qt::red);
    scene->addItem(leftCircle);

    QGraphicsEllipseItem *rightCircle = new QGraphicsEllipseItem(110, -20, 40, 40);
    rightCircle->setBrush(Qt::blue);
    scene->addItem(rightCircle);

    // QGraphicsView::setScene() を使ってカスタムビューにシーンを設定
    ZoomableGraphicsView *view = new ZoomableGraphicsView(scene);

    view->setWindowTitle("ズームとパン可能なビューの例");
    view->resize(600, 400);
    view->show();

    return a.exec();
}

解説
この例では、ZoomableGraphicsView という QGraphicsView を継承したカスタムクラスを作成しています。

  • wheelEvent をオーバーライドして、マウスホイールによるズーム機能を実現しています。setTransform() を使ってビューの変換行列を操作し、translate() を使ってズームの中心をマウスカーソル位置に保つように調整しています。
  • mousePressEvent, mouseMoveEvent, mouseReleaseEvent をオーバーライドして、マウスの左ドラッグによるパン(移動)機能を実現しています。これはスクロールバーの値を直接操作することで行っています。

QGraphicsView::setScene() は、ZoomableGraphicsView のコンストラクタで、通常の QGraphicsView と同じように使用されています。このように、カスタムのビュークラスを作成した場合でも、シーンの設定方法は変わりません。



QGraphicsView::setScene() は、QGraphicsView にどの QGraphicsScene を表示するかを設定するための主要かつ標準的な方法であり、実質的な代替方法はありません。これは、Qt の Graphics View Framework の設計において、ビューとシーンを関連付けるための中心的なAPIだからです。

しかし、「代替」という言葉をもう少し広い意味で捉え、同じ目的(シーンの表示)を達成するための異なるアプローチや、setScene() を使わない場合の他の考慮事項について説明することは可能です。

厳密な代替は存在しませんが、以下のような状況やアプローチが考えられます。

QGraphicsView のコンストラクタでシーンを設定する

これは setScene() メソッドを呼び出すのではなく、QGraphicsView のインスタンスを生成する際に引数として QGraphicsScene を渡す方法です。最終的には同じことを行いますが、コードの書き方としては代替と見なせます。

コード例

// main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>

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

    QGraphicsScene *scene = new QGraphicsScene();
    scene->setSceneRect(0, 0, 400, 300);
    QGraphicsRectItem *rectItem = new QGraphicsRectItem(50, 50, 100, 100);
    rectItem->setBrush(QBrush(Qt::blue));
    scene->addItem(rectItem);

    // QGraphicsViewのコンストラクタでシーンを設定
    QGraphicsView *view = new QGraphicsView(scene); // ここがポイント

    view->setWindowTitle("コンストラクタでシーンを設定する例");
    view->resize(600, 400);
    view->show();

    return a.exec();
}

解説
これは最も一般的な QGraphicsView の初期化方法の一つです。QGraphicsView が生成されると同時に、どのシーンを表示するかが決定されます。アプリケーションのライフサイクル中にシーンを切り替える必要がない場合や、初期設定の場合によく使われます。内部的には、このコンストラクタも setScene() を呼び出しています。

単一のシーンオブジェクトを使い回し、アイテムの追加/削除で内容を更新する

setScene() を頻繁に呼び出して異なる QGraphicsScene オブジェクトを切り替えるのではなく、単一の QGraphicsScene インスタンスを使い続け、そのシーン内のアイテムを追加、削除、表示/非表示を切り替えることで、表示内容を更新するアプローチです。

これは「setScene() の代替」というよりは、「複数のシーンオブジェクトを使用しない代替方法」と言えます。

コード例

// mywindow.h
#ifndef MYWINDOW_H
#define MYWINDOW_H

#include <QWidget>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPushButton>
#include <QVBoxLayout>
#include <QGraphicsRectItem>
#include <QGraphicsTextItem>

class MyWindow : public QWidget
{
    Q_OBJECT

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

private slots:
    void showScene1Content();
    void showScene2Content();

private:
    QGraphicsView *m_view;
    QGraphicsScene *m_sharedScene; // 共有する単一のシーン
    QPushButton *m_button1;
    QPushButton *m_button2;

    QList<QGraphicsItem*> m_scene1Items; // シーン1の内容
    QList<QGraphicsItem*> m_scene2Items; // シーン2の内容

    void clearSceneContent();
    void addScene1Content();
    void addScene2Content();
};

#endif // MYWINDOW_H
// mywindow.cpp
#include "mywindow.h"

MyWindow::MyWindow(QWidget *parent)
    : QWidget(parent)
{
    m_sharedScene = new QGraphicsScene(this);
    m_sharedScene->setSceneRect(0, 0, 300, 200);

    m_view = new QGraphicsView(m_sharedScene, this); // コンストラクタでシーンを設定

    m_button1 = new QPushButton("コンテンツ1を表示", this);
    m_button2 = new QPushButton("コンテンツ2を表示", this);

    connect(m_button1, &QPushButton::clicked, this, &MyWindow::showScene1Content);
    connect(m_button2, &QPushButton::clicked, this, &MyWindow::showScene2Content);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(m_view);
    layout->addWidget(m_button1);
    layout->addWidget(m_button2);
    setLayout(layout);

    setWindowTitle("単一シーンでのコンテンツ切り替え例");
    resize(400, 400);

    // 初期表示コンテンツ
    addScene1Content();
    showScene1Content(); // 明示的に表示
}

MyWindow::~MyWindow()
{
    // m_sharedScene は親が設定されているので自動的に解放される
    // m_scene1Items, m_scene2Items に追加したアイテムは
    // シーンが破棄されるときに自動的に解放される
}

void MyWindow::clearSceneContent()
{
    // 現在表示されているアイテムをすべて非表示にするか、シーンから削除する
    // 今回は表示/非表示を切り替える方式
    for (QGraphicsItem *item : m_scene1Items) {
        item->setVisible(false);
    }
    for (QGraphicsItem *item : m_scene2Items) {
        item->setVisible(false);
    }
}

void MyWindow::addScene1Content()
{
    // シーン1のアイテムを一度だけ作成し、リストに保存
    if (m_scene1Items.isEmpty()) {
        QGraphicsRectItem *rect = new QGraphicsRectItem(50, 50, 100, 100);
        rect->setBrush(Qt::green);
        m_sharedScene->addItem(rect);
        m_scene1Items.append(rect);

        QGraphicsTextItem *text = new QGraphicsTextItem("これはコンテンツ1です");
        text->setPos(10, 10);
        m_sharedScene->addItem(text);
        m_scene1Items.append(text);
    }
}

void MyWindow::addScene2Content()
{
    // シーン2のアイテムを一度だけ作成し、リストに保存
    if (m_scene2Items.isEmpty()) {
        QGraphicsEllipseItem *ellipse = new QGraphicsEllipseItem(80, 80, 120, 120);
        ellipse->setBrush(Qt::magenta);
        m_sharedScene->addItem(ellipse);
        m_scene2Items.append(ellipse);

        QGraphicsTextItem *text = new QGraphicsTextItem("これはコンテンツ2です");
        text->setPos(10, 10);
        m_sharedScene->addItem(text);
        m_scene2Items.append(text);
    }
}

void MyWindow::showScene1Content()
{
    clearSceneContent();
    for (QGraphicsItem *item : m_scene1Items) {
        item->setVisible(true);
    }
    m_sharedScene->update(); // シーンの更新を強制
}

void MyWindow::showScene2Content()
{
    clearSceneContent();
    for (QGraphicsItem *item : m_scene2Items) {
        item->setVisible(true);
    }
    m_sharedScene->update(); // シーンの更新を強制
}

解説
このアプローチは、複数の論理的な「シーン」を管理しつつも、物理的には単一の QGraphicsScene オブジェクトを使用します。コンテンツの切り替えは、QGraphicsItemsetVisible() メソッドを使って行われます。

利点

  • シーンの切り替えが非常に高速になる(新しいシーンを設定するオーバーヘッドがないため)。
  • QGraphicsScene オブジェクトの生成・破棄が不要になるため、メモリ管理が簡素化される可能性がある。

欠点

  • シーン固有の設定(例: bspTreeDepth, itemIndexMethod など)をコンテンツごとに変えたい場合は、このアプローチでは難しい。
  • 各コンテンツに属するアイテムを別途管理するためのロジック(例: QList<QGraphicsItem*>)が必要になる。
  • 異なるコンテンツ間でアイテムが重ならないように、アイテムのZ値などを考慮する必要がある。
  • シーン内に大量のアイテムが存在する場合、表示/非表示の切り替え自体がパフォーマンスに影響を与える可能性がある。

シーンのアイテムを動的にロード/アンロードする

これは上記2のバリエーションですが、表示しないアイテムを完全にシーンから削除し(scene->removeItem(item);)、必要になったときに再度追加するアプローチです。これは特に、メモリ使用量を最適化したい場合や、同時に多数のアイテムを保持する必要がない場合に有用です。

コード例(概念的):

void MyWindow::loadContent1() {
    clearScene(); // シーンから既存のアイテムをすべて削除
    // コンテンツ1のアイテムを作成し、シーンに追加
    QGraphicsRectItem *rect = new QGraphicsRectItem(...);
    m_sharedScene->addItem(rect);
    // ...
}

void MyWindow::loadContent2() {
    clearScene(); // シーンから既存のアイテムをすべて削除
    // コンテンツ2のアイテムを作成し、シーンに追加
    QGraphicsEllipseItem *ellipse = new QGraphicsEllipseItem(...);
    m_sharedScene->addItem(ellipse);
    // ...
}

void MyWindow::clearScene() {
    for (QGraphicsItem *item : m_sharedScene->items()) {
        m_sharedScene->removeItem(item);
        delete item; // シーンから削除したアイテムを手動で解放
    }
}

解説
このアプローチは、一度に表示するアイテム数が少ない場合に適しています。しかし、アイテムの生成と破棄のオーバーヘッドが発生するため、頻繁な切り替えには向かない場合があります。

QGraphicsView::setScene()QGraphicsViewQGraphicsScene を関連付けるための基本的なメカニズムであり、その機能自体を直接代替するAPIは Qt には存在しません。