QtのQGraphicsView::drawForegroundとは?基本から徹底解説
QGraphicsView と QGraphicsScene の関係
まず、QGraphicsView
と QGraphicsScene
の基本的な関係を理解することが重要です。
QGraphicsView
:QGraphicsScene
の内容を表示するためのウィジェットです。シーンの一部または全体を「ビューポート」として表示し、ズームやパンなどの操作を提供します。QGraphicsScene
: 2Dグラフィカルアイテムを管理するためのサーフェス(キャンバスのようなもの)です。アイテム(QGraphicsItem
)を追加したり、アイテム同士の衝突検出を行ったりします。また、シーン全体に適用される背景と前景も管理できます。
drawForeground()
の役割
QGraphicsView
は、QGraphicsScene
の内容を描画する際に、以下の順序でレイヤーを描画します。
QGraphicsView::drawBackground()
: ビュー独自の背景を描画します。これはシーンの背景よりも手前に描画されます。QGraphicsScene::drawBackground()
: シーンの背景を描画します。QGraphicsScene
のアイテム: シーンに追加された全てのQGraphicsItem
(図形、画像、テキストなど)を描画します。QGraphicsScene::drawForeground()
: シーンの前景を描画します。QGraphicsView::drawForeground()
: ビュー独自の前景を描画します。これはシーンの前景よりも手前に描画されます。
QGraphicsView::drawForeground()
は、この描画順序の最後に位置します。つまり、シーン上の全てのアイテムやシーンの背景・前景の上に、ビュー固有の追加の描画を行いたい場合にこの関数をオーバーライドします。
drawForeground()
を使用する具体的なケース
- 描画パフォーマンスの最適化: シーンのアイテムとは独立して、頻繁に更新されるがシンプルな要素(例:一時的な選択範囲、インタラクションガイドなど)を直接ビューに描画することで、シーン全体の再描画を避ける場合。
- 特定のビューにのみ表示される情報: 複数の
QGraphicsView
が同じQGraphicsScene
を表示している場合に、あるビューにだけ特別なマーカーやグリッド線などを表示したい場合。QGraphicsScene::drawForeground()
をオーバーライドすると全てのビューに影響しますが、QGraphicsView::drawForeground()
はそのビューにのみ適用されます。 - ビューポートに固定されたオーバーレイ表示: シーンの内容がズームやスクロールによって動いても、ビューポート上の特定の位置に常に表示したい情報(例:ルーラー、座標軸、デバッグ情報、HUD(ヘッドアップディスプレイ)など)を描画する場合。
関数の引数
const QRectF &rect
: 描画が必要な領域を示す矩形です。これは、ビューポート座標系における「ダーティリージョン」(再描画が必要な領域)を表します。通常、このrect
の範囲内のみを描画することでパフォーマンスを向上させます。QPainter *painter
: 描画に使用するQPainter
オブジェクトへのポインタです。このpainter
を使って、ビューポート上に描画を行います。
QGraphicsView
のサブクラスを作成し、drawForeground()
をオーバーライドします。
#include <QGraphicsView>
#include <QPainter>
#include <QDebug>
class MyGraphicsView : public QGraphicsView
{
public:
MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
: QGraphicsView(scene, parent)
{
// ビューの背景や前景のブラシを設定することもできます
// setBackgroundBrush(Qt::lightGray);
// setForegroundBrush(Qt::blue);
}
protected:
// QGraphicsView::drawForeground() をオーバーライド
void drawForeground(QPainter *painter, const QRectF &rect) override
{
// 親クラスのdrawForegroundを呼び出す(オプション、既存の描画がある場合)
QGraphicsView::drawForeground(painter, rect);
// ここにビューのフォアグラウンドのカスタム描画コードを記述
// 例: ビューポートの右上にテキストを描画
painter->setPen(Qt::red);
painter->setFont(QFont("Arial", 16));
painter->drawText(viewport()->rect().topRight() - QPoint(150, 20), "Hello from View Foreground!");
// 例: ビューポート全体に半透明の矩形を描画
painter->setBrush(QColor(0, 255, 0, 50)); // 緑色で半透明
painter->setPen(Qt::NoPen);
painter->drawRect(rect); // ダーティリージョン全体に描画
// デバッグ出力
// qDebug() << "drawForeground called for rect:" << rect;
}
};
このMyGraphicsView
のインスタンスをQGraphicsScene
と組み合わせて使用することで、カスタムの前景描画が実現できます。
QGraphicsView::drawForeground()
は強力なカスタマイズ機能を提供しますが、誤った使い方をすると予期せぬ動作を引き起こすことがあります。
drawForeground() が全く呼び出されない、または更新されない
問題
drawForeground()
をオーバーライドしたのに、描画が行われない、または描画内容が更新されない。
原因とトラブルシューティング
- 描画のトリガー不足
- 原因
QGraphicsView
の描画は、ビューポートがダーティ(再描画が必要)になったときに自動的に行われます。しかし、drawForeground()
の描画内容が変化したことをQtシステムに明示的に伝える必要があります。シーン内のアイテムを動かしたり、シーンの背景/前景を更新しても、ビューのフォアグラウンドは自動的に更新されない場合があります。 - トラブルシューティング
- ビューのフォアグラウンドを強制的に再描画するには、
viewport()->update()
を呼び出すのが最も直接的な方法です。これは、QGraphicsView
のビューポート全体を再描画します。 - 特定の領域だけを再描画したい場合は、
viewport()->update(const QRect &rect)
を使用します。これによりパフォーマンスが向上する可能性があります。 update()
やrepaint()
はイベントキューに描画イベントを追加するだけで、すぐに描画されるわけではありません。すぐに描画したい場合はviewport()->repaint()
を使うこともできますが、通常はupdate()
で十分です。- Qt 5.x 以降では、
QGraphicsScene::update()
やQGraphicsScene::invalidate()
を呼び出しても、ビューのdrawForeground()
が自動的に呼び出されない場合があることに注意してください。これらは主にシーンのアイテムやシーンの背景/前景の更新に影響します。ビュー固有の前景更新にはviewport()->update()
が適切です。
- ビューのフォアグラウンドを強制的に再描画するには、
- 原因
- オーバーライドの誤り
- 原因
関数のシグネチャ(引数の型と数、戻り値の型)が親クラスのdrawForeground
と完全に一致していない。例えば、const QRectF &rect
のconst
を忘れたり、引数の型を間違えたりすると、新しいメソッドがオーバーライドとして認識されず、親クラスのメソッドが呼び出され続けてしまいます。 - トラブルシューティング
親クラスの定義void QGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
と完全に一致しているか確認してください。C++11以降のoverride
キーワードを使用すると、コンパイル時にこのような間違いを検出できます。class MyGraphicsView : public QGraphicsView { protected: void drawForeground(QPainter *painter, const QRectF &rect) override; // 'override' を追加 };
- 原因
描画位置やスケールがおかしい
問題
drawForeground()
で描画した内容が、期待した位置に表示されない、またはズームレベルによってサイズが変わってしまう。
原因とトラブルシューティング
- 座標系の混同
- 原因
QGraphicsView::drawForeground()
に渡されるQPainter
は、デフォルトでビューポート座標系(ピクセル単位)で動作します。一方、QGraphicsScene
やQGraphicsItem
の描画はシーン座標系で動作します。この違いを理解していないと、意図しない描画結果になります。 - トラブルシューティング
drawForeground()
内での描画は、常にビューポートの左上 (0,0
) を原点とするピクセル座標で行われると認識してください。- もしシーン上の特定のアイテムの位置に合わせて何かを描画したい場合は、そのアイテムのシーン座標をビューポート座標に変換する必要があります。これには
QGraphicsView::mapFromScene(const QPointF &scenePoint)
やQGraphicsView::mapFromScene(const QRectF &sceneRect)
を使用します。 - ズームによって描画内容のサイズを変えたくない場合(例:常に同じフォントサイズでテキストを表示したい場合)は、
QPainter::setWorldMatrixEnabled(false)
を使用してペインターのワールド変換を一時的に無効にするか、QPainter::save()
とQPainter::restore()
で状態を保存・復元し、painter->setTransform(QTransform())
で変換をリセットしてから描画し、その後元の変換に戻すといった方法も考えられます。
- 原因
描画がちらつく(フリッカー)
問題
drawForeground()
で描画される内容が頻繁に更新される際、画面がちらつく。
原因とトラブルシューティング
- ダブルバッファリングの確認
- 原因
Qtのウィジェットは通常ダブルバッファリングされているため、描画がちらつくことは少ないはずです。しかし、何らかの理由でダブルバッファリングが正しく機能していない可能性があります。 - トラブルシューティング
QGraphicsView
はQWidget
を継承しており、通常はデフォルトでダブルバッファリングが有効です。setViewportUpdateMode()
をFullViewportUpdate
以外のモード(例:BoundingRectViewportUpdate
やMinimalViewportUpdate
)に設定してみることも、描画頻度を減らすことでちらつきを抑えるのに役立つ場合があります。ただし、これはdrawForeground()
の動作ではなく、ビュー全体の更新モードに影響します。
- 原因
- 部分更新の不足
- 原因
viewport()->update()
やviewport()->repaint()
を引数なしで呼び出すと、ビューポート全体が再描画されます。変更が小さい場合でも全体を再描画することで、ちらつきが生じることがあります。 - トラブルシューティング
変化した領域のみを再描画するようにviewport()->update(const QRect &rect)
を使用します。rect
引数には、再描画が必要な最小限の領域を指定します。
- 原因
QPainter の状態管理の誤り
問題
drawForeground()
で設定したペンの色やフォントなどが、他の描画に影響を与えてしまう。
原因とトラブルシューティング
パフォーマンスの問題
問題
drawForeground()
内の描画が重く、アプリケーションの動作が遅くなる。
- 複雑すぎる描画
- 原因
drawForeground()
は頻繁に呼び出される可能性があるため、その中で複雑な計算や大量の描画を行うとパフォーマンスが低下します。 - トラブルシューティング
- 描画内容の最適化
可能な限り描画内容をシンプルに保ちます。ループ処理内で大量のプリミティブを描画するのを避けるなど。 - キャッシュ
描画内容が静的である、またはあまり変化しない場合は、QPixmap
やQImage
に描画内容を事前にレンダリングしておき、drawForeground()
内ではそのキャッシュされた画像を単に描画するようにします。 - rect 引数の活用
drawForeground()
に渡されるrect
引数は、実際に描画が必要な領域を示しています。この領域の外を描画しないようにすることで、無駄な描画を避けてパフォーマンスを向上させることができます。
- 描画内容の最適化
- 原因
QGraphicsView::drawForeground()
は、QGraphicsScene
の内容(アイテム、背景、前景)の上に、ビュー固有の描画を行うために使用されます。ここでは、いくつかの具体的な使用例をコードと共に示します。
例1: ビューポートに固定されたデバッグ情報を表示する
この例では、ビューポートの右下隅に、現在のズームレベルやマウスカーソルのシーン座標を表示するデバッグ情報(HUD)を描画します。この情報は、ズームやスクロールに関わらずビューポート内の同じ位置に固定されます。
mygraphicsview.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QPainter> // QPainter を使うため
#include <QDebug> // デバッグ出力のため
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT // QObject のマクロ(シグナル・スロットを使う場合)
public:
MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
protected:
// QGraphicsView::drawForeground() をオーバーライド
void drawForeground(QPainter *painter, const QRectF &rect) override;
// マウスイベントを処理してカーソル位置を取得するため
void mouseMoveEvent(QMouseEvent *event) override;
private:
QPointF m_mouseScenePos; // マウスカーソルのシーン座標
};
#endif // MYGRAPHICSVIEW_H
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QMouseEvent> // QMouseEvent のため
#include <QTransform> // 変換情報の取得のため
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
setMouseTracking(true); // マウストラックを有効にして、マウスが動くたびに mouseMoveEvent を受け取るようにする
}
void MyGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
{
// 親クラスの描画を呼び出す (通常は必要)
QGraphicsView::drawForeground(painter, rect);
// QPainter の現在の状態を保存
// これにより、ここでの設定が他の描画に影響を与えないようにする
painter->save();
// デバッグ情報の描画設定
painter->setPen(Qt::white); // 白いペン
painter->setFont(QFont("Arial", 10)); // Arial 10pt フォント
painter->setBrush(QColor(0, 0, 0, 150)); // 半透明の黒い背景
// ビューポートの矩形を取得(ピクセル座標)
QRect viewRect = viewport()->rect();
// ズームレベルの計算 (xスケールを使用)
qreal zoomLevel = transform().m11(); // QTransform の m11 はX軸のスケール要素
// 描画するテキスト
QString zoomText = QString("Zoom: %1x").arg(zoomLevel, 0, 'f', 2); // ズームレベルを小数点以下2桁で表示
QString mouseText = QString("Mouse (Scene): X=%1, Y=%2").arg(m_mouseScenePos.x(), 0, 'f', 1).arg(m_mouseScenePos.y(), 0, 'f', 1);
// テキストのサイズを計算
QFontMetrics fm(painter->font());
int zoomTextWidth = fm.horizontalAdvance(zoomText);
int mouseTextWidth = fm.horizontalAdvance(mouseText);
int lineHeight = fm.height() + 2; // 行の高さ
// デバッグ情報の表示位置を計算 (ビューポートの右下)
int padding = 10;
int maxTextWidth = qMax(zoomTextWidth, mouseTextWidth);
// 背景矩形
QRect textRect = QRect(viewRect.width() - maxTextWidth - 2 * padding,
viewRect.height() - 2 * lineHeight - 2 * padding,
maxTextWidth + 2 * padding,
2 * lineHeight + 2 * padding);
// 背景を描画
painter->drawRoundedRect(textRect, 5, 5); // 角丸の矩形
// テキストを描画
painter->drawText(textRect.left() + padding,
textRect.top() + padding + fm.ascent(), // ascent でベースラインを調整
zoomText);
painter->drawText(textRect.left() + padding,
textRect.top() + padding + fm.ascent() + lineHeight,
mouseText);
// QPainter の状態を復元
painter->restore();
}
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
// マウスカーソル位置をシーン座標に変換
m_mouseScenePos = mapToScene(event->pos());
// ビューのフォアグラウンドを更新して、デバッグ情報を再描画
// ここで viewport()->update() を呼ぶことで drawForeground が再実行される
// 変更された領域 (デバッグ情報が表示される右下の領域) のみを更新することでパフォーマンスを向上
QRect debugInfoRect = QRect(viewport()->rect().width() - 200, viewport()->rect().height() - 70, 200, 70); // 大体の領域
viewport()->update(debugInfoRect);
// 親クラスのイベントハンドラを呼び出す (重要)
QGraphicsView::mouseMoveEvent(event);
}
main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include "mygraphicsview.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(-200, -200, 400, 400); // シーンの範囲を設定
// シーンにアイテムを追加
QGraphicsRectItem *rectItem1 = new QGraphicsRectItem(-100, -100, 50, 50);
rectItem1->setBrush(Qt::blue);
scene.addItem(rectItem1);
QGraphicsRectItem *rectItem2 = new QGraphicsRectItem(50, 50, 80, 80);
rectItem2->setBrush(Qt::green);
scene.addItem(rectItem2);
// カスタムの MyGraphicsView を作成
MyGraphicsView view(&scene);
view.setWindowTitle("QGraphicsView::drawForeground Example");
view.setRenderHint(QPainter::Antialiasing); // アンチエイリアスを有効にする(描画を滑らかに)
view.resize(600, 400); // ウィンドウサイズ
view.show();
return a.exec();
}
解説
mouseMoveEvent
をオーバーライドし、マウスカーソルが動くたびにm_mouseScenePos
を更新し、viewport()->update()
を呼び出してdrawForeground()
を再描画させています。これにより、マウス位置の変化がすぐにHUDに反映されます。transform().m11()
で現在のズームレベルを取得しています。viewport()->rect()
でビューポートのピクセル座標系での矩形を取得し、これを使ってデバッグ情報を常に右下隅に配置しています。drawForeground()
内では、painter->save()
とpainter->restore()
を使って、描画設定が他の描画に影響しないようにしています。MyGraphicsView
クラスを作成し、drawForeground()
をオーバーライドしています。
例2: ビューポートにルーラー(定規)を表示する
この例では、ビューポートの上端と左端に、シーン座標に対応したルーラーを描画します。ルーラーはビューポートに固定されますが、表示される数値はシーンのズームとスクロールに合わせて変化します。
mygraphicsview.h
(変更なし)
上記例1の mygraphicsview.h
と同じでOKです。
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QMouseEvent>
#include <QTransform>
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
setMouseTracking(true); // マウストラックを有効に
}
void MyGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawForeground(painter, rect);
painter->save();
// ルーラーの描画設定
painter->setPen(QPen(Qt::darkGray, 1)); // 濃い灰色の線
painter->setFont(QFont("Arial", 8)); // 小さめのフォント
painter->setBrush(QColor(240, 240, 240, 200)); // 半透明の薄い灰色の背景
int rulerSize = 25; // ルーラーの幅/高さ (ピクセル)
QRect viewRect = viewport()->rect();
// --- 水平ルーラーの描画 ---
QRectF horzRulerRect(viewRect.left() + rulerSize, viewRect.top(), viewRect.width() - rulerSize, rulerSize);
painter->drawRect(horzRulerRect); // ルーラーの背景
// 描画するグリッド間隔をズームレベルに応じて調整
qreal currentScale = transform().m11(); // 現在のズームレベル
qreal gridInterval = 100.0; // デフォルトのグリッド間隔 (シーン単位)
if (currentScale < 0.5) gridInterval = 500.0;
else if (currentScale < 1.0) gridInterval = 200.0;
else if (currentScale < 2.0) gridInterval = 100.0;
else if (currentScale < 5.0) gridInterval = 50.0;
else if (currentScale < 10.0) gridInterval = 20.0;
else gridInterval = 10.0; // さらにズームしたら10
// ルーラーの目盛りと数値を描画
// ビューポートの左端 (ピクセル) に対応するシーン座標
qreal startSceneX = mapToScene(viewRect.topLeft()).x();
qreal endSceneX = mapToScene(viewRect.topRight()).x();
// 最初の目盛りの開始位置を計算
qreal firstTickX = qCeil(startSceneX / gridInterval) * gridInterval;
for (qreal sceneX = firstTickX; sceneX <= endSceneX; sceneX += gridInterval) {
QPointF viewPoint = mapFromScene(sceneX, 0); // シーンX座標をビューポートピクセル座標に変換
int pixelX = qRound(viewPoint.x());
if (pixelX >= horzRulerRect.left() && pixelX <= horzRulerRect.right()) {
// 長い目盛り
painter->drawLine(pixelX, horzRulerRect.top(), pixelX, horzRulerRect.bottom());
// 数値
painter->drawText(pixelX + 2, horzRulerRect.bottom() - 2, QString::number(sceneX));
}
// 中間の目盛り (例: gridInterval/2)
qreal middleTickX = sceneX + gridInterval / 2.0;
if (middleTickX < endSceneX) {
QPointF middleViewPoint = mapFromScene(middleTickX, 0);
int middlePixelX = qRound(middleViewPoint.x());
if (middlePixelX >= horzRulerRect.left() && middlePixelX <= horzRulerRect.right()) {
painter->drawLine(middlePixelX, horzRulerRect.top() + rulerSize / 2, middlePixelX, horzRulerRect.bottom());
}
}
}
// --- 垂直ルーラーの描画 ---
QRectF vertRulerRect(viewRect.left(), viewRect.top() + rulerSize, rulerSize, viewRect.height() - rulerSize);
painter->drawRect(vertRulerRect); // ルーラーの背景
// 垂直ルーラーの目盛りと数値を描画
qreal startSceneY = mapToScene(viewRect.topLeft()).y();
qreal endSceneY = mapToScene(viewRect.bottomLeft()).y();
// 最初の目盛りの開始位置を計算
qreal firstTickY = qCeil(startSceneY / gridInterval) * gridInterval;
for (qreal sceneY = firstTickY; sceneY <= endSceneY; sceneY += gridInterval) {
QPointF viewPoint = mapFromScene(0, sceneY); // シーンY座標をビューポートピクセル座標に変換
int pixelY = qRound(viewPoint.y());
if (pixelY >= vertRulerRect.top() && pixelY <= vertRulerRect.bottom()) {
// 長い目盛り
painter->drawLine(vertRulerRect.left(), pixelY, vertRulerRect.right(), pixelY);
// 数値
painter->drawText(vertRulerRect.left() + 2, pixelY + 2 + painter->fontMetrics().ascent(), QString::number(sceneY));
}
// 中間の目盛り
qreal middleTickY = sceneY + gridInterval / 2.0;
if (middleTickY < endSceneY) {
QPointF middleViewPoint = mapFromScene(0, middleTickY);
int middlePixelY = qRound(middleViewPoint.y());
if (middlePixelY >= vertRulerRect.top() && middlePixelY <= vertRulerRect.bottom()) {
painter->drawLine(vertRulerRect.left() + rulerSize / 2, middlePixelY, vertRulerRect.right(), middlePixelY);
}
}
}
// 左上隅のルーラーの角
painter->drawRect(0, 0, rulerSize, rulerSize);
painter->restore();
}
// 例1と同じ mouseMoveEvent を使用
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
m_mouseScenePos = mapToScene(event->pos());
// ルーラーとHUD両方の更新
QRect debugAndRulerArea = QRect(0, 0, viewport()->width(), viewport()->height()); // 全体を更新
viewport()->update(debugAndRulerArea);
QGraphicsView::mouseMoveEvent(event);
}
main.cpp
(変更なし)
上記例1の main.cpp
と同じでOKです。
viewport()->update()
を呼び出すことで、ビューのズームやスクロール(QGraphicsView::setTransform()
やQGraphicsView::translate()
など)が行われたときにdrawForeground
が再描画され、ルーラーの数値が適切に更新されます。gridInterval
はズームレベルに応じて動的に変化させています。これにより、非常にズームアウトしたときに目盛りが多すぎたり、ズームインしたときに目盛りが少なすぎたりするのを防ぎます。mapFromScene(sceneX, 0)
やmapFromScene(0, sceneY)
を使用して、シーン座標での目盛り位置をビューポートのピクセル座標に変換し、そこに目盛り線と数値を描画しています。mapToScene(viewRect.topLeft())
を使用して、ビューポートのピクセル座標をシーン座標に変換し、ルーラーの開始位置と終了位置を計算しています。drawForeground()
内で、水平ルーラーと垂直ルーラーを描画しています。
QGraphicsView::drawForeground()
の代替手段
QGraphicsScene::drawForeground() の利用
説明
QGraphicsScene
にも drawForeground()
という仮想関数があり、これをオーバーライドすることでシーン全体の前景を描画できます。
QGraphicsScene::drawForeground(QPainter *painter, const QRectF &rect)
違いと使い分け
QGraphicsScene::drawForeground()
: シーン座標系で描画され、シーン全体の前景(全てのビューに共通して表示されるグリッド線、特定の領域のハイライトなど)に適しています。ズームやスクロールによって描画内容の位置やスケールが変わります。QGraphicsView::drawForeground()
: ビューポート座標系で描画され、ビュー固有の描画(ズームやスクロールに影響されないUI要素など)に適しています。
利点
- シーン座標系で描画できるため、シーンのアイテムとの位置関係を考慮しやすいです。
- シーンの前景として描画されるため、複数の
QGraphicsView
が同じシーンを表示している場合、全てのビューで同じ前景が表示されます。
欠点
- 個々のビューに異なる前景を表示することはできません。
- ビューポートに固定された要素(例: ルーラー、HUD、デバッグ情報)を実装するには、ビューポート座標系への変換が別途必要になり、複雑になります。
ユースケース
- シーン全体の境界線。
- シーン上の特定の領域をハイライトするオーバーレイ。
- シーン全体に適用されるカスタムグリッド。
QGraphicsItem を使用する
説明
QGraphicsItem
をシーンに追加し、その paint()
メソッドでカスタム描画を行うことができます。特に、ビューポートに固定したい場合は、カスタムアイテムを作成し、その itemChange()
メソッドで ItemSceneHasChanged
イベントを処理し、ビューポートの位置に合わせてアイテムの位置を更新する方法があります。
利点
QGraphicsEffect
を適用することも可能です。- Z-値 (Z-order) を使用して他のアイテムとの描画順序を細かく制御できます。
- アイテムはイベントを受け取ることができ(クリック、ホバーなど)、インタラクティブな前景要素を作成できます。
- Qtのアイテム管理システムに完全に統合されます。
欠点
- 非常に単純な描画(例: 一時的なマーカー)の場合、アイテムとして管理するオーバーヘッドが大きいと感じるかもしれません。
- ビューポートに固定されたアイテムを実現するには、ビューの
scrollContentsBy()
やresizeEvent()
を捕捉してアイテムの位置を更新するロジックが必要になり、手間がかかる場合があります。
ユースケース
- ユーザーがドラッグして移動できるルーラーやガイド。
- アイテムに付随するカスタムラベルや注釈。
- 選択ツールで表示されるドラッグ領域の矩形。
- ドラッグ操作中に表示される仮のガイドライン。
QPainterPath と QGraphicsPathItem を利用する
説明
もし描画内容が主に図形や線である場合、QPainterPath
を使用して描画形状を定義し、それを QGraphicsPathItem
にラップしてシーンに追加することもできます。
利点
QGraphicsItem
の全ての利点(イベント処理、Z-値など)を享受できます。- 複雑な形状の定義が容易です。
欠点
- ビューポート固定の課題は
QGraphicsItem
と同様です。 - 主に図形描画向けであり、テキストや画像などには直接的ではありません(ただし、パスの一部として含めることは可能)。
ユースケース
- カスタムの枠線や背景。
- フローチャートのコネクタライン。
- カスタムの複雑なグリッドパターン。
QGraphicsView の viewportEvent() または paintEvent() を利用する
説明
QGraphicsView
は QWidget
から継承しているため、paintEvent(QPaintEvent *event)
をオーバーライドして直接ビューポートに描画することも可能です。ただし、これは通常推奨されません。
代わりに、viewportEvent(QEvent *event)
をオーバーライドし、ビューポートの paintEvent
に対応するイベントを処理することもできます。
利点
- 直接
QPainter
を使用するため、オーバーヘッドが少ない場合があります。 - 非常に低レベルでの描画制御が可能です。
欠点
- 通常、
drawBackground()
やdrawForeground()
をオーバーライドする方が、Graphics View Frameworkの意図に沿った方法です。 - 特に複雑な描画ロジックの場合、QtのGraphics View Frameworkの恩恵(アイテム管理、変換など)を受けられなくなります。
QGraphicsView
の描画パイプライン(背景、シーンアイテム、前景の順序)から外れるため、他の要素との描画順序の管理が複雑になります。
- 非推奨
非常に特殊な描画が必要で、他の方法では実現できない場合。
-
特定のシーン描画の上に、シンプルなオーバーレイを素早く表示したい場合:
QGraphicsView::drawForeground()
がシンプルで効率的です。
-
インタラクティブな描画要素、または既存のアイテムに関連付けられた描画:
- 最良の選択肢
カスタムQGraphicsItem
- 最良の選択肢
-
全てのビューに共通で、シーン座標系で描画される前景:
- 最良の選択肢
QGraphicsScene::drawForeground()
- 最良の選択肢
-
ビューポートに固定され、シーンの内容に影響されない(ズーム・スクロールしても動かない)UI要素:
- 最良の選択肢
QGraphicsView::drawForeground()
- 次善の選択肢: カスタム
QGraphicsItem
を作成し、ビューポートの動きに合わせて位置を更新する(より複雑なインタラクションやイベント処理が必要な場合)。
- 最良の選択肢