Qt アプリ開発:グラフィックアイテムのフォーカス管理 (QGraphicsScene)

2025-04-26

QGraphicsScene::focusInEvent() とは

QGraphicsScene::focusInEvent() は、QGraphicsScene クラスの仮想関数の一つです。この関数は、シーン内のいずれかのアイテム(QGraphicsItem の派生クラス)がキーボードフォーカスを受け取った際に、そのシーン自身に対して呼び出されます。

役割と目的

このイベントハンドラの主な役割は、シーンがフォーカスを得たときに特別な処理を行いたい場合に再実装(オーバーライド)することです。例えば、以下のような処理が考えられます。

  • アプリケーション全体の状態に影響を与える処理を行う。
  • フォーカスを得たアイテムとは異なる何らかの視覚的なフィードバックを提供する(シーンの背景色を変えるなど、あまり一般的ではありませんが)。
  • シーン全体の状態を更新する(特定のアイテムがフォーカスされたことを記録するなど)。

イベントの流れ

  1. ユーザーがキーボード操作(Tabキーを押すなど)を行うか、プログラム的に QGraphicsItem::setFocus() が呼び出されるなどして、シーン内の特定の QGraphicsItem がキーボードフォーカスを受け取ります。
  2. そのフォーカスを受け取ったアイテムは、自身の focusInEvent() ハンドラを呼び出します。
  3. その後、そのアイテムが所属する QGraphicsScenefocusInEvent() ハンドラも呼び出されます。

つまり、アイテムがフォーカスを得るというイベントは、まずそのアイテム自身に通知され、次にそのアイテムを含むシーンに通知されるという流れになります。

再実装 (オーバーライド) の方法

QGraphicsScene::focusInEvent() を再実装するには、QGraphicsScene を継承した独自のクラスを作成し、そのクラス内でこの関数をオーバーライドします。

#include <QGraphicsScene>
#include <QFocusEvent>
#include <QDebug>

class MyGraphicsScene : public QGraphicsScene {
public:
    MyGraphicsScene(QObject *parent = nullptr) : QGraphicsScene(parent) {}

protected:
    void focusInEvent(QFocusEvent *event) override {
        QGraphicsScene::focusInEvent(event); // デフォルトの処理も実行することを推奨

        qDebug() << "シーンがフォーカスを受け取りました。理由:" << event->reason();
    }
};

上記の例では、MyGraphicsScene クラスで focusInEvent() をオーバーライドし、フォーカスを受け取った理由(event->reason())をデバッグ出力しています。

QFocusEvent オブジェクト

focusInEvent() 関数は、引数として QFocusEvent オブジェクトを受け取ります。このオブジェクトには、フォーカスイベントに関する情報が含まれています。主な情報としては以下のようなものがあります。

  • lostFocus(): フォーカスを失ったイベントかどうかを示します(false が返ります)。
  • gotFocus(): このイベントがフォーカスを得たイベントであることを示します(focusOutEvent() と対になるイベントなので、常に true が返ります)。
  • reason(): フォーカスがどのようにして与えられたかを示す Qt::FocusReason 列挙型の値を取得します(例えば、マウスのクリック、Tabキーの押下、プログラムによる設定など)。
  • シーン自体がフォーカスを受け取ることができるのは、通常、シーンにフォーカス可能なアイテムが含まれており、それらのアイテムがフォーカスを受け渡すことができる場合です。シーン自体に直接フォーカスを設定することは一般的ではありません。
  • focusInEvent() は、シーン内の いずれかの アイテムがフォーカスを得たときに呼び出されます。どのアイテムがフォーカスを得たのかを特定する必要がある場合は、通常、個々の QGraphicsItemfocusInEvent() ハンドラで処理を行います。


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

QGraphicsScene::focusInEvent() 自体は、主にイベントハンドラを再実装した際のロジックに関する問題が発生しやすいです。以下に、よくあるエラーとその対処法を挙げます。

focusInEvent() が期待通りに呼び出されない

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

    • シーン内の少なくとも一つの QGraphicsItem に対して setFlag(QGraphicsItem::ItemIsFocusable, true) が設定されているか確認してください。
    • 実際にそのアイテムをクリックしたり、Tabキーでフォーカスを移動させたりする操作を行ってみてください。
    • QGraphicsView がフォーカスを受け取れる設定になっているか (setFocusPolicy()) を確認してください。通常は Qt::StrongFocusQt::TabFocus が適切です。
    • 他のウィジェットがアクティブになっていないか確認してください。
    • シーン内にフォーカスを受け取れるアイテム (QGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable)) が存在しない。
    • フォーカスを受け取れるアイテムが存在しても、それが実際にフォーカスを得る操作(クリック、Tabキーなど)が行われていない。
    • 親となる QGraphicsView がフォーカスを持っていない、またはフォーカスを受け取れない設定になっている。
    • 他のウィジェットがフォーカスを奪っている。

focusInEvent() 内の処理が期待通りに動作しない

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

    • focusInEvent() 内の処理をステップ実行するなどして、ロジックが意図通りに動作しているか確認してください。
    • QFocusEvent::reason() を利用して、フォーカスを得た理由に応じて異なる処理を行っている場合に、その条件が正しく判定されているか確認してください。
    • 他のスレッドやイベントとの相互作用で問題が発生していないか確認してください。
  • 原因

    • 再実装した focusInEvent() 内のロジックに誤りがある。
    • イベントオブジェクト (QFocusEvent) の情報を正しく利用できていない。
    • 他の処理との競合が発生している。

フォーカスを得たアイテムを特定できない

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

    • フォーカスを得たアイテムを特定する必要がある場合は、通常、各 QGraphicsItemfocusInEvent() ハンドラ内で処理を行います。
    • シーン全体でフォーカスされたアイテムを追跡したい場合は、各アイテムの focusInEvent()focusOutEvent() ハンドラ内で、シーンのメンバー変数などを更新して管理する方法が考えられます。
    • QGraphicsScene::focusItem() 関数を使用すると、現在フォーカスを持っているアイテムを取得できます。focusInEvent() 内でこの関数を呼び出すことで、フォーカスを得たアイテムを確認できます。
  • 原因

    • QGraphicsScene::focusInEvent() はシーン全体に通知されるイベントであり、どのアイテムが実際にフォーカスを得たかの直接的な情報は QFocusEvent オブジェクトには含まれていません。

フォーカスチェインに関する問題

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

    • QGraphicsItem::setFocusProxy() を使用して、フォーカスの委譲先を明示的に設定することで、フォーカス移動の順序を制御できます。
    • アイテムの作成順序や、シーンへの追加順序がフォーカスチェインに影響を与える場合があるため、確認してください。
  • 原因

    • 複数のフォーカス可能なアイテムが存在する場合、Tabキーなどでのフォーカス移動の順序が意図しないものになっている。

スーパークラスの focusInEvent() を呼び忘れている

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

    • 再実装した focusInEvent() の先頭で、必ず QGraphicsScene::focusInEvent(event) を呼び出すようにしてください。
  • 原因

    • focusInEvent() を再実装する際に、スーパークラス (QGraphicsScene::focusInEvent(event)) の呼び出しを忘れていると、デフォルトのフォーカス処理が行われず、予期しない動作を引き起こす可能性があります。

デバッグのヒント

  • フォーカスを受け取れるアイテムの focusPolicy() を明示的に設定してみることも、問題の特定に役立つ場合があります。
  • qDebug() を使用して、focusInEvent() が呼び出されたタイミングや、QFocusEvent オブジェクトの内容(reason() など)を出力してみることは、問題の切り分けに非常に有効です。


例1: シーンがフォーカスを得たときにメッセージを表示する

この例では、QGraphicsScene を継承したカスタムクラス MyScene を作成し、focusInEvent() をオーバーライドして、シーンがフォーカスを得たときにコンソールにメッセージを表示します。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        // フォーカスを受け取れるアイテムを追加
        QGraphicsRectItem *rect1 = new QGraphicsRectItem(0, 0, 50, 50);
        rect1->setFlag(QGraphicsItem::ItemIsFocusable);
        addItem(rect1);

        QGraphicsRectItem *rect2 = new QGraphicsRectItem(60, 0, 50, 50);
        rect2->setFlag(QGraphicsItem::ItemIsFocusable);
        addItem(rect2);
    }

protected:
    void focusInEvent(QFocusEvent *event) override {
        QGraphicsScene::focusInEvent(event); // デフォルトの処理を呼び出す

        qDebug() << "MyScene: シーンがフォーカスを受け取りました。理由:" << event->reason();
    }
};

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

    MyScene scene;
    QGraphicsView view(&scene);
    view.show();
    view.setFocus(); // ビューに初期フォーカスを設定 (なくてもアイテムがフォーカスを得ればシーンの focusInEvent が呼ばれる)

    return a.exec();
}

説明

  • main() 関数では、MyScene のインスタンスを作成し、それを表示する QGraphicsView を作成しています。view.setFocus() は、起動時にビューにフォーカスを設定する例です。アイテムがフォーカスを得れば、シーンの focusInEvent() も呼び出されます。
  • focusInEvent() 関数をオーバーライドし、qDebug() を使ってフォーカスを受け取った際のメッセージと理由 (event->reason()) を出力しています。
  • コンストラクタで、フォーカスを受け取れるようにフラグを設定した二つの QGraphicsRectItem をシーンに追加しています (setFlag(QGraphicsItem::ItemIsFocusable, true))。
  • MyScene クラスは QGraphicsScene を継承しています。

実行結果

このプログラムを実行し、Tabキーを押して矩形の間でフォーカスを移動させると、フォーカスがどちらかの矩形に移るたびに、コンソールに以下のようなメッセージが出力されます。

MyScene: シーンがフォーカスを受け取りました。理由: Qt::TabFocusReason

フォーカスがマウスのクリックによって移動した場合は、理由が Qt::MouseFocusReason になります。

例2: シーンがフォーカスを得たときに背景色を変更する (あまり一般的ではない)

この例では、シーンがフォーカスを得たときに背景色を薄い青色に変更し、フォーカスを失ったときに元の色に戻します。これには、対応する focusOutEvent() も実装する必要があります。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QFocusEvent>
#include <QBrush>

class MyScene : public QGraphicsScene {
private:
    QBrush originalBackgroundBrush;

public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        originalBackgroundBrush = backgroundBrush();

        QGraphicsRectItem *rect1 = new QGraphicsRectItem(0, 0, 50, 50);
        rect1->setFlag(QGraphicsItem::ItemIsFocusable);
        addItem(rect1);

        QGraphicsRectItem *rect2 = new QGraphicsRectItem(60, 0, 50, 50);
        rect2->setFlag(QGraphicsItem::ItemIsFocusable);
        addItem(rect2);
    }

protected:
    void focusInEvent(QFocusEvent *event) override {
        QGraphicsScene::focusInEvent(event);
        setBackgroundBrush(QBrush(Qt::lightGray));
    }

    void focusOutEvent(QFocusEvent *event) override {
        QGraphicsScene::focusOutEvent(event);
        setBackgroundBrush(originalBackgroundBrush);
    }
};

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

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

    return a.exec();
}

説明

  • 対応する focusOutEvent() もオーバーライドし、フォーカスを失ったときに背景色を元の originalBackgroundBrush に戻しています。
  • focusInEvent() で、背景色を Qt::lightGray のブラシに設定しています。
  • MyScene クラスでは、コンストラクタで元の背景色を originalBackgroundBrush に保存しています。

実行結果

このプログラムを実行し、Tabキーで矩形の間でフォーカスを移動させると、フォーカスがシーン内のいずれかの矩形にある間はシーンの背景色が薄い灰色になり、フォーカスがビューの外に出るなどしてシーン内のどのアイテムもフォーカスを持っていない状態になると、元の背景色に戻ります。

例3: フォーカスを得たアイテムを追跡する

この例では、シーン内で現在フォーカスを持っているアイテムを追跡し、フォーカスが変更されるたびにその情報をコンソールに出力します。ここでは、各アイテムの focusInEvent()focusOutEvent() を利用します。

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

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() << "MyRectItem: フォーカスを得ました:" << this;
        emit itemFocused(this);
    }

    void focusOutEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusOutEvent(event);
        qDebug() << "MyRectItem: フォーカスを失いました:" << this;
        emit itemFocusLost(this);
    }

signals:
    void itemFocused(QGraphicsItem *item);
    void itemFocusLost(QGraphicsItem *item);
};

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        MyRectItem *rect1 = new MyRectItem(0, 0, 50, 50);
        addItem(rect1);
        connect(rect1, &MyRectItem::itemFocused, this, &MyScene::onItemFocused);
        connect(rect1, &MyRectItem::itemFocusLost, this, &MyScene::onItemFocusLost);

        MyRectItem *rect2 = new MyRectItem(60, 0, 50, 50);
        addItem(rect2);
        connect(rect2, &MyRectItem::itemFocused, this, &MyScene::onItemFocused);
        connect(rect2, &MyRectItem::itemFocusLost, this, &MyScene::onItemFocusLost);
    }

private slots:
    void onItemFocused(QGraphicsItem *item) {
        qDebug() << "MyScene: アイテムがフォーカスされました:" << item;
    }

    void onItemFocusLost(QGraphicsItem *item) {
        qDebug() << "MyScene: アイテムがフォーカスを失いました:" << item;
    }
};

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

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

    return a.exec();
}
  • この例では、シーン自身の focusInEvent() は直接利用していませんが、アイテムのフォーカス状態の変化をシーンで監視する一般的なパターンを示しています。
  • MyScene クラスでは、これらのシグナルをスロット (onItemFocused, onItemFocusLost) に接続し、フォーカス状態の変化をコンソールに出力します。
  • MyRectItemfocusInEvent()focusOutEvent() で、それぞれフォーカスを得たときと失ったときにシグナル (itemFocused, itemFocusLost) を発行します。
  • MyRectItem クラスは QGraphicsRectItem を継承し、フォーカス可能に設定しています。


個々の QGraphicsItem のフォーカスイベントを処理する

最も一般的で柔軟な方法は、シーン内の個々の QGraphicsItem がフォーカスを得たり失ったりするイベントを処理することです。

  • QGraphicsItem::focusOutEvent(QFocusEvent *event)
    アイテムがキーボードフォーカスを失ったときに呼び出されます。
  • QGraphicsItem::focusInEvent(QFocusEvent *event)
    アイテムがキーボードフォーカスを受け取ったときに呼び出されます。


#include <QGraphicsItem>
#include <QFocusEvent>
#include <QDebug>
#include <QGraphicsRectItem>

class MyFocusableItem : public QGraphicsRectItem {
public:
    MyFocusableItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(x, y, w, h, parent) {
        setFlag(ItemIsFocusable); // フォーカスを受け取れるように設定
    }

protected:
    void focusInEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusInEvent(event);
        qDebug() << "アイテムがフォーカスを得ました:" << this;
        // フォーカスを得た際の処理
    }

    void focusOutEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusOutEvent(event);
        qDebug() << "アイテムがフォーカスを失いました:" << this;
        // フォーカスを失った際の処理
    }
};

利点

  • より粒度の細かい制御が可能です。
  • アイテムごとに異なるフォーカス処理を実装できます。
  • どのアイテムがフォーカスを得たのかを直接知ることができます。

シグナルとスロットを使用する

QGraphicsItem クラスは、フォーカス状態が変化したときに発行されるシグナルを持っていません。しかし、カスタムの QGraphicsItem サブクラスを作成し、focusInEvent()focusOutEvent() の中で独自のシグナルを発行することで、フォーカス状態の変化を他のオブジェクトに通知できます。

例 (上記の例1の MyFocusableItem を拡張)

class MyFocusableItem : public QGraphicsRectItem {
signals:
    void gotFocus();
    void lostFocus();

public:
    // ... (コンストラクタは同じ)

protected:
    void focusInEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusInEvent(event);
        emit gotFocus();
    }

    void focusOutEvent(QFocusEvent *event) override {
        QGraphicsRectItem::focusOutEvent(event);
        emit lostFocus();
    }
};

// ... (メイン関数などでアイテムを作成し、シグナルをスロットに接続)
MyFocusableItem *item = new MyFocusableItem(10, 10, 50, 50);
//connect(item, &MyFocusableItem::gotFocus, someObject, &SomeClass::handleItemGotFocus);
//connect(item, &MyFocusableItem::lostFocus, someObject, &SomeClass::handleItemLostFocus);

利点

  • 複数のオブジェクトがフォーカス状態の変化に応答できます。
  • オブジェクト間の疎結合を実現できます。

QGraphicsScene::focusItemChanged シグナルを使用する

QGraphicsScene クラスは、フォーカスを持つアイテムが変化したときに focusItemChanged(QGraphicsItem *newFocus, QGraphicsItem *oldFocus, Qt::FocusReason reason) シグナルを発行します。これを利用することで、シーン全体のフォーカス状態の変化を監視できます。


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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        QGraphicsRectItem *rect1 = new QGraphicsRectItem(0, 0, 50, 50);
        rect1->setFlag(QGraphicsItem::ItemIsFocusable);
        addItem(rect1);

        QGraphicsRectItem *rect2 = new QGraphicsRectItem(60, 0, 50, 50);
        rect2->setFlag(QGraphicsItem::ItemIsFocusable);
        addItem(rect2);

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

private slots:
    void onFocusItemChanged(QGraphicsItem *newFocus, QGraphicsItem *oldFocus, Qt::FocusReason reason) {
        qDebug() << "フォーカスされた新しいアイテム:" << newFocus;
        qDebug() << "以前にフォーカスされていたアイテム:" << oldFocus;
        qDebug() << "フォーカス変更の理由:" << reason;

        if (newFocus) {
            // 新しいフォーカスアイテムに対する処理
        } else {
            // フォーカスがクリアされた場合の処理
        }
    }
};

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

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

    return a.exec();
}

利点

  • フォーカス変更の理由 (Qt::FocusReason) を知ることができます。
  • 新しいフォーカスアイテムと以前のフォーカスアイテムの両方を知ることができます。
  • シーン全体でフォーカスを持つアイテムの変化を簡単に監視できます。

QGraphicsScene::focusInEvent() を使用する場合

QGraphicsScene::focusInEvent() は、シーン内の いずれかの フォーカス可能なアイテムがフォーカスを得たときに、シーン自身に対して一度だけ呼び出されます。これは、シーン全体の状態を更新したり、フォーカス状態の変化に応じて何らかのグローバルな処理を行いたい場合に便利です。


シーン内のアイテムが初めてフォーカスを得たときに、シーン全体に特定の効果を適用する場合などに使用できます。ただし、どのアイテムがフォーカスを得たのかを直接知ることはできません。

  • シーンが(間接的に)フォーカスを得た瞬間に一度だけ何か処理を行いたい場合 (例: 初期化)
    QGraphicsScene::focusInEvent() を使用します。ただし、具体的なアイテムの操作には他の方法と組み合わせる必要があります。
  • シーン全体のフォーカスを持つアイテムの変化を監視したい場合
    QGraphicsScene::focusItemChanged シグナルを使用します。
  • フォーカス状態の変化を他のオブジェクトに通知したい場合
    カスタムシグナルとスロットを使用します。
  • 特定のアイテムのフォーカス状態に応じて処理を行いたい場合
    個々の QGraphicsItemfocusInEvent()focusOutEvent() を使用します。