Qt グラフィックシーンのフォーカス制御:hasFocus() の役割と代替手法

2025-04-26

QGraphicsScene::hasFocus() とは

QGraphicsScene::hasFocus() は、QGraphicsScene クラスのメンバ関数の一つで、そのシーン自身が現在キーボードフォーカスを持っているかどうかを判定するために使用されます。

キーボードフォーカスとは

キーボードフォーカスとは、アプリケーション内のどのウィジェット(またはシーン)が、キーボードからの入力を受け取る状態にあるかを示す概念です。通常、ユーザーがタブキーを押したり、ウィジェットをクリックしたりすることで、キーボードフォーカスが別のウィジェットに移動します。



よくあるエラーとトラブルシューティング

QGraphicsScene::hasFocus() は比較的シンプルな関数ですが、その挙動を理解していないと予期せぬ問題に遭遇することがあります。以下に一般的なエラーとその解決策を挙げます。

期待通りに true が返ってこない

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

    • QGraphicsView がフォーカスを持っているか確認
      QGraphicsView::hasFocus() を呼び出して確認してください。
    • フォーカスを受け取るべきアイテムの ItemIsFocusable フラグを確認
      QGraphicsItem::flags() を調べて、QGraphicsItem::ItemIsFocusable が設定されているか確認してください。
    • 明示的にアイテムにフォーカスを設定
      QGraphicsItem::setFocus() を呼び出して、意図したアイテムにフォーカスを与えてみてください。
    • フォーカスイベントの確認
      QGraphicsView やフォーカス可能なアイテムのフォーカスインイベント (focusInEvent) とフォーカスアウトイベント (focusOutEvent) をオーバーライドして、フォーカスがどのように変化しているかログ出力するなどして確認してください。
    • シーン自身がフォーカスを受け取れない設定になっている
      QGraphicsScene は、デフォルトではキーボードフォーカスを受け取ることができません。フォーカスを受け取るためには、通常、それを表示する QGraphicsView がフォーカスを持ち、さらにそのフォーカスがシーン内のフォーカス可能なアイテムに委譲される必要があります。シーン自身に直接フォーカスを設定する方法は一般的ではありません。
    • フォーカスを持つべきアイテムがフォーカス可能になっていない
      シーン内の特定のアイテムがキーボード入力を受け取るべき場合、そのアイテムが QGraphicsItem::ItemIsFocusable フラグを true に設定されている必要があります。
    • フォーカスが別のウィジェットに移っている
      アプリケーション内の他のウィジェット(例えば、ボタン、LineEditなど)がキーボードフォーカスを持っている場合、シーン(またはそのアイテム)はフォーカスを持っていません。
    • QGraphicsView がフォーカスを持っていない
      シーンを表示している QGraphicsView 自体がフォーカスを持っていない場合、その内部のシーンやアイテムも通常はフォーカスを持つことができません。QGraphicsView::setFocus() を呼び出すか、ユーザーが QGraphicsView をクリックするなどしてフォーカスを与える必要があります。

意図せず true が返ってくる

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

    • シーンにフォーカスを設定している箇所がないか確認
      コード全体を検索して、scene->setFocus() のような呼び出しがないか確認してください。もしあれば、その意図を再検討してください。
    • フォーカスチェインの確認
      QGraphicsView::focusProxy()QGraphicsItem::focusProxy() を使用している場合、フォーカスの委譲が意図通りに行われているか確認してください。
  • 原因

    • 誤ってシーンにフォーカスを設定しようとしている
      通常、シーン自身に直接フォーカスを設定する必要はありません。フォーカスは QGraphicsView やその内部のアイテムが持つべきものです。
    • フォーカス管理の混乱
      複数のフォーカス可能なアイテムが存在する場合、どのアイテムがフォーカスを持っているかを正しく管理できていない可能性があります。

フォーカスが当たっているのにキーイベントが処理されない

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

    • ItemAcceptsKeyEvents フラグの確認
      フォーカスを持つアイテムのフラグを確認してください。
    • キーイベントハンドラの実装を確認
      keyPressEventkeyReleaseEvent が正しく実装され、必要な処理を行っているか確認してください。
    • イベントフィルタの確認
      イベントフィルタが設定されている箇所を確認し、キーイベントをフィルタリングしていないか確認してください。
  • 原因

    • アイテムがキーイベントを受け付ける設定になっていない
      フォーカスを持っているアイテムがキーイベントを処理するためには、QGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable) に加えて、QGraphicsItem::setFlag(QGraphicsItem::ItemAcceptsKeyEvents)true に設定する必要があります。
    • キーイベントハンドラ (keyPressEvent, keyReleaseEvent) が実装されていない、または正しく実装されていない
      アイテムでキー入力を処理するには、これらのイベントハンドラをオーバーライドする必要があります。
    • イベントフィルタリング
      シーンやビュー、あるいは親ウィジェットにインストールされたイベントフィルタがキーイベントを横取りしている可能性があります。

フォーカス状態の視覚的なフィードバックがない

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

    • focusInEvent と focusOutEvent の実装
      これらのイベントハンドラ内で、フォーカス状態に応じてアイテムの描画状態を変更する処理を追加してください。例えば、update() を呼び出して再描画をトリガーし、paint() 関数内でフォーカス状態に応じた描画を行うようにします。
  • 原因

    • フォーカスを示す描画処理が実装されていない
      フォーカスが当たっていることをユーザーに知らせるために、アイテムがフォーカスを得た際の描画処理(例えば、枠線の変更など)を実装する必要があります。

QGraphicsScene::hasFocus() はシーン自身がキーボードフォーカスを持っているかを判定する関数ですが、通常は QGraphicsView やその内部の QGraphicsItem がフォーカスを管理します。トラブルシューティングの際は、以下の点に注意して確認を進めてください。

  • 他のウィジェットやイベントフィルタがフォーカスやイベントを妨害していないか?
  • フォーカスイベントは正しく処理されているか? (focusInEvent, focusOutEvent, keyPressEvent, keyReleaseEvent)
  • フォーカスを受け取るための設定は正しく行われているか? (ItemIsFocusable, ItemAcceptsKeyEvents)
  • フォーカスはどこにあるべきか? シーン自身か、それとも特定のアイテムか?


例1: シーンのフォーカス状態を監視し、デバッグ出力する

この例では、QGraphicsView に表示された QGraphicsScene のフォーカス状態をタイマーを使って定期的に監視し、その結果をデバッグ出力します。

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

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 200, 200);

    QGraphicsView view(&scene);
    view.setFocusPolicy(Qt::StrongFocus); // QGraphicsView がフォーカスを受け取れるように設定
    view.show();
    view.setFocus(); // 最初はビューにフォーカスを与える

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&]() {
        qDebug() << "Scene has focus:" << scene.hasFocus();
        qDebug() << "View has focus:" << view.hasFocus();
    });
    timer.start(1000); // 1秒ごとに確認

    return a.exec();
}

説明

  • ラムダ関数内では、scene.hasFocus()view.hasFocus() を呼び出し、それぞれのフォーカス状態を qDebug() で出力しています。
  • QTimer を使用して、1秒ごとにラムダ関数を実行しています。
  • view.setFocus(); は、プログラム開始時に QGraphicsView にキーボードフォーカスを与えます。
  • view.setFocusPolicy(Qt::StrongFocus); は、QGraphicsView がキーボードフォーカスを受け取ることができるように設定しています。
  • まず、QGraphicsScene とそれを表示する QGraphicsView を作成します。

この例を実行すると、QGraphicsView がフォーカスを持っている間は scene.hasFocus()false を返し、view.hasFocus()true を返すことがわかります。通常、シーン自身が直接フォーカスを持つことは少ないため、このような結果になります。

例2: シーン内のアイテムのフォーカス状態に応じて処理を変える

この例では、シーンにフォーカス可能な矩形アイテムを追加し、そのアイテムがフォーカスを持っているかどうかでシーンの背景色を変えます。

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

class FocusableRect : public QGraphicsRectItem
{
public:
    FocusableRect(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
    {
        scene()->setBackgroundBrush(Qt::yellow);
        QGraphicsRectItem::focusInEvent(event);
    }

    void focusOutEvent(QFocusEvent *event) override
    {
        scene()->setBackgroundBrush(Qt::white);
        QGraphicsRectItem::focusOutEvent(event);
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 300, 200);
    scene.setBackgroundBrush(Qt::white);

    FocusableRect *rect = new FocusableRect(50, 50, 200, 100);
    scene.addItem(rect);
    rect->setFocus(); // 最初はこの矩形にフォーカスを与える

    QGraphicsView view(&scene);
    view.setFocusPolicy(Qt::StrongFocus);
    view.show();

    return a.exec();
}

説明

  • QGraphicsView のフォーカスポリシーも Qt::StrongFocus に設定しています。
  • main() 関数では、FocusableRect のインスタンスを作成し、シーンに追加した後、rect->setFocus() を呼び出して最初にこの矩形にフォーカスを与えています。
  • focusOutEvent() をオーバーライドし、アイテムがフォーカスを失ったときにシーンの背景色を白に戻しています。
  • focusInEvent() をオーバーライドし、アイテムがフォーカスを得たときにシーンの背景色を黄色に変更しています。
  • FocusableRect クラスは QGraphicsRectItem を継承し、コンストラクタで ItemIsFocusable フラグを true に設定しています。これにより、この矩形アイテムはキーボードフォーカスを受け取ることができます。

この例を実行すると、矩形がフォーカスを持っている間はシーンの背景色が黄色になり、フォーカスが外れると白に戻ることが確認できます。この例では QGraphicsScene::hasFocus() は直接使用していませんが、アイテムのフォーカス状態の変化に応じてシーンの状態を間接的に制御しています。

例3: QGraphicsView のフォーカス状態に応じてシーン内のアイテムの有効/無効を切り替える (間接的な利用)

この例では、QGraphicsView がフォーカスを持っている場合にのみ、シーン内の特定のアイテムを操作可能にします。

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

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

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override
    {
        if (scene()->views().first()->hasFocus()) { // ビューがフォーカスを持っているか確認
            QGraphicsRectItem::mousePressEvent(event);
            qDebug() << "Rect pressed (View has focus)";
        } else {
            qDebug() << "Rect pressed (View does not have focus)";
        }
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 300, 200);

    MovableRect *rect = new MovableRect(50, 50, 100, 100);
    scene.addItem(rect);
    rect->setFocus();

    QGraphicsView view(&scene);
    view.setFocusPolicy(Qt::StrongFocus);
    view.show();
    view.setFocus();

    return a.exec();
}

説明

  • ビューがフォーカスを持っている場合にのみ、矩形の移動処理 (QGraphicsRectItem::mousePressEvent(event)) を実行し、そうでない場合はデバッグメッセージを出力します。
  • mousePressEvent() をオーバーライドし、マウスボタンが押されたときに、そのアイテムが属するシーンの最初のビュー (scene()->views().first()) がフォーカスを持っているかどうかを hasFocus() で確認しています。
  • MovableRect クラスは QGraphicsRectItem を継承し、ItemIsMovableItemIsFocusable フラグを true に設定しています。

この例では、シーン自身ではなく、シーンを表示する QGraphicsView のフォーカス状態を利用して、シーン内のアイテムの動作を制御しています。



QGraphicsView::hasFocus() を利用する

シーンは通常 QGraphicsView を通して表示されるため、ビューがフォーカスを持っているかどうかを確認することで、間接的にシーンとのインタラクションが可能かどうかを判断できます。

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

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setFocusPolicy(Qt::StrongFocus);
    view.show();
    view.setFocus();

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&]() {
        qDebug() << "View has focus:" << view.hasFocus();
    });
    timer.start(1000);

    return a.exec();
}

利点

  • 一般的なアプリケーションのUI構造において、ビューが入力の起点となることが多いです。
  • より直接的にユーザーが操作しているビューの状態を反映します。

欠点

  • シーン内の特定のアイテムがフォーカスを持っているかどうかは直接的には分かりません。

QGraphicsItem::hasFocus() とフォーカスイベントを利用する

シーン内の個々のアイテムがフォーカスを持つように設定 (ItemIsFocusable) し、アイテムのフォーカス状態の変化に応じて処理を行う方法です。

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

class FocusableItem : public QGraphicsRectItem
{
public:
    FocusableItem(qreal x, qreal y, qreal w, qreal h) : QGraphicsRectItem(x, y, w, h)
    {
        setFlag(ItemIsFocusable);
    }

protected:
    void focusInEvent(QFocusEvent *event) override
    {
        qDebug() << "Item gained focus";
        // フォーカスを得た時の処理
        QGraphicsRectItem::focusInEvent(event);
    }

    void focusOutEvent(QFocusEvent *event) override
    {
        qDebug() << "Item lost focus";
        // フォーカスを失った時の処理
        QGraphicsRectItem::focusOutEvent(event);
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 200, 200);

    FocusableItem *item = new FocusableItem(50, 50, 100, 100);
    scene.addItem(item);
    item->setFocus(); // 最初にアイテムにフォーカスを設定

    QGraphicsView view(&scene);
    view.setFocusPolicy(Qt::StrongFocus);
    view.show();

    return a.exec();
}

利点

  • アイテムごとに異なるフォーカス時の挙動を実装できます。
  • シーン内のどの特定の要素がキーボード入力を受け付けているかを正確に把握できます。

欠点

  • シーン全体としてのフォーカス状態を把握するには、個々のアイテムの状態を管理する必要があります。

フォーカスチェインと focusProxy() を利用する

複雑なUI構造を持つ場合、フォーカスを別のアイテムに委譲することがあります。QGraphicsItem::setFocusProxy() を使用すると、あるアイテムがフォーカスを得た際に、別のアイテムにそのフォーカスを転送できます。

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

class ProxyItem : public QGraphicsRectItem
{
public:
    ProxyItem(qreal x, qreal y, qreal w, qreal h) : QGraphicsRectItem(x, y, w, h)
    {
        setFlag(ItemIsFocusable);
    }

protected:
    void focusInEvent(QFocusEvent *event) override
    {
        qDebug() << "ProxyItem gained focus, but will proxy to TargetItem";
        QGraphicsRectItem::focusInEvent(event);
        if (targetItem) {
            targetItem->setFocus();
        }
    }

private:
    FocusableItem *targetItem = nullptr;
};

class FocusableItem : public QGraphicsRectItem
{
public:
    FocusableItem(qreal x, qreal y, qreal w, qreal h) : QGraphicsRectItem(x, y, w, h)
    {
        setFlag(ItemIsFocusable);
    }

protected:
    void focusInEvent(QFocusEvent *event) override
    {
        qDebug() << "TargetItem gained focus";
        QGraphicsRectItem::focusInEvent(event);
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 300, 200);

    ProxyItem *proxy = new ProxyItem(20, 20, 50, 50);
    FocusableItem *target = new FocusableItem(100, 20, 100, 50);
    proxy->targetItem = target; // ターゲットアイテムを設定

    scene.addItem(proxy);
    scene.addItem(target);
    proxy->setFocus(); // プロキシアイテムに最初にフォーカスを設定

    QGraphicsView view(&scene);
    view.setFocusPolicy(Qt::StrongFocus);
    view.show();

    return a.exec();
}

利点

  • 論理的なグループ化に基づいてフォーカスを処理できます。
  • フォーカスの管理をより柔軟に行えます。

欠点

  • フォーカスチェインが複雑になると、管理が難しくなる可能性があります。

カスタムのフォーカス管理ロジックを実装する

特定のアプリケーションの要件に合わせて、独自のフォーカス管理メカニズムを実装することも可能です。例えば、シーン内の特定の状態に基づいて、どのアイテムが「アクティブ」であるかを管理し、キーイベントをそのアクティブなアイテムにルーティングするなどです。

利点

  • アプリケーションの特定のニーズに完全に合わせたフォーカス管理が可能です。

欠点

  • 実装が複雑になる可能性があり、デバッグや保守が難しくなることがあります。

QGraphicsScene::hasFocus() の利用場面

QGraphicsScene::hasFocus() が有用な場面は比較的限られています。例えば、ビューにフォーカスがない場合に、シーン全体で何らかのグローバルな処理を停止したり、特定のUI要素を非表示にしたりする場合などが考えられます。しかし、通常はビューやアイテムのフォーカス状態をより細かく制御する方が一般的です。

QGraphicsScene::hasFocus() の代替となる方法は、アプリケーションの具体的な要件やUIの構造によって異なります。

  • カスタムのフォーカス管理
    特殊な要件を持つアプリケーション向けです。
  • フォーカスチェイン (focusProxy())
    複雑なUIで、フォーカスの委譲を制御したい場合に有効です。
  • QGraphicsItem::hasFocus() とフォーカスイベント
    シーン内の個々のアイテムのフォーカス状態を管理し、それに応じて処理を行いたい場合に適しています。
  • QGraphicsView::hasFocus()
    ビュー全体のフォーカス状態を把握したい場合に適しています。