QGraphicsViewの背景が描画されない?Qt drawBackground()のデバッグと解決策

2025-05-27

この関数は、QGraphicsView がそのシーンの背景を描画する際に呼び出されます。QGraphicsView は、QGraphicsScene に含まれるアイテム(図形やテキストなど)を描画する前に、この関数を使って背景を描画します。

主な目的

  • カスタム背景の描画
    QGraphicsView の背景に、標準のブラシ(単色、グラデーション、テクスチャなど)では表現できないような複雑な描画を行いたい場合に、この関数をオーバーライド(再実装)します。例えば、グリッド線、特別なパターン、あるいは特定の情報を示す描画などを背景に表示することができます。

引数

  • const QRectF &rect: 描画する必要がある背景の領域(長方形)をシーン座標で表したものです。Qtは、画面全体を再描画するのではなく、変更があった領域(「露出した領域」または「ダーティ領域」と呼ばれる)のみを再描画することでパフォーマンスを最適化します。そのため、このrectは必ずしもビュー全体を表すとは限りません。
  • QPainter *painter: 背景を描画するために使用するQPainterオブジェクトへのポインタです。このpainterを使って、線、図形、画像などを描画できます。

デフォルトの動作

  • QGraphicsView::drawBackground() のデフォルトの実装は、まずビューの backgroundBrush プロパティが設定されているかを確認します。
    • もし backgroundBrush が設定されていれば(Qt::NoBrush でない場合)、そのブラシを使って rect で指定された領域を塗りつぶします。
    • backgroundBrush が設定されていない場合(デフォルト)は、ビューに関連付けられている QGraphicsSceneQGraphicsScene::drawBackground() 関数を呼び出します。これにより、シーンの背景が描画されます。

カスタム描画のポイント

  • QGraphicsScene::drawBackground() との関係
    • QGraphicsView::drawBackground() をオーバーライドした場合、そのビューの背景描画は完全に制御されます。
    • もし QGraphicsScene::drawBackground() をオーバーライドしてシーンの背景を描画したい場合は、QGraphicsViewbackgroundBrushQt::NoBrush のままにしておくか、QGraphicsView::drawBackground() の中で明示的に QGraphicsScene::drawBackground(painter, rect); を呼び出す必要があります。
  • setBackgroundBrush() との違い
    • setBackgroundBrush() を使用すると、単色、グラデーション、テクスチャといった単純な背景を設定できます。これは最も簡単で推奨される方法です。
    • drawBackground() をオーバーライドするのは、setBackgroundBrush() では実現できない、より複雑なカスタム描画が必要な場合です。
  • パフォーマンスへの配慮
    rect で指定された領域のみを描画するように心がけることで、描画パフォーマンスを向上させることができます。しかし、複雑な背景の場合、常に rect に合わせて描画を最適化するのは難しい場合があります。
  • シーン座標での描画
    この関数に渡される painter は、すでにシーン座標系に設定されています。したがって、特別な変換なしに、シーン上の位置に直接描画できます。
#include <QGraphicsView>
#include <QPainter>
#include <QRectF>

class MyGraphicsView : public QGraphicsView
{
public:
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
    }

protected:
    void drawBackground(QPainter *painter, const QRectF &rect) override
    {
        // まず基底クラスの背景描画を呼び出す(これにより、sceneの背景ブラシが描画されるか、
        // QGraphicsViewの背景ブラシが描画される)。
        // もし完全にカスタムな背景にしたい場合は、この行をコメントアウトするか削除する。
        QGraphicsView::drawBackground(painter, rect);

        // ここからカスタムな描画を行う
        // 例: グリッド線を描画する
        qreal left = rect.left();
        qreal right = rect.right();
        qreal top = rect.top();
        qreal bottom = rect.bottom();

        qreal gridSize = 50.0; // グリッドのサイズ

        QPen gridPen(Qt::lightGray, 0); // 薄い灰色で細い線
        painter->setPen(gridPen);

        // 縦線を描画
        for (qreal x = qFloor(left / gridSize) * gridSize; x <= right; x += gridSize) {
            painter->drawLine(QPointF(x, top), QPointF(x, bottom));
        }

        // 横線を描画
        for (qreal y = qFloor(top / gridSize) * gridSize; y <= bottom; y += gridSize) {
            painter->drawLine(QPointF(left, y), QPointF(right, y));
        }

        // さらに複雑な描画を追加することも可能
        // 例: 中心に円を描画
        // painter->setBrush(Qt::blue);
        // painter->drawEllipse(sceneRect().center(), 20, 20);
    }
};


QGraphicsView::drawBackground() は強力なツールですが、誤った使い方をすると予期しない動作やパフォーマンスの問題を引き起こすことがあります。

背景が全く描画されない、またはデフォルトの背景しか見えない

原因

  • イベントシステムの問題
    QGraphicsView の更新メカニズムが適切にトリガーされていない可能性があります。
  • オーバーライドの誤り
    シグネチャ(関数名、引数の型と順序、戻り値の型)が正確に一致していないと、コンパイラはそれをオーバーライドとして認識せず、別の関数として扱ってしまいます。特に、const の有無や参照 (&) の有無など、細かい部分に注意が必要です。
  • QGraphicsScene::drawBackground() との競合
    QGraphicsView::drawBackground() のデフォルト実装は、backgroundBrushQt::NoBrush の場合に QGraphicsScene::drawBackground() を呼び出します。もしシーン側で背景を設定しているのにビュー側でカスタム描画したい場合、この関係を理解していないと混乱が生じます。
  • QGraphicsView::backgroundBrush の設定
    QGraphicsView は、デフォルトで backgroundBrush プロパティが設定されている場合、そのブラシを使って背景を塗りつぶします。もし drawBackground() をオーバーライドしていても、backgroundBrushQt::NoBrush 以外に設定されていると、オーバーライドしたコードが呼び出される前にデフォルトの背景が描画されてしまうか、あるいはオーバーライドした描画が backgroundBrush に上書きされてしまうことがあります。

トラブルシューティング

  • ビューの更新
    背景に変更を加えた後、ビューが再描画されるように view->viewport()->update(); または view->update(); を呼び出します。アイテムの追加や移動によってもビューは更新されますが、背景自体を変更した場合は明示的な更新が必要です。
  • シグネチャの確認
    // 正しいオーバーライドの例
    void drawBackground(QPainter *painter, const QRectF &rect) override;
    
    override キーワードを使用することで、コンパイラがオーバーライドの誤りを検出してくれるので、常に使用することを強く推奨します。
  • 基底クラスの呼び出し
    オーバーライドした drawBackground() の中で、最初に QGraphicsView::drawBackground(painter, rect); を呼び出しているか確認します。これにより、デフォルトの動作(シーンの背景描画など)が尊重され、その上にカスタム描画を追加する形になります。完全にカスタムな背景にしたい場合はこの行を削除しますが、意図しない描画を防ぐために注意が必要です。
  • backgroundBrush の確認
    QGraphicsViewsetBackgroundBrush(Qt::NoBrush); を呼び出して、ビューの背景ブラシを無効にします。これにより、オーバーライドした drawBackground() が確実に呼び出され、その描画が反映されるようになります。

パフォーマンスの問題(描画が遅い、ちらつく)

原因

  • 画像のスケーリング
    背景画像を毎回 rect に合わせてスケーリングして描画すると、コストが高くなります。
  • アンチエイリアシングの過剰使用
    QPainter::setRenderHint(QPainter::Antialiasing); は見た目を良くしますが、描画コストが増大します。グリッド線のような単純な線にまでアンチエイリアシングを適用すると、パフォーマンスに影響が出ることがあります。
  • rect 引数の無視
    drawBackground() に渡される rect 引数は、実際に描画が必要な領域を示しています。この rect を無視して常にビュー全体を描画しようとすると、無駄な描画処理が発生し、パフォーマンスが大幅に悪化します。
  • 不必要な再描画
    drawBackground() は、ビューの一部が露出するたびに(スクロール、リサイズ、アイテムの移動など)頻繁に呼び出されます。この関数内で重い計算や複雑な描画を毎回行っていると、パフォーマンスが低下します。

トラブルシューティング

  • 複雑な背景は QGraphicsItem で
    背景が非常に複雑で、かつ一部が頻繁に更新されるような場合は、背景全体を QGraphicsItem として実装し、そのアイテムの paint() メソッドで描画を行うことを検討します。QGraphicsItem は、boundingRect()shape() を適切に実装することで、より効率的な部分更新を行うことができます。
  • 描画ヒントの選択
    必要な場所のみで QPainter::Antialiasing などの QPainter::RenderHint を有効にし、不要な場合は無効にします。特に、グリッド線などの直線にはアンチエイリアシングは不要な場合が多いです。
  • キャッシュの利用
    • QGraphicsView::CacheBackground
      QGraphicsView::setCacheMode(QGraphicsView::CacheBackground); を設定すると、Qtは背景を一度描画した後にキャッシュします。背景が頻繁に変わらない場合に非常に効果的です。ただし、背景が動的に変化する場合は、キャッシュが無効になったり、再生成のコストがかかるため、かえって遅くなることもあります。
    • 手動でのキャッシュ
      複雑な背景の場合、QPixmap などに事前に描画しておき、drawBackground() ではその QPixmap の必要な部分だけを描画するようにすることも検討します。
  • rect 引数の利用
    rect で指定された領域内でのみ描画を行うようにロジックを最適化します。例えば、グリッド線を描画する場合、rect の範囲内にあるグリッド線のみを描画するようにします。
    // グリッド線の例(rectを考慮)
    qreal left = rect.left();
    qreal right = rect.right();
    qreal top = rect.top();
    qreal bottom = rect.bottom();
    
    qreal gridSize = 50.0;
    QPen gridPen(Qt::lightGray, 0);
    painter->setPen(gridPen);
    
    // rectの範囲内にあるグリッド線のみを描画
    for (qreal x = qFloor(left / gridSize) * gridSize; x <= right; x += gridSize) {
        painter->drawLine(QPointF(x, top), QPointF(x, bottom));
    }
    for (qreal y = qFloor(top / gridSize) * gridSize; y <= bottom; y += gridSize) {
        painter->drawLine(QPointF(left, y), QPointF(right, y));
    }
    

描画がずれる、位置がずれる

原因

  • QPainter の状態変更の忘れ
    painter->save()painter->restore() のペアを適切に使用していないと、他の描画(アイテムの描画など)に影響を与える可能性があります。
  • ビューポート座標とシーン座標の混同
    drawBackground()painterシーン座標に設定されています。しかし、誤ってビューポート座標やウィジェット座標で描画しようとすると、描画がずれます。

トラブルシューティング

  • QPainter の状態管理
    カスタム描画を行う前に painter->save(); を呼び出し、描画が完了したら painter->restore(); を呼び出す習慣をつけましょう。これにより、背景描画で行った painter の設定(ペン、ブラシ、変換行列など)が、その後に描画されるアイテムや前景に影響を与えるのを防ぎます。
  • 常にシーン座標で描画
    drawBackground() 内では、渡された QPainter は既にシーン座標に変換されているため、そのままシーン上の位置に描画します。mapToScene()mapFromScene() を使う必要はありません。

背景がちらつく (Flickering)

原因

  • 不適切な更新モード
    QGraphicsView::setViewportUpdateMode() の設定が不適切だと、必要以上に再描画が行われたり、部分的な更新がうまくいかなかったりして、ちらつきに見えることがあります。
  • 二重バッファリングの欠如
    以前の Qt バージョンでは、ちらつきを防ぐために二重バッファリングを手動で有効にする必要がありましたが、現代の Qt バージョンでは通常は自動的に処理されます。しかし、特定のグラフィックスドライバや設定によっては問題が発生することがあります。

トラブルシューティング

  • カスタムビューポートの使用
    稀なケースですが、特定の QWidget をビューポートとして使用している場合、そのウィジェットの描画設定が影響している可能性もあります。
  • setOptimizationFlag() の確認
    QGraphicsView::setOptimizationFlag(QGraphicsView::DontSavePainterState); のような最適化フラグを誤って設定していないか確認します。
  • setViewportUpdateMode() の調整
    QGraphicsView::setViewportUpdateMode(QGraphicsView::FullViewportUpdate); を試してみてください。これは最も単純で確実な更新モードですが、パフォーマンスは最も低くなります。もしこれでちらつきが解消され、パフォーマンスが許容できるならこれで良いでしょう。より効率的な BoundingRectViewportUpdateSmartViewportUpdate を使用する場合は、描画ロジックが rect を適切に扱っていることを確認する必要があります。

Qt 5.x 以降での update() 呼び出しの問題

原因

  • update() と drawBackground() の関係の変化
    Qt 4.x から Qt 5.x への移行時に、QGraphicsView::update() の動作が変更され、直接 drawBackground() を呼び出さなくなったという報告があります。これは、QGraphicsView が描画を最適化しようとする内部的なメカニズムによるものです。
  • viewport()->update() の使用
    ビューポートの更新を明示的に要求します。
  • scene()->invalidate() を使用
    QGraphicsView の背景の更新が必要な場合は、ビューではなくシーンに対して scene()->invalidate(scene()->sceneRect(), QGraphicsScene::BackgroundLayer); を呼び出すことを検討します。これにより、シーンの指定された領域の背景レイヤーが無効化され、次回の描画イベントで再描画がトリガーされます。


シンプルなグリッド背景

最も一般的なカスタム背景の1つは、グリッド線です。これは、アイテムの配置やサイズ調整の目安として非常に役立ちます。

// mygraphicsview.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QPainter>
#include <QRectF>

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

protected:
    // QGraphicsView::drawBackgroundをオーバーライド
    void drawBackground(QPainter *painter, const QRectF &rect) override;

private:
    qreal m_gridSize; // グリッドのセルサイズ
};

#endif // MYGRAPHICSVIEW_H
// mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QtMath> // qFloor, qCeil を使用

MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent),
      m_gridSize(50.0) // 50x50のグリッド
{
    // 背景ブラシを無効にする(drawBackgroundが確実に呼び出されるように)
    setBackgroundBrush(Qt::NoBrush);
    // 背景のキャッシュを有効にする(パフォーマンス向上)
    setCacheMode(QGraphicsView::CacheBackground);
}

void MyGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
    // 1. (オプション) 基底クラスの背景描画を呼び出す
    //    もしQGraphicsScene::backgroundBrushが設定されている場合、それが描画されます。
    //    完全にカスタムな背景にしたい場合は、この行をコメントアウトまたは削除します。
    // QGraphicsView::drawBackground(painter, rect);

    // 2. QPainterの状態を保存(カスタム描画が他の描画に影響しないように)
    painter->save();

    // 3. グリッド線を描画するためのペンを設定
    QPen gridPen(Qt::lightGray, 0.5); // 薄い灰色、細い線
    painter->setPen(gridPen);

    // 4. 描画が必要な領域 (rect) に合わせてグリッド線を計算・描画
    //    パフォーマンスのため、rectの範囲内のみを描画することが重要です。
    qreal left = rect.left();
    qreal right = rect.right();
    qreal top = rect.top();
    qreal bottom = rect.bottom();

    // 縦線を描画
    // rectの左端より小さい最初のグリッド線から、右端より大きい最後のグリッド線まで描画
    for (qreal x = qFloor(left / m_gridSize) * m_gridSize; x <= right; x += m_gridSize) {
        painter->drawLine(QPointF(x, top), QPointF(x, bottom));
    }

    // 横線を描画
    // rectの上端より小さい最初のグリッド線から、下端より大きい最後のグリッド線まで描画
    for (qreal y = qFloor(top / m_gridSize) * m_gridSize; y <= bottom; y += m_gridSize) {
        painter->drawLine(QPointF(left, y), QPointF(right, y));
    }

    // 5. (オプション) より太いメイングリッド線を描画
    QPen mainGridPen(Qt::gray, 1.0); // 濃い灰色、少し太い線
    painter->setPen(mainGridPen);
    qreal majorGridSize = m_gridSize * 5; // 例えば、5セルごとに太い線

    for (qreal x = qFloor(left / majorGridSize) * majorGridSize; x <= right; x += majorGridSize) {
        painter->drawLine(QPointF(x, top), QPointF(x, bottom));
    }
    for (qreal y = qFloor(top / majorGridSize) * majorGridSize; y <= bottom; y += majorGridSize) {
        painter->drawLine(QPointF(left, y), QPointF(right, y));
    }

    // 6. QPainterの状態を復元
    painter->restore();
}
// main.cpp (アプリケーションのエントリポイント)
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsScene>
#include "mygraphicsview.h" // 作成したカスタムビューをインクルード

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

    QMainWindow window;
    window.setWindowTitle("Custom QGraphicsView Background Example");
    window.resize(800, 600);

    QGraphicsScene *scene = new QGraphicsScene(0, 0, 1000, 1000, &window); // シーンサイズを設定
    // シーンの背景ブラシを設定することも可能ですが、
    // MyGraphicsViewでQt::NoBrushを設定しているため、この設定は無視されます。
    // scene->setBackgroundBrush(Qt::darkCyan);

    MyGraphicsView *view = new MyGraphicsView(scene, &window);
    view->setRenderHint(QPainter::Antialiasing); // アイテムの描画を滑らかにする
    view->setCacheMode(QGraphicsView::CacheBackground); // 背景のキャッシュを有効にする
    view->setDragMode(QGraphicsView::ScrollHandDrag); // 手のひらツールでスクロールできるように

    // シーンにいくつかのアイテムを追加して、背景との関係を見る
    QGraphicsRectItem *rectItem = scene->addRect(100, 100, 200, 150, QPen(Qt::black), QBrush(Qt::blue));
    rectItem->setFlag(QGraphicsItem::ItemIsMovable);

    QGraphicsEllipseItem *ellipseItem = scene->addEllipse(300, 200, 100, 100, QPen(Qt::black), QBrush(Qt::green));
    ellipseItem->setFlag(QGraphicsItem::ItemIsMovable);

    window.setCentralWidget(view);
    window.show();

    return a.exec();
}

解説

  • painter->save()painter->restore() は、描画設定(ペン、ブラシなど)が背景描画に限定され、その後に描画されるアイテムに影響を与えないようにするために非常に重要です。
  • rect 引数は、描画が必要な領域(シーン座標)を示しています。パフォーマンスのために、この領域内でのみ描画を行うように計算しています。
  • drawBackground 内では、QPainter を使用してグリッド線を描画しています。
  • setCacheMode(QGraphicsView::CacheBackground) は、背景が変化しない限り、Qtが背景の描画結果をキャッシュするように指示します。これにより、スクロール時などにパフォーマンスが向上します。
  • コンストラクタで setBackgroundBrush(Qt::NoBrush) を設定し、QGraphicsView のデフォルトの背景描画を無効にしています。これにより、オーバーライドした drawBackground が完全に背景描画を制御します。
  • MyGraphicsView クラスは QGraphicsView を継承し、drawBackground メソッドをオーバーライドしています。

背景画像を描画する

drawBackground() を使用して、背景に画像をタイル状に配置したり、フィットさせたりすることも可能です。

// mygraphicsview.h (上記のMyGraphicsViewクラスに以下を追加または修正)
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QPainter>
#include <QRectF>
#include <QPixmap> // QPixmapを使う場合

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
    void setBackgroundImage(const QPixmap &pixmap);

protected:
    void drawBackground(QPainter *painter, const QRectF &rect) override;

private:
    QPixmap m_backgroundImage;
    // qreal m_gridSize; // グリッドを使うなら残す
};

#endif // MYGRAPHICSVIEW_H
// mygraphicsview.cpp (drawBackgroundのみ変更)
#include "mygraphicsview.h"
#include <QtMath>

MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    setBackgroundBrush(Qt::NoBrush);
    setCacheMode(QGraphicsView::CacheBackground);
    // 初期背景として色を設定することもできます
    // setBackgroundBrush(QBrush(QColor(240, 240, 240))); // 明るい灰色
}

void MyGraphicsView::setBackgroundImage(const QPixmap &pixmap)
{
    m_backgroundImage = pixmap;
    // 背景画像が変更されたらビューポートを更新して再描画をトリガー
    viewport()->update();
}

void MyGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
    painter->save();

    // 背景色で塗りつぶす(画像が透明な場合や、画像がない場合に備えて)
    painter->fillRect(rect, QBrush(QColor(240, 240, 240))); // 明るい灰色で塗りつぶし

    if (!m_backgroundImage.isNull()) {
        // オプション1: 画像をタイル状に配置 (背景ブラシのように)
        // QBrush imageBrush(m_backgroundImage);
        // painter->setBrush(imageBrush);
        // painter->fillRect(rect, imageBrush); // rect領域をブラシで塗りつぶす

        // オプション2: 画像をビューの可視領域にフィットさせる (アスペクト比を維持)
        // QRectF sourceRect = m_backgroundImage.rect(); // 画像の元のサイズ
        // painter->drawPixmap(rect, m_backgroundImage, sourceRect);

        // オプション3: 画像をシーンの中央に固定配置し、ズームしても画像サイズは変わらないようにする
        // painter->setTransform(QTransform()); // painterの変換をリセット
        // QPointF viewCenter = mapToScene(viewport()->rect().center()); // ビューポートの中心をシーン座標で取得
        // QPointF imageTopLeft = viewCenter - QPointF(m_backgroundImage.width() / 2.0, m_backgroundImage.height() / 2.0); // 画像を中央に配置するための左上座標
        // painter->drawPixmap(imageTopLeft, m_backgroundImage);
        // painter->setTransform(transform()); // painterの変換を元に戻す

        // オプション4: 画像をシーンの中心に1枚だけ描画し、ズームアウトしても画像自体は小さくならないようにする
        // QPointF imageScenePos = sceneRect().center() - QPointF(m_backgroundImage.width() / 2.0, m_backgroundImage.height() / 2.0);
        // if (rect.intersects(QRectF(imageScenePos, m_backgroundImage.size()))) {
        //     painter->drawPixmap(imageScenePos, m_backgroundImage);
        // }
        
        // 今回の例では、画像をビューの可視領域全体にストレッチして表示します
        painter->drawPixmap(rect.toRect(), m_backgroundImage, m_backgroundImage.rect());
    }

    // 最後にグリッド線を描画(オプション1のタイル状背景の上にグリッドを重ねる)
    // (グリッド線のコードは上記「シンプルなグリッド背景」のdrawBackgroundからコピー)
    // 例として、背景画像の上にグリッド線を重ねて表示する場合
    QPen gridPen(Qt::lightGray, 0.5); // 薄い灰色で細い線
    painter->setPen(gridPen);

    qreal left = rect.left();
    qreal right = rect.right();
    qreal top = rect.top();
    qreal bottom = rect.bottom();

    qreal gridSize = 50.0; // グリッドのサイズ
    for (qreal x = qFloor(left / gridSize) * gridSize; x <= right; x += gridSize) {
        painter->drawLine(QPointF(x, top), QPointF(x, bottom));
    }
    for (qreal y = qFloor(top / gridSize) * gridSize; y <= bottom; y += gridSize) {
        painter->drawLine(QPointF(left, y), QPointF(right, y));
    }


    painter->restore();
}
// main.cpp (上記を少し修正)
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsScene>
#include <QPixmap> // QPixmapを使う場合
#include "mygraphicsview.h"

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

    QMainWindow window;
    window.setWindowTitle("Custom QGraphicsView Background Image Example");
    window.resize(800, 600);

    QGraphicsScene *scene = new QGraphicsScene(0, 0, 1000, 1000, &window);

    MyGraphicsView *view = new MyGraphicsView(scene, &window);
    view->setRenderHint(QPainter::Antialiasing);
    view->setCacheMode(QGraphicsView::CacheBackground);
    view->setDragMode(QGraphicsView::ScrollHandDrag);

    // 背景画像を設定
    QPixmap backgroundImage(":/images/background_pattern.png"); // リソースファイルから画像を読み込む
    // もしリソースファイルを使わないなら:
    // QPixmap backgroundImage("path/to/your/image.png");
    if (!backgroundImage.isNull()) {
        view->setBackgroundImage(backgroundImage);
    } else {
        qWarning("Failed to load background image!");
    }

    // シーンにアイテムを追加
    QGraphicsRectItem *rectItem = scene->addRect(100, 100, 200, 150, QPen(Qt::black), QBrush(Qt::blue));
    rectItem->setFlag(QGraphicsItem::ItemIsMovable);

    QGraphicsEllipseItem *ellipseItem = scene->addEllipse(300, 200, 100, 100, QPen(Qt::black), QBrush(Qt::green));
    ellipseItem->setFlag(QGraphicsItem::ItemIsMovable);

    window.setCentralWidget(view);
    window.show();

    return a.exec();
}
  • 画像ファイルは、Qtのリソースシステム (.qrc ファイル) を使用するか、ファイルパスを直接指定してロードします。リソースシステムを使用すると、実行ファイルに画像を埋め込むことができ、配布が容易になります。
  • 複数のオプション(タイル状、中央固定、ズーム影響なしなど)のコメントアウトされたコードも示しています。用途に合わせて選択してください。
  • drawBackground() 内で、m_backgroundImage を使用して背景を描画します。例では、rect (描画が必要な領域) に画像をストレッチしてフィットさせています。
  • setBackgroundImage() メソッドで画像をセットし、viewport()->update() を呼び出してビューの再描画をトリガーします。
  • m_backgroundImage メンバー変数に QPixmap を保持します。


QGraphicsView::setBackgroundBrush() を使用する

これは最もシンプルで、最もよく使われる背景設定方法です。単色、グラデーション、または単一のパターン画像(タイル状に繰り返される画像)を設定したい場合に最適です。

特徴

  • 制限
    複雑な描画(例えば、シーン座標に依存するグリッド線や動的な背景)には向いていません。指定したブラシでビューの可視領域を塗りつぶすだけです。
  • 効率的
    Qtが内部で描画を最適化します(特にキャッシュを有効にしている場合)。
  • 簡単
    数行のコードで背景を設定できます。

使用例

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QBrush>
#include <QColor>
#include <QPixmap>

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600); // シーンのサイズを設定

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

    // 例1: 単色で背景を設定
    // view.setBackgroundBrush(QBrush(Qt::lightGray));

    // 例2: グラデーションで背景を設定
    // QLinearGradient gradient(0, 0, 1, 1); // 0,0から1,1へのグラデーション(相対座標)
    // gradient.setCoordinateMode(QGradient::ObjectBoundingMode); // シーン全体に適用
    // gradient.setColorAt(0, Qt::blue);
    // gradient.setColorAt(1, Qt::green);
    // view.setBackgroundBrush(QBrush(gradient));

    // 例3: 画像をタイル状に配置
    QPixmap backgroundPixmap(":/images/tile_pattern.png"); // リソースファイルから画像を読み込む
    // background_pattern.png は、例えば20x20ピクセルの小さなパターン画像
    if (!backgroundPixmap.isNull()) {
        QBrush patternBrush(backgroundPixmap);
        view.setBackgroundBrush(patternBrush);
    } else {
        qWarning("Failed to load background image!");
        view.setBackgroundBrush(QBrush(Qt::darkCyan)); // 画像読み込み失敗時のフォールバック
    }

    // アイテムを追加
    scene.addRect(100, 100, 50, 50, QPen(Qt::black), QBrush(Qt::red));

    view.resize(800, 600);
    view.show();

    return a.exec();
}

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

QGraphicsView::drawBackground() と似ていますが、こちらはQGraphicsSceneの背景描画を制御します。複数のQGraphicsViewが同じQGraphicsSceneを表示している場合、この方法を使うと、すべてのビューに同じカスタム背景が適用されます。

特徴

  • ビューのbackgroundBrushによる上書きの可能性
    QGraphicsViewbackgroundBrushQt::NoBrush でない場合、QGraphicsView::drawBackground() が呼び出され、シーンの背景描画がスキップされるか上書きされる可能性があります。
  • シーン座標での描画
    QGraphicsView::drawBackground() と同様に、QPainter はシーン座標に設定されています。
  • ビュー間で共通の背景
    複数のビューで背景を共有する場合に適しています。

使用例

// mygraphicsscene.h
#ifndef MYGRAPHICSSCENE_H
#define MYGRAPHICSSCENE_H

#include <QGraphicsScene>
#include <QPainter>
#include <QRectF>

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

protected:
    void drawBackground(QPainter *painter, const QRectF &rect) override;
};

#endif // MYGRAPHICSBSCENE_H
// mygraphicsscene.cpp
#include "mygraphicsscene.h"
#include <QtMath>

MyGraphicsScene::MyGraphicsScene(QObject *parent)
    : QGraphicsScene(parent)
{
    // シーンの背景ブラシを無効にする
    // これにより、QGraphicsScene::drawBackgroundが確実に呼び出される
    setBackgroundBrush(Qt::NoBrush);
}

void MyGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
    // 常に基底クラスの描画を呼び出す必要はないが、
    // シーンの背景ブラシを有効にしている場合は呼び出すべき
    // QGraphicsScene::drawBackground(painter, rect);

    painter->save();

    // グリッド線を描画
    QPen gridPen(Qt::darkGray, 0.5);
    painter->setPen(gridPen);

    qreal gridSize = 25.0; // 25x25の細かいグリッド

    qreal left = rect.left();
    qreal right = rect.right();
    qreal top = rect.top();
    qreal bottom = rect.bottom();

    for (qreal x = qFloor(left / gridSize) * gridSize; x <= right; x += gridSize) {
        painter->drawLine(QPointF(x, top), QPointF(x, bottom));
    }
    for (qreal y = qFloor(top / gridSize) * gridSize; y <= bottom; y += gridSize) {
        painter->drawLine(QPointF(left, y), QPointF(right, y));
    }

    // シーンの中心に何か描画
    painter->setBrush(QBrush(Qt::red, Qt::Dense6Pattern));
    painter->drawEllipse(sceneRect().center().x() - 30, sceneRect().center().y() - 30, 60, 60);

    painter->restore();
}
// main.cpp (MyGraphicsViewの代わりにMyGraphicsSceneを使用)
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include "mygraphicsscene.h" // 作成したカスタムシーンをインクルード

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

    QMainWindow window;
    window.setWindowTitle("QGraphicsScene::drawBackground Example");
    window.resize(800, 600);

    MyGraphicsScene *scene = new MyGraphicsScene(&window);
    scene->setSceneRect(0, 0, 1000, 1000); // シーンサイズを設定

    QGraphicsView *view1 = new QGraphicsView(scene, &window);
    view1->setRenderHint(QPainter::Antialiasing);
    view1->setDragMode(QGraphicsView::ScrollHandDrag);

    // もう一つのビューを同じシーンに設定
    QGraphicsView *view2 = new QGraphicsView(scene, &window);
    view2->setRenderHint(QPainter::Antialiasing);
    view2->setDragMode(QGraphicsView::ScrollHandDrag);
    view2->scale(0.5, 0.5); // ズームレベルを変更してみる

    // レイアウトにビューを追加
    QWidget *centralWidget = new QWidget(&window);
    QHBoxLayout *layout = new QHBoxLayout(centralWidget);
    layout->addWidget(view1);
    layout->addWidget(view2);
    window.setCentralWidget(centralWidget);

    // アイテムを追加
    scene->addText("Hello, Scene Background!");
    scene->addRect(50, 50, 100, 100, QPen(Qt::black), QBrush(Qt::yellow))->setFlag(QGraphicsItem::ItemIsMovable);

    window.show();

    return a.exec();
}

QGraphicsItem を背景として追加する

背景をQGraphicsItemとしてシーンに追加することも可能です。この方法では、QGraphicsItemのすべての機能(移動、回転、スケール、Z値、イベント処理など)を利用できます。背景が動的であったり、複雑なインタラクションを伴う場合に非常に有用です。

特徴

  • 描画順序
    他のアイテムと同様に、Z値に基づいて描画されます。
  • レイヤー管理
    setZValue() を使って、他のアイテムの下に配置できます(Z値は負の値を設定するのが一般的)。
  • 動的
    背景自体が移動したり、インタラクションを持ったりする場合に最適。
  • 柔軟性
    QGraphicsItemの全機能が利用可能。

使用例

// backgrounditem.h
#ifndef BACKGROUNDITEM_H
#define BACKGROUNDITEM_H

#include <QGraphicsItem>
#include <QPainter>

class BackgroundItem : public QGraphicsItem
{
public:
    BackgroundItem(const QRectF &sceneRect, QGraphicsItem *parent = nullptr);

    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;

private:
    QRectF m_sceneRect;
    // 背景画像や他の描画用のデータをここに持つ
    QPixmap m_tileImage;
};

#endif // BACKGROUNDITEM_H
// backgrounditem.cpp
#include "backgrounditem.h"

BackgroundItem::BackgroundItem(const QRectF &sceneRect, QGraphicsItem *parent)
    : QGraphicsItem(parent),
      m_sceneRect(sceneRect)
{
    // 背景アイテムは通常、クリックや選択の対象としない
    setFlag(QGraphicsItem::ItemIsMovable, false);
    setFlag(QGraphicsItem::ItemIsSelectable, false);
    // Z値を非常に低い値に設定し、他のアイテムの下に描画されるようにする
    setZValue(-1000);

    // タイル用画像を読み込み(例)
    m_tileImage.load(":/images/small_grid_tile.png"); // 10x10ピクセルくらいのタイル画像
    if (m_tileImage.isNull()) {
        qWarning("Failed to load tile image for background item!");
    }
}

QRectF BackgroundItem::boundingRect() const
{
    // シーン全体をカバーするようにバウンディングボックスを返す
    return m_sceneRect;
}

void BackgroundItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option); // 通常、背景アイテムではスタイルオプションは使用しない
    Q_UNUSED(widget); // 通常、背景アイテムではウィジェットは使用しない

    // シーンの背景色で塗りつぶす(オプション)
    painter->fillRect(m_sceneRect, QBrush(QColor(230, 230, 255))); // 薄い青色

    // タイル画像を繰り返し描画
    if (!m_tileImage.isNull()) {
        QBrush tileBrush(m_tileImage);
        painter->fillRect(m_sceneRect, tileBrush);
    }

    // グリッド線を描画(例)
    QPen gridPen(Qt::darkGreen, 0.2); // 緑色の細い線
    painter->setPen(gridPen);

    qreal gridSize = 30.0; // 30x30のグリッド

    qreal left = m_sceneRect.left();
    qreal right = m_sceneRect.right();
    qreal top = m_sceneRect.top();
    qreal bottom = m_sceneRect.bottom();

    for (qreal x = qFloor(left / gridSize) * gridSize; x <= right; x += gridSize) {
        painter->drawLine(QPointF(x, top), QPointF(x, bottom));
    }
    for (qreal y = qFloor(top / gridSize) * gridSize; y <= bottom; y += gridSize) {
        painter->drawLine(QPointF(left, y), QPointF(right, y));
    }
}
// main.cpp (BackgroundItemをシーンに追加)
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include "backgrounditem.h" // 作成したカスタム背景アイテムをインクルード

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

    QMainWindow window;
    window.setWindowTitle("QGraphicsItem as Background Example");
    window.resize(800, 600);

    QGraphicsScene *scene = new QGraphicsScene(&window);
    scene->setSceneRect(0, 0, 1000, 1000); // シーンサイズを設定

    // 背景アイテムをシーンに追加
    BackgroundItem *backgroundItem = new BackgroundItem(scene->sceneRect());
    scene->addItem(backgroundItem);

    QGraphicsView *view = new QGraphicsView(scene, &window);
    view->setRenderHint(QPainter::Antialiasing);
    view->setDragMode(QGraphicsView::ScrollHandDrag);

    // アイテムを追加(背景アイテムの上に表示される)
    QGraphicsRectItem *rectItem = scene->addRect(100, 100, 200, 150, QPen(Qt::black), QBrush(Qt::cyan));
    rectItem->setFlag(QGraphicsItem::ItemIsMovable);
    rectItem->setZValue(100); // 背景より高いZ値

    QGraphicsTextItem *textItem = scene->addText("Movable Item");
    textItem->setPos(150, 150);
    textItem->setFlag(QGraphicsItem::ItemIsMovable);
    textItem->setZValue(101); // さらに高いZ値

    window.setCentralWidget(view);
    window.show();

    return a.exec();
}
  • この方法では、BackgroundItem の描画は他のアイテムと同様に処理され、キャッシュモードなどの最適化も適用されます。背景が複雑で動的な場合に非常に有効です。
  • setZValue(-1000); のように非常に低いZ値を設定することで、他のすべてのアイテムの下に描画されることを保証します。
  • コンストラクタでシーンの範囲を受け取り、boundingRect()paint() を実装します。
  • BackgroundItemQGraphicsItem を継承します。
  • QGraphicsItem を背景として追加: 背景自体が動的であったり、ユーザーインタラクションを伴う場合に最も柔軟。Z値でレイヤーを細かく制御できる。
  • QGraphicsView::drawBackground(): 特定のビューにのみカスタム背景を適用したい場合に最適。描画はシーン座標。QGraphicsScene::drawBackground() の描画を上書きできる。
  • QGraphicsScene::drawBackground(): 複数のビューで同じカスタム背景を共有したい場合に最適。描画はシーン座標。
  • QGraphicsView::setBackgroundBrush(): 最もシンプル。単色、グラデーション、タイル状画像など、静的な背景に最適。