QGraphicsViewの落とし穴と対策:Qtグラフィック表示のトラブルシューティング

2025-05-27

QGraphicsViewは、QtのGraphics Viewフレームワークの一部であり、QGraphicsSceneの内容を表示するためのウィジェットです。Graphics Viewフレームワークは、大量の2Dグラフィックアイテムを管理し、インタラクティブに操作するための強力なシステムを提供します。

簡単に言うと、QGraphicsSceneが「描画する世界(キャンバス)」であるのに対し、QGraphicsViewは「その世界を見るための窓やカメラ」のような役割を果たします。

主要な役割と特徴

  1. Sceneの表示: QGraphicsViewは、関連付けられたQGraphicsSceneに配置されたすべてのアイテム(図形、画像、テキストなど)を表示します。
  2. ビュー変換(Transformations):
    • ズーム: scale()メソッドを使って、シーンの表示を拡大・縮小できます。
    • パン(移動): マウスのドラッグ操作などでビューポートを移動させ、シーンの異なる部分を表示できます。
    • 回転: rotate()メソッドを使って、シーンの表示を回転させることができます。
  3. 座標変換: QGraphicsViewは、ビューの座標系(ウィジェット上のピクセル座標)とシーンの座標系(シーン内の論理座標)間のマッピングを担当します。これにより、マウスイベントなどのビュー座標をシーン座標に変換したり、その逆を行ったりすることができます。
  4. 複数のビュー: 一つのQGraphicsSceneに対して、複数のQGraphicsViewインスタンスを関連付けることができます。これにより、同じシーンの内容を異なる視点や異なるズームレベルで同時に表示することが可能です。例えば、CADアプリケーションで全体図と詳細図を同時に表示するような場合に便利です。
  5. インタラクティブ性: QGraphicsViewは、ユーザーの入力イベント(マウスのクリック、ドラッグ、ホイールスクロールなど)を捕捉し、それをシーン内のアイテムに転送します。これにより、アイテムがイベントに応答し、インタラクティブな動作(アイテムの選択、移動、サイズ変更など)を実現できます。
  6. パフォーマンス: 大量のグラフィックアイテムを効率的にレンダリングするために、様々な最適化機能を提供します。例えば、表示されている範囲のみをレンダリングするカリングや、キャッシュモードなどがあります。

構造のイメージ

  • QGraphicsView: シーンを表示するためのウィジェット。ユーザーとのインタラクションを処理し、ビュー変換を適用します。
  • QGraphicsItem: シーン上に描画される個々の要素(QGraphicsRectItemQGraphicsEllipseItemQGraphicsTextItemなど)。
  • QGraphicsScene: アイテムが配置される仮想的なキャンバス。アイテムの管理(追加、削除、衝突検出など)を行います。

使用例の簡単な流れ

  1. QGraphicsSceneのインスタンスを作成します。
  2. 描画したいQGraphicsItemのサブクラスのインスタンスを作成し、QGraphicsScene::addItem()でシーンに追加します。
  3. QGraphicsViewのインスタンスを作成します。
  4. QGraphicsView::setScene()メソッドを使って、作成したシーンをビューに設定します。
  5. QGraphicsViewをウィジェットとして表示します(例: レイアウトに追加するか、メインウィンドウの中央ウィジェットに設定するなど)。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem> // 例として四角形アイテム

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

    // 1. シーンを作成
    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600); // シーンの論理的なサイズを設定

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

    // 3. ビューを作成
    QGraphicsView view(&scene); // シーンをビューに関連付け
    view.setWindowTitle("QGraphicsView の例");
    view.setRenderHint(QPainter::Antialiasing); // アンチエイリアスを有効にする
    view.setDragMode(QGraphicsView::ScrollHandDrag); // マウスドラッグでパンできるようにする

    // 4. ビューを表示
    view.show();

    return a.exec();
}

この例では、青い四角形が描画されたシーンをQGraphicsViewが表示します。ユーザーはマウスドラッグで四角形を移動させたり、ホイールでズームしたりできるかもしれません(setDragModewheelEventのオーバーライドなどによって)。



QGraphicsViewは非常に柔軟で強力なツールですが、その複雑さゆえに、いくつかの一般的な問題に直面することがあります。ここでは、よくあるエラーとその解決策について説明します。

何も表示されない(真っ白なビュー)

最もよくある問題の一つです。

  • トラブルシューティング

    • QGraphicsView::setScene(myScene);が正しく呼び出されているか確認します。
    • myScene->addItem(myItem);が正しく呼び出されているか確認します。
    • myItem->pos()myItem->scenePos()でアイテムの位置を確認し、myScene->sceneRect()の範囲内にあるか確認します。必要に応じてQGraphicsScene::setSceneRect()を適切に設定します。
    • QGraphicsItem::isVisible()を確認します。
    • デバッガを使って、オブジェクトが適切に初期化され、有効なポインタを持っているか確認します。
    • シーンがビューに設定されていない
      QGraphicsView::setScene()を呼び出していない、または間違ったシーンを設定している。
    • アイテムがシーンに追加されていない
      QGraphicsScene::addItem()QGraphicsItemをシーンに追加し忘れている。
    • アイテムがシーンの範囲外にある
      QGraphicsItemQGraphicsScene::sceneRect()で定義された範囲外に配置されている。ビューがその範囲外のアイテムを自動的に表示しない場合があります。
    • シーンのサイズ(sceneRect)が不適切
      QGraphicsScene::setSceneRect()を設定していないか、アイテムが収まりきらないほど小さい範囲を設定している。デフォルトのsceneRectは無限大ですが、明示的に設定しないと予期しない挙動になることがあります。
    • アイテムが非表示になっている
      QGraphicsItem::setVisible(false)が呼び出されている、または親アイテムが非表示になっている。
    • ビューが適切に表示されていない
      QGraphicsViewがウィジェット階層に追加されていない、またはshow()が呼び出されていない。
    • ポインタの問題
      QGraphicsSceneQGraphicsViewQGraphicsItemをローカル変数として作成し、スコープを抜けるとすぐに破棄されてしまうケース。これらはヒープに割り当て(newで作成)し、適切なライフサイクル管理を行う必要があります。

アイテムが更新されない/変更が反映されない

アイテムのプロパティを変更したのに、ビューにその変更が反映されないことがあります。

  • トラブルシューティング

    • アイテムのプロパティを変更した後、QGraphicsItem::update()またはQGraphicsScene::update()を呼び出します。アイテムが頻繁に動く場合は、QGraphicsItem::prepareGeometryChange()を呼び出してから変更を行い、その後にupdate()を呼び出すと効率的です。
    • カスタムアイテムの場合、boundingRect()の実装が正確であることを確認します。アイテムのすべての描画内容を囲む最小の長方形を返す必要があります。線幅なども考慮に入れる必要があります。
  • 考えられる原因

    • 更新のトリガーが不足
      QGraphicsItemのプロパティ(位置、色など)を変更した後、ビューに再描画を指示していない。
    • アイテムのboundingRect()が不適切
      QGraphicsItem::boundingRect()がアイテムの実際の描画領域を正確に返していない場合、Qtは再描画が必要な領域を正しく判断できず、更新がスキップされることがあります。

パフォーマンスの問題(動作が遅い、カクつく)

多数のアイテムを扱ったり、複雑な描画を行う場合に発生します。

  • 考えられる原因

    • アンチエイリアシングの多用
      QGraphicsView::setRenderHint(QPainter::Antialiasing)は見た目を向上させますが、描画コストが増加します。
    • 不適切なキャッシュモード
      QGraphicsView::setCacheMode()NoCacheになっているか、CacheBackgroundが適切に設定されていない。
    • boundingRect()の不正確さ
      アイテムのboundingRect()が実際の描画領域よりも大きすぎると、不必要な再描画が頻繁に発生します。
    • QGraphicsItem::paint()の実装の非効率性
      カスタムアイテムのpaint()メソッド内で複雑な計算やリソースを消費する処理を行っている。
    • アイテムの総数が多い
      数千、数万のアイテムがある場合、描画自体がボトルネックになることがあります。
    • 描画のクリッピング不足
      QGraphicsItem::paint()内でQPainter::setClipRect()を使用して、実際に再描画が必要な部分のみを描画するように最適化されていない。

マウスイベント、座標変換のずれ

マウスのクリック位置とアイテムの認識位置がずれる、ズーム・パン後にイベントがずれるなど。

  • トラブルシューティング

    • 座標変換関数の理解と適切な使用
      • QGraphicsView::mapToScene(QPoint viewPoint): ビュー座標をシーン座標に変換。
      • QGraphicsView::mapFromScene(QPointF scenePoint): シーン座標をビュー座標に変換。
      • QGraphicsItem::mapToScene(QPointF itemPoint): アイテムのローカル座標をシーン座標に変換。
      • QGraphicsItem::mapFromScene(QPointF scenePoint): シーン座標をアイテムのローカル座標に変換。 これらの関数を正確に使用します。
    • イベントハンドラ(mousePressEventなど)内で、イベントのpos()は常にビュー座標なので、mapToScene()でシーン座標に変換してからアイテムの操作を行います。
  • 考えられる原因

    • 座標変換の誤解
      シーン座標、ビュー座標、アイテム座標の変換を混同している。
    • 変換行列の不適切な適用
      QGraphicsView::transform()QGraphicsItem::transform()が意図せず変更されている。

メモリリーク/クラッシュ

アプリケーションがクラッシュしたり、メモリ使用量が増加し続けたりする。

  • トラブルシューティング

    • Qtのオブジェクトは親子関係を持つことで、親が破棄されるときに子も自動的に破棄されます。QGraphicsItemQGraphicsScene::addItem()で追加すると、シーンがそのアイテムの親になります。しかし、QGraphicsScene自体や、QGraphicsViewのインスタンスは、適切な親を設定するか、アプリケーション終了時に手動でdeleteする必要があります。
    • スマートポインタ(std::unique_ptrQSharedPointerなど)を使用して、オブジェクトのライフサイクル管理を自動化することを検討します。
    • デバッグビルドで実行し、メモリデバッガ(Valgrindなど)を使用してメモリリークや不正なメモリアクセスを特定します。
  • 考えられる原因

    • オブジェクトの所有権の問題
      QGraphicsSceneQGraphicsItemnewで作成したが、deleteし忘れている。特に、親オブジェクトを設定していない場合、手動で解放する必要があります。
    • 循環参照
      稀ですが、オブジェクト間に循環参照が生じていてメモリが解放されないケース。
    • 不正なポインタの使用
      解放済みのメモリにアクセスしようとしている。
  • Qt Forum/Stack Overflowの活用
    似たような問題に遭遇している人がいないか検索し、解決策を見つけます。
  • デバッグ出力の活用
    qDebug()を使って、アイテムの位置やサイズ、更新頻度などをコンソールに出力し、問題の箇所を特定します。


QGraphicsView を用いたグラフィックアプリケーションの基本的な構築方法から、インタラクティブな機能の追加まで、主要な例を説明します。

基本的な表示(シンプルな四角形)

最も基本的な QGraphicsView の使い方です。シーンを作成し、そこにアイテムを追加し、ビューで表示します。

main.cpp

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem> // 四角形アイテム

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

    // 1. QGraphicsScene のインスタンスを作成
    QGraphicsScene scene;
    // シーンの論理的な範囲を設定(任意だが推奨)
    // この範囲外のアイテムは描画されない可能性がある
    scene.setSceneRect(0, 0, 400, 300);

    // 2. QGraphicsItem を作成し、シーンに追加
    // 四角形アイテム (x, y, width, height)
    QGraphicsRectItem *rectItem = new QGraphicsRectItem(50, 50, 100, 80);
    rectItem->setBrush(Qt::blue); // 青色のブラシを設定
    rectItem->setPen(QPen(Qt::red, 2)); // 赤色で太さ2のペンを設定
    scene.addItem(rectItem); // シーンにアイテムを追加

    // もう一つ別の四角形を少しずらして追加
    QGraphicsRectItem *anotherRect = new QGraphicsRectItem(150, 120, 120, 90);
    anotherRect->setBrush(Qt::green);
    anotherRect->setRotation(15); // 15度回転
    scene.addItem(anotherRect);

    // 3. QGraphicsView のインスタンスを作成し、シーンを設定
    QGraphicsView view(&scene);
    view.setWindowTitle("基本的なQGraphicsViewの例");
    view.setRenderHint(QPainter::Antialiasing); // 描画を滑らかにする(パフォーマンスに影響あり)
    view.resize(600, 400); // ビューのウィンドウサイズを設定

    // 4. ビューを表示
    view.show();

    return a.exec();
}

このコードでは、QGraphicsScene上に2つの四角形を配置し、QGraphicsViewを通してそれらを表示しています。setRenderHint(QPainter::Antialiasing)は、図形の縁を滑らかにするためのオプションです。

ズームとパン(インタラクティブな操作)

QGraphicsViewを継承し、マウスホイールでズーム、マウスドラッグでパン(移動)ができるようにします。

CustomGraphicsView.h

#ifndef CUSTOMGRAPHICSVIEW_H
#define CUSTOMGRAPHICSVIEW_H

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

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

protected:
    // マウスホイールイベントをオーバーライドしてズームを実装
    void wheelEvent(QWheelEvent *event) override;
    // マウスプレスイベントをオーバーライドしてパンの開始を検出
    void mousePressEvent(QMouseEvent *event) override;
    // マウスムーブイベントをオーバーライドしてパンを実装
    void mouseMoveEvent(QMouseEvent *event) override;
    // マウスリリースイベントをオーバーライドしてパンの終了を検出
    void mouseReleaseEvent(QMouseEvent *event) override;

private:
    bool m_panning;     // パン中かどうか
    QPointF m_lastPanPos; // パン開始時のマウス位置(シーン座標)
};

#endif // CUSTOMGRAPHICSVIEW_H

CustomGraphicsView.cpp

#include "CustomGraphicsView.h"
#include <QDebug> // デバッグ出力用

CustomGraphicsView::CustomGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent),
      m_panning(false)
{
    // ビューの中心を基準にズームを行うように設定
    setTransformationAnchor(AnchorUnderMouse);
    // スクロールバーは表示しない(パンで移動するため)
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}

void CustomGraphicsView::wheelEvent(QWheelEvent *event) {
    // ズーム係数を計算
    qreal scaleFactor = 1.15; // ズームの速さ
    if (event->angleDelta().y() > 0) {
        // ホイールを上に回した場合(ズームイン)
        scale(scaleFactor, scaleFactor);
    } else {
        // ホイールを下に回した場合(ズームアウト)
        scale(1.0 / scaleFactor, 1.0 / scaleFactor);
    }
    event->accept(); // イベントを処理済みとしてマーク
}

void CustomGraphicsView::mousePressEvent(QMouseEvent *event) {
    if (event->button() == Qt::MiddleButton) { // 中央ボタン(マウスホイール)でパンを開始
        m_panning = true;
        // マウスプレス時のシーン座標を記録
        m_lastPanPos = mapToScene(event->pos());
        setCursor(Qt::ClosedHandCursor); // カーソルを閉じた手アイコンに変更
        event->accept();
    } else {
        // それ以外のボタンは親クラスの処理に任せる
        QGraphicsView::mousePressEvent(event);
    }
}

void CustomGraphicsView::mouseMoveEvent(QMouseEvent *event) {
    if (m_panning) {
        // 現在のマウス位置のシーン座標
        QPointF currentPanPos = mapToScene(event->pos());
        // 移動量
        QPointF delta = currentPanPos - m_lastPanPos;
        // シーンを移動 (スクロールバーを操作するようなイメージ)
        // ビューポートをdx, dyだけスクロール
        scrollContentsBy(-static_cast<int>(delta.x()), -static_cast<int>(delta.y()));
        m_lastPanPos = currentPanPos; // 位置を更新
        event->accept();
    } else {
        QGraphicsView::mouseMoveEvent(event);
    }
}

void CustomGraphicsView::mouseReleaseEvent(QMouseEvent *event) {
    if (event->button() == Qt::MiddleButton) {
        m_panning = false;
        setCursor(Qt::ArrowCursor); // カーソルを元に戻す
        event->accept();
    } else {
        QGraphicsView::mouseReleaseEvent(event);
    }
}

main.cpp (CustomGraphicsViewを使用)

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include "CustomGraphicsView.h" // 作成したカスタムビューをインクルード

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

    QGraphicsScene scene;
    scene.setSceneRect(-200, -150, 400, 300); // センターが(0,0)になるように設定

    QGraphicsRectItem *rectItem = new QGraphicsRectItem(-50, -50, 100, 100); // センターに配置
    rectItem->setBrush(Qt::blue);
    scene.addItem(rectItem);

    // カスタムビューを使用
    CustomGraphicsView view(&scene);
    view.setWindowTitle("ズームとパンの例");
    view.setRenderHint(QPainter::Antialiasing);
    view.resize(600, 400);

    view.show();

    return a.exec();
}

この例では、CustomGraphicsViewを定義し、wheelEventでズーム、mousePressEventmouseMoveEventmouseReleaseEventでパンを実装しています。setTransformationAnchor(AnchorUnderMouse)は、マウスカーソルがある位置を中心にズームするように設定する重要なメソッドです。

カスタムQGraphicsItemの作成

独自の描画ロジックを持つアイテムを作成します。ここでは、星形を描画するカスタムアイテムの例を示します。

StarItem.h

#ifndef STARITEM_H
#define STARITEM_H

#include <QGraphicsItem>
#include <QPainter>

class StarItem : public QGraphicsItem {
public:
    explicit StarItem(QGraphicsItem *parent = nullptr);

    // QGraphicsItem::boundingRect() をオーバーライド
    // アイテムの描画範囲を返します。これが正確でないと描画更新がおかしくなります。
    QRectF boundingRect() const override;

    // QGraphicsItem::paint() をオーバーライド
    // アイテムの実際の描画を行います。
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;

    // オプション:アイテムがクリック可能であることを示す
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;

private:
    QColor m_color; // 星の色
};

#endif // STARITEM_H

StarItem.cpp

#include "StarItem.h"
#include <QGraphicsSceneMouseEvent>
#include <QRandomGenerator> // ランダムな色生成用

StarItem::StarItem(QGraphicsItem *parent)
    : QGraphicsItem(parent)
{
    // アイテムが選択可能、移動可能、クリック可能であることを設定
    setFlags(ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges);
    // ランダムな色を生成
    m_color = QColor(QRandomGenerator::global()->bounded(256),
                     QRandomGenerator::global()->bounded(256),
                     QRandomGenerator::global()->bounded(256));
}

QRectF StarItem::boundingRect() const {
    // 星の最大サイズを考慮した境界矩形を返す
    // ここでは単純に中心から半径50の円に収まるものとする
    return QRectF(-50, -50, 100, 100);
}

void StarItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
    Q_UNUSED(option); // 未使用の引数
    Q_UNUSED(widget); // 未使用の引数

    painter->setBrush(m_color); // 色を設定
    painter->setPen(QPen(Qt::black, 2)); // 黒の輪郭

    // 星形のパスを作成(簡単な五芒星の例)
    QPainterPath path;
    path.moveTo(0, -40); // 上の頂点
    for (int i = 0; i < 5; ++i) {
        path.lineTo(40 * qSin(qDegreesToRadians(72.0 * i + 18.0)), 40 * qCos(qDegreesToRadians(72.0 * i + 18.0)));
        path.lineTo(15 * qSin(qDegreesToRadians(72.0 * i + 54.0)), 15 * qCos(qDegreesToRadians(72.0 * i + 54.0)));
    }
    path.closeSubpath();

    painter->drawPath(path);

    // アイテムが選択されている場合、枠を描画
    if (isSelected()) {
        painter->setPen(QPen(Qt::DashLine)); // 点線
        painter->drawRect(boundingRect());
    }
}

void StarItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
    // デバッグ出力
    qDebug() << "Star clicked at scene pos:" << event->scenePos()
             << "item pos:" << event->pos();

    // 親クラスの処理を呼び出し、選択状態の切り替えや移動を可能にする
    QGraphicsItem::mousePressEvent(event);
    update(); // アイテムの描画を更新(選択状態の変更を反映)
}

main.cpp (CustomGraphicsView と StarItem を使用)

#include <QApplication>
#include <QGraphicsScene>
#include "CustomGraphicsView.h"
#include "StarItem.h" // 作成したカスタムアイテムをインクルード
#include <QRandomGenerator>

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

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

    // 複数のStarItemをランダムな位置に追加
    for (int i = 0; i < 5; ++i) {
        StarItem *star = new StarItem();
        // ランダムな位置に配置
        star->setPos(QRandomGenerator::global()->bounded(-250, 250),
                      QRandomGenerator::global()->bounded(-150, 150));
        scene.addItem(star);
    }

    CustomGraphicsView view(&scene);
    view.setWindowTitle("カスタムアイテムとインタラクションの例");
    view.setRenderHint(QPainter::Antialiasing);
    view.resize(800, 600);

    view.show();

    return a.exec();
}

この例では、QGraphicsItemを継承したStarItemクラスを作成し、boundingRect()paint()メソッドをオーバーライドしてカスタムな星形を描画しています。mousePressEventをオーバーライドすることで、アイテムがクリックされた時の挙動を定義できます。また、ItemIsSelectableItemIsMovableフラグを設定することで、Qtの標準機能でアイテムの選択や移動が可能になります。



QWidget を直接使用して描画 (paintEvent のオーバーライド)

最も基本的な代替手段は、カスタムの QWidget を作成し、その paintEvent() メソッドをオーバーライドして、直接 QPainter を使用して描画することです。

  • 適しているケース

    • 動的に変化する内容が少ない、比較的シンプルなグラフや図形描画。
    • 特定の領域に固定された背景や情報を描画する場合。
    • ユーザーが描画する内容をピクセルレベルで完全に制御したい場合。
  • 欠点

    • アイテム管理の欠如
      QGraphicsScene のようなアイテム管理システムがありません。個々の図形を「アイテム」として扱いたい場合、自分でそのロジック(位置、サイズ、選択状態など)を実装する必要があります。
    • 複雑なインタラクション
      ズーム、パン、アイテムの選択、衝突検出など、QGraphicsView が提供するインタラクション機能を自分で実装する必要があります。特に多数のアイテムを扱う場合、パフォーマンスの最適化も課題になります。
    • パフォーマンス(アイテム数が多い場合)
      数百、数千といった大量のアイテムがある場合、すべての描画をpaintEventで一から行うのは非効率的で、パフォーマンスが低下する可能性があります。
    • シンプルさ
      グラフィックスビューフレームワークのオーバーヘッドがなく、シンプルな描画には非常に簡単です。
    • 完全な制御
      描画プロセスを完全に制御できます。

簡単なコード例

#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QMouseEvent>

class CustomPaintWidget : public QWidget {
public:
    CustomPaintWidget(QWidget *parent = nullptr) : QWidget(parent) {
        setMinimumSize(400, 300);
        // ドラッグで四角形を移動できるようにする
        setMouseTracking(true);
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);

        // 背景を描画
        painter.fillRect(rect(), Qt::white);

        // 青い四角形を描画
        painter.setBrush(Qt::blue);
        painter.setPen(Qt::NoPen);
        painter.drawRect(m_rect); // m_rect はクラスメンバーとして定義する

        // テキストを描画
        painter.setPen(Qt::black);
        painter.drawText(10, 20, "Hello from CustomPaintWidget!");
    }

    void mousePressEvent(QMouseEvent *event) override {
        if (m_rect.contains(event->pos())) {
            m_dragging = true;
            m_offset = event->pos() - m_rect.topLeft();
        }
    }

    void mouseMoveEvent(QMouseEvent *event) override {
        if (m_dragging) {
            m_rect.moveTo(event->pos() - m_offset);
            update(); // 再描画を要求
        }
    }

    void mouseReleaseEvent(QMouseEvent *event) override {
        m_dragging = false;
    }

private:
    QRect m_rect = QRect(50, 50, 100, 80);
    bool m_dragging = false;
    QPoint m_offset;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    CustomPaintWidget widget;
    widget.setWindowTitle("QWidgetで直接描画");
    widget.show();
    return a.exec();
}

Qt Quick / QML

Qt Quickは、宣言的なUI記述言語であるQMLを使用し、ハードウェアアクセラレーションを利用したモダンなUI開発のためのフレームワークです。アニメーション、トランジション、タッチ操作などに非常に優れています。

  • 適しているケース

    • モダンでリッチなUI(アニメーション、タッチ操作)を持つアプリケーション。
    • ゲームや情報表示ダッシュボードなど、パフォーマンスが重視されるグラフィカルなアプリケーション。
    • Web技術に馴染みのある開発者。
  • 欠点

    • 学習コスト
      QMLという新しい言語とQt Quickの概念を学ぶ必要があります。
    • 既存のQGraphicsViewコードとの互換性
      QGraphicsItem を直接QMLに移植することはできません。
    • 複雑な図形描画
      QPainterのような低レベルの描画APIを直接使うよりも、QMLのプリミティブ(Rectangle, Path, Shapeなど)やカスタムC++アイテムを通じて描画するため、一部の複雑な幾何学的描画では慣れが必要です。
    • デバッグの複雑さ
      QMLとC++の連携が複雑になると、デバッグが難しくなる場合があります。
  • 利点

    • ハードウェアアクセラレーション
      GPUを活用するため、滑らかなアニメーションや高解像度での描画に優れています。
    • 宣言的UI
      UIの構造と振る舞いをQMLで記述するため、コード量が減り、可読性が向上します。
    • アニメーションとトランジション
      簡単にリッチなアニメーションを実現できます。
    • クロスプラットフォーム
      デスクトップだけでなく、モバイル、組み込みデバイスにも適しています。
    • C++との連携
      C++バックエンドとQMLフロントエンドをシームレスに連携できます。

簡単なQMLコード例 (main.qml)

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "QMLの描画例"

    Rectangle {
        id: myRect
        x: 50
        y: 50
        width: 100
        height: 80
        color: "blue"

        // クリックで色を変える
        MouseArea {
            anchors.fill: parent
            onClicked: parent.color = "red"
        }

        // ドラッグで移動
        MouseArea {
            id: rectDragger
            anchors.fill: parent
            drag.target: parent
            drag.minimumX: 0
            drag.maximumX: parent.parent.width - parent.width
            drag.minimumY: 0
            drag.maximumY: parent.parent.height - parent.height
        }
    }

    Text {
        text: "Hello from QML!"
        x: 10
        y: 20
        font.pixelSize: 18
        color: "black"
    }
}

C++でQMLをロードする例

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); // main.qmlをリソースとして追加
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

QOpenGLWidget / QOpenGLWindow (OpenGLの直接使用)

QGraphicsViewQPainter が提供する抽象化層を避け、OpenGL(またはVulkan/Direct3DなどのグラフィックAPI)を直接使用して描画します。

  • 適しているケース

    • 高パフォーマンスが絶対的に必要な、大量の動的なグラフィック(CADソフトウェア、ゲーム、科学シミュレーションなど)。
    • カスタムなシェーダーやGPUアクセラレーションをフルに活用したい場合。
    • 2Dと3Dグラフィックをシームレスに統合したい場合。
  • 欠点

    • 学習曲線が急
      OpenGLのAPIや概念(シェーダー、行列変換など)を深く理解する必要があります。
    • コード量が多い
      描画のために多くの低レベルなコードを記述する必要があります。
    • プラットフォーム依存性
      OpenGL自体はクロスプラットフォームですが、プラットフォームごとの差異やドライバーの問題に対応する必要がある場合があります。
    • 高レベルな機能の欠如
      アイテム管理、衝突検出、イベント処理など、QGraphicsViewが提供する便利な機能はすべて自分で実装する必要があります。
  • 利点

    • 最高のパフォーマンス
      GPUの機能を最大限に活用し、非常に複雑な2D/3Dグラフィックを高フレームレートでレンダリングできます。
    • 完全な制御
      シェーダー、テクスチャ、バッファオブジェクトなど、グラフィックパイプラインのあらゆる側面を細かく制御できます。
    • 3D描画
      2Dだけでなく、3Dグラフィックスにも対応できます。

簡単なコード例 (QOpenGLWidgetのpaintGLをオーバーライド)

#include <QApplication>
#include <QOpenGLWidget>
#include <QOpenGLFunctions> // OpenGL関数を使用するためのヘルパークラス

class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
public:
    MyGLWidget(QWidget *parent = nullptr) : QOpenGLWidget(parent) {
        setMinimumSize(400, 300);
    }

protected:
    void initializeGL() override {
        initializeOpenGLFunctions(); // OpenGL関数の初期化
        glClearColor(0.2f, 0.2f, 0.2f, 1.0f); // 背景色を設定
    }

    void resizeGL(int w, int h) override {
        glViewport(0, 0, w, h); // ビューポートを設定
        // ここでプロジェクション行列などを設定することも可能
    }

    void paintGL() override {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // バッファをクリア

        // 例: 単純な四角形をOpenGLで描画
        glBegin(GL_QUADS);
            glColor3f(1.0f, 0.0f, 0.0f); // 赤色
            glVertex2f(-0.5f, -0.5f);
            glVertex2f( 0.5f, -0.5f);
            glVertex2f( 0.5f,  0.5f);
            glVertex2f(-0.5f,  0.5f);
        glEnd();
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MyGLWidget glWidget;
    glWidget.setWindowTitle("QOpenGLWidgetの例");
    glWidget.show();
    return a.exec();
}

Qt Charts / Qt Data Visualization

Qt Chartsは2Dグラフ、Qt Data Visualizationは3Dグラフに特化したモジュールです。データ可視化が主な目的であれば、これらの専用モジュールが最も効率的です。

  • 適しているケース
    • 科学技術計算、ビジネスアプリケーションなどで、データの可視化が主要な機能である場合。
  • 欠点
    • 汎用性の欠如
      グラフ以外の一般的な2Dグラフィック描画には向きません。
  • 利点
    • グラフ描画に特化
      棒グラフ、折れ線グラフ、円グラフ、散布図などの描画機能がすぐに使えます。
    • インタラクティブ性
      ズーム、パン、選択など、グラフに特化したインタラクション機能が組み込まれています。
    • データバインディング
      データモデルから簡単にグラフを生成できます。
代替方法利点欠点適しているケース
QWidget + paintEventシンプル、完全な制御アイテム管理なし、複雑なインタラクションが困難シンプルな描画、少ないアイテム、完全なピクセル制御
Qt Quick / QMLハードウェアアクセラレーション、宣言的UI、アニメーション学習コスト、複雑な幾何学的描画に慣れが必要モダンUI、高パフォーマンスアニメーション、モバイル
QOpenGLWidget最高のパフォーマンス、完全なGPU制御、3D学習曲線が急、低レベルなコーディングが必要高パフォーマンス、複雑なグラフィック、3D統合
Qt Charts/Data Vizグラフに特化、豊富な機能汎用性なしデータ可視化、統計分析