QGraphicsScene::focusItemChanged()で実現するQtグラフィックス操作の応用

2025-04-26

  • focusItemChanged() シグナルは、フォーカスを持つアイテムが変更されたときに、その変更を通知します。
  • フォーカスを持つアイテムとは、キーボードイベントやマウスイベントを受け取るアイテムのことです。
  • QGraphicsItem は、シーン内に描画される要素(矩形、円、画像など)を表します。
  • QGraphicsScene は、QGraphicsItem オブジェクトを管理するコンテナです。

詳細

focusItemChanged() シグナルは、次の引数を受け取ります。

  • QGraphicsItem *oldFocusItem: 以前にフォーカスを持っていたアイテムへのポインタ。
  • QGraphicsItem *newFocusItem: 新しくフォーカスを持つアイテムへのポインタ。

このシグナルは、以下のような状況で発生します。

  • フォーカスを持っていたアイテムが削除された場合。
  • プログラム内で QGraphicsItem::setFocus() メソッドを呼び出して、特定のアイテムにフォーカスを設定した場合。
  • ユーザーがマウスでアイテムをクリックし、そのアイテムにフォーカスを与えた場合。
  • ユーザーがキーボードの Tab キーや Shift+Tab キーを使用して、フォーカスをアイテム間で移動した場合。

使用例

focusItemChanged() シグナルをスロットに接続することで、フォーカスが変更されたときに特定の処理を実行できます。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        addItem(new QGraphicsRectItem(0, 0, 100, 100));
        addItem(new QGraphicsRectItem(150, 0, 100, 100));
        addItem(new QGraphicsRectItem(0, 150, 100, 100));
        addItem(new QGraphicsRectItem(150, 150, 100, 100));

        connect(this, &QGraphicsScene::focusItemChanged, this, &MyScene::onFocusItemChanged);

        items()[0]->setFlag(QGraphicsItem::ItemIsFocusable);
        items()[1]->setFlag(QGraphicsItem::ItemIsFocusable);
        items()[2]->setFlag(QGraphicsItem::ItemIsFocusable);
        items()[3]->setFlag(QGraphicsItem::ItemIsFocusable);

    }

private slots:
    void onFocusItemChanged(QGraphicsItem *newFocusItem, QGraphicsItem *oldFocusItem) {
        qDebug() << "Focus changed:";
        if (oldFocusItem) {
            qDebug() << "Old focus item:" << oldFocusItem;
        } else {
            qDebug() << "Old focus item: nullptr";
        }
        if (newFocusItem) {
            qDebug() << "New focus item:" << newFocusItem;
        } else {
            qDebug() << "New focus item: nullptr";
        }
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

この例では、MyScene クラスで focusItemChanged() シグナルを onFocusItemChanged() スロットに接続し、フォーカスが変更されたときにデバッグ出力を表示しています。  



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

    • 原因
      • QGraphicsItemItemIsFocusable フラグが設定されていない。このフラグが設定されていないと、アイテムはフォーカスを受け取ることができません。
      • QGraphicsView がフォーカスを受け取っていない。ビューがフォーカスを受け取っていないと、シーン内のアイテムにフォーカスを移すことができません。
      • シグナルの接続が正しく行われていない。connect() 関数が適切に呼び出されていないか、スロットの定義が間違っている可能性があります。
    • トラブルシューティング
      • QGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable) を呼び出して、フォーカスを受け取るべきアイテムにフラグを設定します。
      • QGraphicsView::setFocus() を呼び出して、ビューにフォーカスを与えます。
      • connect() 関数の引数を再確認し、シグナルとスロットが正しく接続されていることを確認します。
      • デバッガを使用して、シグナルが発行されているかどうかを確認します。
  1. newFocusItem または oldFocusItem が nullptr になる

    • 原因
      • シーン内の最初のアイテムがフォーカスを受け取った場合、oldFocusItemnullptr になります。
      • シーン内の最後のアイテムからフォーカスが失われた場合、newFocusItemnullptr になります。
      • フォーカスを持っていたアイテムが削除された場合、oldFocusItem が削除されたアイテムを指している場合や、newFocusItemがnullptrになります。
    • トラブルシューティング
      • nullptr チェックを実装し、安全にポインタを使用します。
      • アイテムの削除処理を慎重に行い、削除後にフォーカスに関連する処理が適切に実行されるようにします。
      • デバッガを使用して、newFocusItemoldFocusItem の値を確認し、問題の原因を特定します。
  2. フォーカスが期待通りに移動しない

    • 原因
      • アイテムのフォーカス順序が適切に設定されていない。デフォルトでは、アイテムがシーンに追加された順序でフォーカスが移動します。
      • カスタムのフォーカス移動ロジックが実装されている場合、そのロジックにバグがある可能性があります。
    • トラブルシューティング
      • QGraphicsItem::setFocusPolicy() を使用して、アイテムのフォーカスポリシーを明示的に設定します。
      • カスタムのフォーカス移動ロジックを慎重にデバッグし、問題のある箇所を特定します。
      • QGraphicsScene::focusItem() を使用して、プログラム内で明示的にフォーカスを設定します。
  3. スロット内でクラッシュまたは予期しない動作が発生する

    • 原因
      • スロット内で不正なポインタ操作、またはメモリリークが発生している。
      • スロット内で実行時間が長く、UI スレッドをブロックしている。
      • スロット内で例外がスローされ、適切に処理されていない。
    • トラブルシューティング
      • デバッガを使用して、クラッシュまたは予期しない動作が発生する箇所を特定します。
      • メモリリークチェッカーを使用して、メモリリークを検出します。
      • 時間のかかる処理は、別のスレッドで実行します。
      • 例外処理を実装し、例外が発生した場合にプログラムがクラッシュしないようにします。
  4. QGraphicsViewのフォーカスに関する問題

    • 原因
      • QGraphicsViewがフォーカスを受け取っていない。
      • QGraphicsViewのフォーカスポリシーが正しく設定されていない。
    • トラブルシューティング
      • QGraphicsView::setFocus()を呼び出して、ビューにフォーカスを与えます。
      • QGraphicsView::setFocusPolicy()を使用してフォーカスに関するポリシーを設定します。

デバッグのヒント

  • デバッガを使用して、プログラムの実行をステップ実行し、変数の値や呼び出し履歴を確認します。
  • qDebug() を使用して、シグナルの引数や変数の値を表示します。


例1: フォーカスが変更されたときにアイテムの枠線を強調表示する

この例では、フォーカスが変更されたときに、フォーカスを持つアイテムの枠線を赤く強調表示します。

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

class FocusScene : public QGraphicsScene {
public:
    FocusScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        rect1 = new QGraphicsRectItem(0, 0, 100, 100);
        rect2 = new QGraphicsRectItem(150, 0, 100, 100);

        addItem(rect1);
        addItem(rect2);

        rect1->setFlag(QGraphicsItem::ItemIsFocusable);
        rect2->setFlag(QGraphicsItem::ItemIsFocusable);

        connect(this, &QGraphicsScene::focusItemChanged, this, &FocusScene::onFocusItemChanged);
    }

private slots:
    void onFocusItemChanged(QGraphicsItem *newFocusItem, QGraphicsItem *oldFocusItem) {
        if (oldFocusItem) {
            oldFocusItem->setPen(QPen(Qt::black)); // 古いアイテムの枠線を黒に戻す
        }
        if (newFocusItem) {
            newFocusItem->setPen(QPen(Qt::red, 3)); // 新しいアイテムの枠線を赤く強調
        }
    }

private:
    QGraphicsRectItem *rect1;
    QGraphicsRectItem *rect2;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    FocusScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

説明

  1. FocusScene クラスは QGraphicsScene を継承しています。
  2. QGraphicsRectItemrect1rect2 を作成し、シーンに追加します。
  3. rect1rect2ItemIsFocusable フラグを設定し、フォーカスを受け取れるようにします。
  4. focusItemChanged() シグナルを onFocusItemChanged() スロットに接続します。
  5. onFocusItemChanged() スロットでは、古いフォーカスアイテムの枠線を黒に戻し、新しいフォーカスアイテムの枠線を赤く強調表示します。

例2: フォーカスが変更されたときにアイテムの名前をデバッグ出力する

この例では、フォーカスが変更されたときに、フォーカスを持つアイテムの名前をデバッグ出力します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include <QGraphicsView>
#include <QDebug>

class FocusScene : public QGraphicsScene {
public:
    FocusScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        text1 = new QGraphicsTextItem("Item 1");
        text2 = new QGraphicsTextItem("Item 2");

        addItem(text1);
        addItem(text2);

        text1->setFlag(QGraphicsItem::ItemIsFocusable);
        text2->setFlag(QGraphicsItem::ItemIsFocusable);

        connect(this, &QGraphicsScene::focusItemChanged, this, &FocusScene::onFocusItemChanged);
    }

private slots:
    void onFocusItemChanged(QGraphicsItem *newFocusItem, QGraphicsItem *oldFocusItem) {
        if (newFocusItem) {
            qDebug() << "Focused item:" << newFocusItem->toPlainText();
        } else {
            qDebug() << "No item focused";
        }
    }

private:
    QGraphicsTextItem *text1;
    QGraphicsTextItem *text2;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    FocusScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

説明

  1. FocusScene クラスは QGraphicsScene を継承しています。
  2. QGraphicsTextItemtext1text2 を作成し、シーンに追加します。
  3. text1text2ItemIsFocusable フラグを設定し、フォーカスを受け取れるようにします。
  4. focusItemChanged() シグナルを onFocusItemChanged() スロットに接続します。
  5. onFocusItemChanged() スロットでは、新しいフォーカスアイテムの名前をデバッグ出力します。

例3: フォーカスが変更された時に、アイテムのZ値を変更する

この例ではフォーカスされたアイテムを一番上に表示させます。

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

class FocusScene : public QGraphicsScene {
public:
    FocusScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        rect1 = new QGraphicsRectItem(0, 0, 100, 100);
        rect2 = new QGraphicsRectItem(50, 50, 100, 100);

        addItem(rect1);
        addItem(rect2);

        rect1->setFlag(QGraphicsItem::ItemIsFocusable);
        rect2->setFlag(QGraphicsItem::ItemIsFocusable);

        connect(this, &QGraphicsScene::focusItemChanged, this, &FocusScene::onFocusItemChanged);
    }

private slots:
    void onFocusItemChanged(QGraphicsItem *newFocusItem, QGraphicsItem *oldFocusItem) {
        if (oldFocusItem) {
            oldFocusItem->setZValue(0);
        }
        if (newFocusItem) {
            newFocusItem->setZValue(1);
        }
    }

private:
    QGraphicsRectItem *rect1;
    QGraphicsRectItem *rect2;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    FocusScene scene;
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}


QGraphicsItem::focusInEvent() と QGraphicsItem::focusOutEvent() を使用する

  • この方法は、アイテムごとに異なる処理が必要な場合に特に有効です。
  • これらの関数をオーバーライドすることで、各アイテムごとにフォーカス状態の変化を処理できます。
  • QGraphicsItem クラスには、アイテムがフォーカスを受け取ったときと失ったときに呼び出される focusInEvent()focusOutEvent() という仮想関数があります。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QDebug>
#include <QFocusEvent>

class MyRectItem : public QGraphicsRectItem {
public:
    MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(x, y, width, height, parent) {
        setFlag(ItemIsFocusable);
    }

protected:
    void focusInEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusInEvent(event);
        qDebug() << "Item focused:" << this;
        // フォーカスを受け取ったときの処理
        setPen(QPen(Qt::red, 3));
    }

    void focusOutEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusOutEvent(event);
        qDebug() << "Item unfocused:" << this;
        // フォーカスを失ったときの処理
        setPen(QPen(Qt::black));
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QGraphicsScene scene;
    MyRectItem *rect1 = new MyRectItem(0, 0, 100, 100);
    MyRectItem *rect2 = new MyRectItem(150, 0, 100, 100);
    scene.addItem(rect1);
    scene.addItem(rect2);
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

QGraphicsScene::focusItem() を使用して現在のフォーカスアイテムを定期的に確認する

  • この方法は、シグナル/スロット機構を使用せずに、ポーリングベースでフォーカス状態を監視する場合に有効です。
  • タイマーなどを使用して、定期的にこのメソッドを呼び出し、フォーカスアイテムが変更されたかどうかを確認します。
  • QGraphicsScene::focusItem() メソッドを使用すると、現在のフォーカスアイテムを取得できます。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QDebug>
#include <QTimer>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = new QGraphicsRectItem(0, 0, 100, 100);
    QGraphicsRectItem *rect2 = new QGraphicsRectItem(150, 0, 100, 100);
    rect1->setFlag(QGraphicsItem::ItemIsFocusable);
    rect2->setFlag(QGraphicsItem::ItemIsFocusable);
    scene.addItem(rect1);
    scene.addItem(rect2);
    QGraphicsView view(&scene);
    view.show();

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&scene]() {
        static QGraphicsItem *previousFocusItem = nullptr;
        QGraphicsItem *currentFocusItem = scene.focusItem();
        if (currentFocusItem != previousFocusItem) {
            if (previousFocusItem) {
                qDebug() << "Focus changed from:" << previousFocusItem;
            }
            if (currentFocusItem) {
                qDebug() << "Focus changed to:" << currentFocusItem;
            }
            previousFocusItem = currentFocusItem;
        }
    });
    timer.start(100); // 100msごとに確認

    return app.exec();
}
  • この方法は、イベントを細かく制御する必要がある場合に有効です。
  • QObject::installEventFilter() を使用して、QGraphicsScene または QGraphicsView にイベントフィルターをインストールし、QEvent::FocusInQEvent::FocusOut イベントを監視します。