Qtプログラミング徹底解説:QGraphicsScene::itemAt() の使い方とトラブルシューティング

2025-05-27

基本的な機能

  • 指定された座標にアイテムが存在しない場合は、nullptr (C++11以降) または 0 (それ以前) を返します。
  • 複数のアイテムが同じ座標に存在する場合、最も上にある(Z値が高い)アイテムが返されます。
  • QGraphicsScene 上の特定の点(座標)に位置する QGraphicsItem を返します。

関数の形式

QGraphicsItem *QGraphicsScene::itemAt(const QPointF &pos, const QTransform &deviceTransform) const;

または

QGraphicsItem *QGraphicsScene::itemAt(qreal x, qreal y, const QTransform &deviceTransform) const;

引数の説明

  • deviceTransform: これはオプションの引数で、デバイス座標からシーン座標への変換を指定します。通常、QGraphicsView から取得した変換行列を使用します。これにより、ビューの変換(ズーム、回転など)を考慮した座標変換が行われます。通常、ビューから座標を取得して、ビューの変換を考慮するために使用されます。
  • pos (または x, y): 検索する座標を QPointF オブジェクトまたは qreal 型の x 座標と y 座標で指定します。これは QGraphicsScene の座標系における座標です。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);

    QGraphicsView view(&scene);
    view.show();

    // シーン座標(75, 75)にあるアイテムを取得
    QGraphicsItem *item = scene.itemAt(75, 75, view.transform());

    if (item) {
        qDebug() << "Item found:" << item; //アイテムが見つかった場合、アイテムへのポインタが表示されます。
    } else {
        qDebug() << "No item found."; //アイテムが見つからなかった場合、No item foundと表示されます。
    }

    //ビューの座標からシーン座標へ変換してアイテムを取得する例
    QPoint viewPoint(100,100);
    QPointF scenePoint = view.mapToScene(viewPoint);
    QGraphicsItem * item2 = scene.itemAt(scenePoint, view.transform());
    if (item2){
        qDebug() << "Item found from viewPoint:" << item2;
    }

    return app.exec();
}


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

    • 原因
      • 指定された座標にアイテムが存在しない。
      • 座標が QGraphicsScene の範囲外である。
      • deviceTransform が正しく設定されていない(ビューの変換が考慮されていない)。
    • トラブルシューティング
      • 座標が正しいか、アイテムが実際にその位置に存在するかを確認します。
      • QGraphicsScene の境界を確認し、座標が範囲内にあることを確認します。
      • QGraphicsView から取得した transform()deviceTransform 引数に渡していることを確認します。特にビューをズーム、回転、スクロールしている場合は必須です。
      • ビューの座標から取得する場合は、QGraphicsView::mapToScene() を使ってシーン座標に変換してからitemAt()を呼び出してください。
  1. 期待と異なるアイテムが返される

    • 原因
      • 複数のアイテムが同じ座標に重なっている。
      • アイテムの境界(bounding rect)が予期せず重なっている。
      • Z値が意図したものではない。
    • トラブルシューティング
      • 重なっているアイテムの Z 値(QGraphicsItem::zValue())を確認し、期待どおりの順序になっているか確認します。
      • アイテムの境界を確認し、意図しない重なりがないか確認します。
      • QGraphicsScene::items() を使用して、その座標にあるすべてのアイテムを取得し、デバッグします。
      • QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform)などのオーバーロード関数を使用して、範囲内のすべてのアイテムを取得し、フィルター処理を行うことも有効です。
  2. 座標変換の問題

    • 原因
      • deviceTransform が正しく設定されていない。
      • ビューの変換(ズーム、回転、スクロール)が正しく考慮されていない。
      • ビュー座標とシーン座標の混同。
    • トラブルシューティング
      • QGraphicsView::transform() を使用して、ビューの変換行列を取得し、itemAt() に渡します。
      • QGraphicsView::mapToScene()QGraphicsView::mapFromScene() を使用して、ビュー座標とシーン座標を正しく変換します。
      • デバッグ時に、座標変換の結果をログに出力して確認します。
  3. パフォーマンスの問題

    • 原因
      • 非常に多くのアイテムが存在するシーンで itemAt() を頻繁に呼び出す。
      • 複雑なアイテムの境界計算。
    • トラブルシューティング
      • itemAt() の呼び出し回数を減らすために、イベントフィルタリングやキャッシュなどの最適化手法を検討します。
      • アイテムの境界を単純化します。
      • QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform)のように範囲を指定する関数を使う。
      • 必要であれば、独自の空間インデックス構造を実装して検索を高速化します。

デバッグのヒント

  • ビュー座標からシーン座標への変換、シーン座標からビュー座標への変換を可視化するデバッグコードを書いて、座標変換が意図した通りに行われているか確認する。
  • グラフィカルデバッガを使用して、アイテムの境界や座標を視覚的に確認します。
  • QGraphicsScene::items() を使用して、特定の領域にあるすべてのアイテムを取得し、デバッグします。
  • qDebug() を使用して、座標、アイテムの境界、Z 値、変換行列などの情報をログに出力します。


例1: マウスクリックでアイテムを取得し、情報を表示する

この例では、QGraphicsView 上でマウスクリックされた位置にある QGraphicsItem を取得し、そのアイテムの型と座標をコンソールに表示します。

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

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

protected:
    void mousePressEvent(QMouseEvent *event) override {
        QPointF scenePos = mapToScene(event->pos()); //ビュー座標をシーン座標に変換
        QGraphicsItem *item = scene()->itemAt(scenePos, transform());//シーン座標を元にアイテムを取得

        if (item) {
            qDebug() << "Clicked item type:" << item->type() << ", position:" << item->pos();
        } else {
            qDebug() << "No item clicked.";
        }
        QGraphicsView::mousePressEvent(event); //親クラスの処理も呼ぶ
    }
};

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);

    MyView view(&scene);
    view.show();

    return app.exec();
}

例2: マウスカーソル下のアイテムをハイライトする

この例では、マウスカーソルの下にある QGraphicsItem をハイライト表示します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
#include <QMouseEvent>
#include <QPen>

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

protected:
    void mouseMoveEvent(QMouseEvent *event) override {
        QPointF scenePos = mapToScene(event->pos());
        QGraphicsItem *item = scene()->itemAt(scenePos, transform());

        if (item != highlightedItem) {
            if (highlightedItem) {
                highlightedItem->setPen(QPen(Qt::black)); //前のハイライトを解除
            }
            if (item) {
                item->setPen(QPen(Qt::red)); //新しいアイテムをハイライト
            }
            highlightedItem = item;
            viewport()->update(); //再描画
        }
        QGraphicsView::mouseMoveEvent(event);
    }
private:
    QGraphicsItem * highlightedItem;
};

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);

    MyView view(&scene);
    view.show();

    return app.exec();
}

例3: 特定の種類のアイテムだけを取得する

この例では、マウスクリックされた位置にある QGraphicsRectItem だけを取得し、情報を表示します。

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

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

protected:
    void mousePressEvent(QMouseEvent *event) override {
        QPointF scenePos = mapToScene(event->pos());
        QGraphicsItem *item = scene()->itemAt(scenePos, transform());

        if (item && item->type() == QGraphicsRectItem::Type) {
            QGraphicsRectItem *rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
            qDebug() << "Clicked rect item position:" << rectItem->pos();
        } else {
            qDebug() << "No rect item clicked.";
        }
        QGraphicsView::mousePressEvent(event);
    }
};

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);
    scene.addEllipse(120,120,50,50); //rectItem以外も追加

    MyView view(&scene);
    view.show();

    return app.exec();
}


QGraphicsScene::items() を使用した範囲検索

QGraphicsScene::items() は、特定の範囲内にあるすべてのアイテムをリストとして返します。

  • 使用例
  • 利点
    • 特定の範囲内にある複数のアイテムを一度に取得できる。
    • 範囲を指定することで、itemAt() よりも広範囲のアイテムを効率的に取得できる。
    • アイテムのリストを処理することで、より複雑なロジックを実装できる。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);

    QGraphicsView view(&scene);
    view.show();

    QRectF searchRect(25, 25, 100, 100); // 検索範囲
    QList<QGraphicsItem*> items = scene.items(searchRect, Qt::ItemSelectionMode::Intersects, Qt::SortOrder::ZOrder, view.transform());

    for (QGraphicsItem *item : items) {
        qDebug() << "Item in range:" << item;
    }

    return app.exec();
}

イベントフィルタリング

QGraphicsView にイベントフィルタをインストールすることで、マウスイベントをインターセプトし、itemAt() を使用せずにアイテムを特定できます。

  • 使用例
  • 利点
    • マウスイベントが発生するたびにアイテムを特定できるため、リアルタイムな処理が可能。
    • itemAt() の呼び出し回数を減らすことで、パフォーマンスを向上させることができる。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
#include <QEvent>

class ItemFilter : public QObject {
public:
    ItemFilter(QGraphicsView *view) : QObject(view), view(view) {}

protected:
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event->type() == QEvent::MouseMove) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            QPointF scenePos = view->mapToScene(mouseEvent->pos());
            QGraphicsItem *item = view->scene()->itemAt(scenePos, view->transform());

            if (item) {
                qDebug() << "Mouse over item:" << item;
            }
        }
        return QObject::eventFilter(obj, event);
    }

private:
    QGraphicsView *view;
};

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);

    QGraphicsView view(&scene);
    ItemFilter filter(&view);
    view.installEventFilter(&filter);
    view.show();

    return app.exec();
}

空間インデックス構造

大量のアイテムが存在する場合、itemAt()items() のパフォーマンスが低下する可能性があります。このような場合、空間インデックス構造(例: QuadTree、KD-Tree)を実装することで、検索を高速化できます。

  • 注意
    • 空間インデックス構造の実装は複雑になる可能性がある。
    • Qt には標準で空間インデックス構造が提供されていないため、自分で実装するか、サードパーティライブラリを使用する必要がある。
  • 利点
    • 大規模なシーンでの検索パフォーマンスを向上させることができる。
    • 複雑な検索クエリを効率的に処理できる。

アイテムのカスタムデータ

アイテムにカスタムデータを設定することで、アイテムの特定やフィルタリングを容易にできます。

  • 使用例
  • 利点
    • アイテムの属性に基づいて検索やフィルタリングを実行できる。
    • アイテムの特定に必要な情報を効率的に管理できる。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

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

    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(0, 0, 100, 100);
    rect1->setData(0, "Rect1"); //カスタムデータの設定
    QGraphicsRectItem *rect2 = scene.addRect(50, 50, 100, 100);
    rect2->setData(0, "Rect2");

    QGraphicsView view(&scene);
    view.show();

    QList<QGraphicsItem*> allItems = scene.items();
    for (QGraphicsItem *item : allItems) {
        if (item->data(0).toString() == "Rect1") {
            qDebug() << "Found Rect1:" << item;
        }
    }

    return app.exec();
}