QGraphicsScene::changed()で陥りやすいエラーと対策:Qt開発のコツ

2025-04-26

  • 目的
    シーンの変更を検出し、必要な処理(例えば、再描画、データの更新など)を行うために使用されます。
  • 引数
    QList<QRectF> 型の引数を持ちます。このリストは、変更された領域の矩形(長方形)のリストです。
  • 発行タイミング
    シーン内のアイテムが変更されたときに発行されます。
  • シグナル
    QGraphicsScene クラスに定義されたシグナルです。

具体的な使用例

    • QGraphicsScene::changed() シグナルをスロットに接続することで、シーンの変更を監視できます。
    • 例えば、シーンが変更されたときに特定の関数を呼び出すことができます。
    QGraphicsScene *scene = new QGraphicsScene(this);
    connect(scene, &QGraphicsScene::changed, this, &MyWidget::sceneChanged);
    
  1. 変更された領域の再描画

    • QList<QRectF> 引数を使用して、変更された領域のみを再描画できます。これにより、パフォーマンスが向上します。
    void MyWidget::sceneChanged(const QList<QRectF> &rects) {
        for (const QRectF &rect : rects) {
            // rect に基づいて再描画処理を行う
            // 例えば、QGraphicsView の viewport を更新する
        }
    }
    
  2. データの更新

    • シーンの変更に応じて、関連するデータを更新できます。
    • 例えば、シーン内のアイテムの位置が変更されたときに、対応するデータ構造を更新できます。

重要なポイント

  • QGraphicsScene::items() の様なシーン内のアイテムのリストを返す関数を使用するときは、changed()シグナルの発行の有無に関わらず、リストの更新は、必要に応じて行わなければなりません。
  • QGraphicsScene::update() 関数を呼び出した場合、changed() シグナルが発行されるとは限りません。update() は、再描画をスケジュールするだけで、実際の変更がコミットされるまでシグナルは発行されません。
  • QGraphicsScene::changed() シグナルは、シーン内のアイテムの可視状態の変更(例えば、setVisible(false))によっても発行されます。

QGraphicsScene::changed() シグナルは、QGraphicsScene 内のアイテムが変更されたときに通知されるシグナルです。このシグナルを使用することで、シーンの変更を監視し、必要な処理(再描画、データ更新など)を行うことができます。引数として渡される QList<QRectF> は、変更された領域の矩形リストであり、効率的な再描画に役立ちます。



一般的なエラーとトラブルシューティング

    • 原因
      • アイテムの変更がシーンによって認識されていない。例えば、アイテムのプロパティを直接変更した場合、シーンは変更を認識しないことがあります。
      • QGraphicsScene::update() の呼び出しが、実際の変更をコミットする前に終了している。update() は描画の再スケジュールのみを行うため、変更が反映されるまで時間がかかる場合があります。
      • アイテムの flags() が適切に設定されていない。ItemIsMovableItemIsSelectable などのフラグが設定されていないと、シーンが変更を検出できないことがあります。
    • トラブルシューティング
      • QGraphicsItem::setPos()QGraphicsItem::setScale()QGraphicsItem::setRotation() など、シーンが認識できるアイテムの変更メソッドを使用します。
      • QGraphicsScene::update() を呼び出した後、QApplication::processEvents() を呼び出して、変更を強制的に処理します。ただし、processEvents() は処理をブロックするため、多用は避けるべきです。
      • アイテムのflags()を適切な値に設定します。
      • デバッガを使用して、シグナルが発行されるタイミングと、アイテムの変更がシーンにどのように反映されているかを調査します。
  1. QList<QRectF> 引数の解釈ミス

    • 原因
      • 矩形リストが、シーン全体の変更領域を表していると誤解している。実際には、変更されたアイテムの境界矩形のリストです。
      • 矩形リストの座標がシーン座標系であることを理解していない。ビュー座標系とシーン座標系を混同している。
    • トラブルシューティング
      • 矩形リストは、変更されたアイテムの境界矩形を表していることを理解します。
      • ビュー座標系とシーン座標系を区別し、必要に応じて座標変換を行います。QGraphicsView::mapToScene()QGraphicsView::mapFromScene() を使用します。
      • デバッガを使用し、リスト内の矩形が期待通りの座標とサイズを持っているか確認する。
  2. パフォーマンスの問題

    • 原因
      • changed() シグナルに接続されたスロットが、過度に複雑な処理を行っている。
      • 変更領域全体を再描画しているため、不要な再描画が発生している。
    • トラブルシューティング
      • スロット内の処理を最適化し、必要な処理のみを実行します。
      • QList<QRectF> 引数を使用して、変更された領域のみを再描画します。
      • 必要に応じて、描画の最適化(ダブルバッファリング、キャッシュなど)を行います。
  3. メモリリーク

    • 原因
      • スロット内で動的にメモリを割り当て、解放を忘れている。
      • シーン内のアイテムを削除する際に、関連するリソースを適切に解放していない。
    • トラブルシューティング
      • メモリリーク検出ツール(Valgrind など)を使用して、メモリリークを特定します。
      • スマートポインタ(QSharedPointer など)を使用して、メモリ管理を自動化します。
      • アイテムを削除する際に、関連するリソースを明示的に解放します。

デバッグのヒント

  • シンプルなテストケースを作成し、問題を再現できる最小限のコードでデバッグを行います。
  • デバッガを使用して、ステップ実行を行い、変数の値や処理の流れを追跡します。
  • qDebug() を使用して、シグナルの発行タイミング、引数の値、スロット内の処理などをログ出力します。


例1:シーンの変更を監視し、変更された領域をコンソールに出力する

この例では、QGraphicsScene::changed() シグナルをスロットに接続し、変更された領域の矩形リストをコンソールに出力します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug>

class MyWidget : public QObject {
public:
    MyWidget() {
        scene = new QGraphicsScene();
        connect(scene, &QGraphicsScene::changed, this, &MyWidget::sceneChanged);

        // シーンに矩形アイテムを追加
        QGraphicsRectItem *rect1 = scene->addRect(0, 0, 50, 50);
        QGraphicsRectItem *rect2 = scene->addRect(100, 100, 80, 80);

        // アイテムを移動して、シーンを変更
        rect1->setPos(20, 20);
        rect2->setPos(120, 120);
    }

    void sceneChanged(const QList<QRectF> &rects) {
        qDebug() << "シーンが変更されました。変更された領域:";
        for (const QRectF &rect : rects) {
            qDebug() << rect;
        }
    }

private:
    QGraphicsScene *scene;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyWidget widget;
    return app.exec();
}

説明

  1. MyWidget クラスを作成し、QGraphicsScene のインスタンスを作成します。
  2. connect() を使用して、QGraphicsScene::changed() シグナルを sceneChanged() スロットに接続します。
  3. シーンに QGraphicsRectItem を追加します。
  4. アイテムの setPos() を呼び出して、シーンを変更します。
  5. sceneChanged() スロットでは、引数として渡された QList<QRectF> をループ処理し、各矩形を qDebug() でコンソールに出力します。

例2:変更された領域のみを再描画するカスタムビュー

この例では、QGraphicsView を継承したカスタムビューを作成し、QGraphicsScene::changed() シグナルを使用して変更された領域のみを再描画します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QPainter>

class MyView : public QGraphicsView {
public:
    MyView(QGraphicsScene *scene) : QGraphicsView(scene) {
        connect(scene, &QGraphicsScene::changed, this, &MyView::sceneChanged);
    }

    void sceneChanged(const QList<QRectF> &rects) {
        for (const QRectF &rect : rects) {
            update(mapFromScene(rect).boundingRect()); // ビュー座標系に変換して再描画
        }
    }

protected:
    void drawBackground(QPainter *painter, const QRectF &rect) override {
        QGraphicsView::drawBackground(painter, rect);
        // 背景の描画
    }

    void drawForeground(QPainter *painter, const QRectF &rect) override {
        QGraphicsView::drawForeground(painter, rect);
        // 前景の描画
    }
};

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect = scene.addRect(0, 0, 100, 100);
    MyView view(&scene);
    view.show();

    rect->setPos(50, 50); // シーンを変更

    return app.exec();
}

説明

  1. MyView クラスは QGraphicsView を継承します。
  2. sceneChanged() スロットでは、引数として渡された QList<QRectF> をループ処理し、mapFromScene() を使用してシーン座標系からビュー座標系に変換します。
  3. update() を呼び出して、変換された矩形のみを再描画します。
  4. drawBackground()drawForeground() をオーバーライドして、背景と前景の描画をカスタマイズできます。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QTimer>
#include <QDebug>

class MyWidget : public QObject {
public:
    MyWidget() {
        scene = new QGraphicsScene();
        connect(scene, &QGraphicsScene::changed, this, &MyWidget::sceneChanged);

        QGraphicsRectItem *rect = scene->addRect(0, 0, 50, 50);

        timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, &MyWidget::processChanges);
        timer->start(100); // 100ミリ秒ごとに処理

        rect->setPos(20, 20);
        rect->setPos(40, 40); // 短時間内に複数回変更
    }

    void sceneChanged(const QList<QRectF> &rects) {
        changeRects.append(rects);
    }

    void processChanges() {
        if (changeRects.isEmpty()) return;

        qDebug() << "まとめて処理された変更:";
        for (const QRectF &rects : changeRects) {
            for(const QRectF &rect : rects) {
                qDebug() << rect;
            }
        }
        changeRects.clear();
    }

private:
    QGraphicsScene *scene;
    QTimer *timer;
    QList<QList<QRectF>> changeRects;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyWidget widget;
    return app.exec();
}
  1. タイマーを使用して、processChanges() スロットを定期的に呼び出します。
  2. sceneChanged() スロットでは、変更された矩形リストを changeRects リストに追加します。


  1. アイテムの変更を直接監視する

    • QGraphicsItem クラスは、アイテムの変更を監視するためのシグナルを提供します。
    • QGraphicsItem::xChanged()QGraphicsItem::yChanged()QGraphicsItem::posChanged()QGraphicsItem::rotationChanged()QGraphicsItem::scaleChanged() などのシグナルを使用できます。
    • 特定のアイテムの変更のみを監視する必要がある場合に便利です。
    • 例:
    #include <QApplication>
    #include <QGraphicsScene>
    #include <QGraphicsRectItem>
    #include <QDebug>
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
    
        QGraphicsScene scene;
        QGraphicsRectItem *rect = scene.addRect(0, 0, 50, 50);
    
        QObject::connect(rect, &QGraphicsItem::posChanged, [&]() {
            qDebug() << "矩形の位置が変更されました。新しい位置:" << rect->pos();
        });
    
        rect->setPos(20, 20); // 位置を変更
    
        return app.exec();
    }
    
  2. カスタムアイテムの変更通知

    • QGraphicsItem を継承したカスタムアイテムクラスを作成し、独自の変更通知メカニズムを実装できます。
    • アイテムの内部状態が変更されたときに、カスタムシグナルを発行できます。
    • 複雑なアイテムの変更を監視する必要がある場合に便利です。
    • 例:
    #include <QApplication>
    #include <QGraphicsScene>
    #include <QGraphicsItem>
    #include <QDebug>
    
    class MyItem : public QGraphicsItem {
        Q_OBJECT
    public:
        MyItem() : QGraphicsItem() {}
        QRectF boundingRect() const override { return QRectF(0, 0, 50, 50); }
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
            painter->drawRect(boundingRect());
        }
        void setCustomValue(int value) {
            if (customValue != value) {
                customValue = value;
                emit customValueChanged(value);
            }
        }
    signals:
        void customValueChanged(int value);
    private:
        int customValue = 0;
    };
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
    
        QGraphicsScene scene;
        MyItem *myItem = new MyItem();
        scene.addItem(myItem);
    
        QObject::connect(myItem, &MyItem::customValueChanged, [&](int value) {
            qDebug() << "カスタム値が変更されました。新しい値:" << value;
        });
    
        myItem->setCustomValue(10);
    
        return app.exec();
    }
    #include "main.moc"
    
  3. タイマーを使用したポーリング

    • タイマーを使用して、定期的にシーン内のアイテムの状態をチェックできます。
    • アイテムの変更を検出するために、以前の状態と比較します。
    • QGraphicsScene::changed() シグナルが信頼できない場合や、特定の条件下でのみ変更を検出する必要がある場合に役立ちます。
    • パフォーマンスに影響を与える可能性があるため、ポーリング間隔を適切に設定する必要があります。
    • 例:
    #include <QApplication>
    #include <QGraphicsScene>
    #include <QGraphicsRectItem>
    #include <QTimer>
    #include <QDebug>
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
    
        QGraphicsScene scene;
        QGraphicsRectItem *rect = scene.addRect(0, 0, 50, 50);
        QPointF previousPos = rect->pos();
    
        QTimer timer;
        QObject::connect(&timer, &QTimer::timeout, [&]() {
            if (rect->pos() != previousPos) {
                qDebug() << "矩形の位置が変更されました。新しい位置:" << rect->pos();
                previousPos = rect->pos();
            }
        });
        timer.start(100); // 100ミリ秒ごとにチェック
    
        rect->setPos(20, 20);
    
        return app.exec();
    }
    
  4. QGraphicsScene::items() と QGraphicsItem::data() を使用した監視

    • QGraphicsScene::items() を使用して、シーン内のアイテムのリストを取得し、QGraphicsItem::data() を使用して、アイテムに関連付けられたカスタムデータを監視します。
    • アイテムのカスタムデータが変更されたときに、必要な処理を実行できます。
    • 複雑なデータ構造の変更を監視する必要がある場合に役立ちます。