Qtのフォーカス管理:QGraphicsSceneとQGraphicsViewの連携

2025-04-26

QGraphicsScene::setFocus() とは

QGraphicsScene::setFocus() は、QGraphicsScene クラスのメンバ関数の一つで、シーン内の特定のアイテム(QGraphicsItem の派生クラスのオブジェクト)にキーボードフォーカスを設定するために使用されます。

役割と機能

  • フォーカス状態の視覚的な表現
    フォーカスが設定されたアイテムは、通常、視覚的にハイライト表示されます(例えば、枠線が強調されたり、色が変わったりします)。これは、どのアイテムが現在キー入力を受け付ける状態にあるかをユーザーに知らせるためです。
  • インタラクティブな操作
    これにより、ユーザーはキーボードを使ってシーン内の特定のアイテムを操作したり、制御したりできるようになります。例えば、テキストエディタのようなアイテムにフォーカスを設定すれば、ユーザーはキーボードで文字を入力できます。
  • キーイベントの送信先
    フォーカスが設定されたアイテムは、キーボード入力(キープレス、キーリリースなど)に関するイベントを受け取るようになります。

使用方法

QGraphicsScene::setFocus() を呼び出すには、フォーカスを与えたい QGraphicsItem のポインタまたは参照を引数として渡します。

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

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

    // QGraphicsScene の作成
    QGraphicsScene scene;

    // 矩形のアイテムを作成
    QGraphicsRectItem *rect1 = new QGraphicsRectItem(0, 0, 100, 50);
    QGraphicsRectItem *rect2 = new QGraphicsRectItem(150, 0, 100, 50);
    scene.addItem(rect1);
    scene.addItem(rect2);

    // rect1 にキーボードフォーカスを設定
    rect1->setFlag(QGraphicsItem::ItemIsFocusable); // フォーカスを受け取れるようにフラグを設定する必要がある
    scene.setFocus(rect1);

    // QGraphicsView の作成とシーンの設定
    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}
  • フォーカスイベント
    アイテムがフォーカスを得たり失ったりすると、focusInEvent() および focusOutEvent() というイベントがそのアイテムに送信されます。これらのイベントハンドラをオーバーライドすることで、フォーカス状態の変化に応じたカスタムな処理を実装できます。
  • フォーカスを解除
    シーン内のどのアイテムにもフォーカスを設定したくない場合は、引数に nullptr を渡して scene.setFocus(nullptr); を呼び出すことができます。
  • フォーカスの移動
    setFocus() を別のアイテムに対して呼び出すと、以前にフォーカスを持っていたアイテムからフォーカスが失われ、新しいアイテムにフォーカスが移動します。
  • ItemIsFocusable フラグ
    QGraphicsItemsetFocus() によってフォーカスを受け取ることができるようにするには、そのアイテムに対して QGraphicsItem::ItemIsFocusable フラグを設定しておく必要があります。これは QGraphicsItem::setFlag() メソッドを使って行います。


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

QGraphicsScene::setFocus() を使用する際に遭遇しやすいエラーとその解決策を以下に示します。

アイテムがフォーカスを受け取れない (No item receives focus)

  • 解決策
    フォーカスを与えたいアイテムに対して、setFlag(QGraphicsItem::ItemIsFocusable, true); を呼び出して、このフラグを明示的に設定してください。
  • 原因
    フォーカスを設定しようとしている QGraphicsItemQGraphicsItem::ItemIsFocusable フラグが設定されていない。
QGraphicsRectItem *rect = new QGraphicsRectItem(0, 0, 100, 50);
rect->setFlag(QGraphicsItem::ItemIsFocusable); // これが必要です
scene->addItem(rect);
scene->setFocus(rect);
  • 解決策
    scene->addItem(item); を呼び出して、アイテムをシーンに確実に追加してください。
  • 原因
    そのアイテムがシーンに追加されていない。

意図しないアイテムがフォーカスを受け取る (Unexpected item receives focus)

  • 解決策
    コード全体を検索して、setFocus() の呼び出し箇所を確認し、意図しない呼び出しがないか確認してください。

  • 原因
    他の場所で意図せず setFocus() が呼び出されている。

  • 解決策
    setFocus() を呼び出す前に、正しい QGraphicsItem のポインタまたは参照を持っていることを確認してください。

  • 原因
    誤ったアイテムに対して setFocus() を呼び出している。

フォーカスが設定されても視覚的な変化がない (No visual feedback of focus)

  • 解決策
    QGraphicsItem::hasFocus() メソッドを使ってアイテムがフォーカスを持っているかどうかを確認し、その結果に基づいて描画を変更するように paint() 関数を実装してください。例えば、フォーカスがある場合に枠線の色を変えるなどです。
  • 原因
    アイテムのペイント処理 (paint() 関数) で、フォーカス状態に応じた描画処理が実装されていない。
void MyGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // ... 他の描画処理 ...

    if (hasFocus()) {
        QPen pen = painter->pen();
        pen.setColor(Qt::blue); // フォーカスがあるときは青い枠線
        pen.setWidth(2);
        painter->setPen(pen);
        painter->drawRect(boundingRect());
    } else {
        // フォーカスがないときの描画
        // ...
    }
}
  • 解決策
    スタイルシートやグラフィックス効果の設定を確認し、意図しない上書きがないか確認してください。必要であれば、カスタムのフォーカスインジケータを実装する必要があります。
  • 原因
    スタイルシートやグラフィックス効果が、デフォルトのフォーカスインジケータを上書きしている。

フォーカスがすぐに別のアイテムに移ってしまう (Focus immediately jumps to another item)

  • 解決策
    親ウィジェットや他のアイテムのフォーカスポリシー (setFocusPolicy()) を確認し、意図しないフォーカスの奪取を防ぐように設定してください。

  • 原因
    他のウィジェットやアイテムが自動的にフォーカスを得ようとしている。

  • 解決策
    イベントハンドラ内のロジックを見直し、不要な setFocus() の呼び出しがないか確認してください。

  • 原因
    フォーカスを得たアイテムのイベントハンドラ(例えば、マウスイベントやキーイベント)の中で、意図せず別のアイテムに setFocus() を呼び出している。

setFocus() を呼び出しても何も起こらない (Calling setFocus() has no effect)

  • 解決策
    親ウィジェットやビューが有効になっている (isEnabled() == true) ことを確認してください。無効になっている場合、フォーカスを受け付けることはできません。

  • 原因
    親ウィジェットやビューが無効になっている。

  • 解決策
    QGraphicsSceneQGraphicsView に設定し (view->setScene(scene);)、そのビューが表示されている (view->show();) ことを確認してください。フォーカスは、表示されているアクティブなビューを通じて機能します。

  • 原因
    シーンがアクティブなビューに関連付けられていない、またはビューが表示されていない。

トラブルシューティングのヒント

  • Qtのドキュメント
    Qtの公式ドキュメントは、各クラスや関数の詳細な情報を提供しています。困ったときは、ドキュメントを参照してください。
  • シンプルな例
    問題を切り分けるために、最小限のコードで問題を再現できる簡単な例を作成してみてください。
  • ステップ実行
    デバッガを使ってコードをステップ実行し、setFocus() の呼び出し前後で何が起こっているかを確認してください。
  • デバッグ出力
    qDebug() を使って、どのアイテムにフォーカスを設定しようとしているか、また hasFocus() の結果などを出力して、状況を把握するのに役立ててください。


例1: 単一のフォーカス可能なアイテムにフォーカスを設定する

この例では、シーンに一つの矩形アイテムを追加し、それにキーボードフォーカスを設定します。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#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
    {
        Q_UNUSED(event);
        qDebug() << "MyRectItem がフォーカスを得ました";
        // フォーカスを得たときの処理(例:色を変える)
        setBrush(Qt::green);
        update();
    }

    // フォーカスを失ったときのイベントハンドラ
    void focusOutEvent(QFocusEvent *event) override
    {
        Q_UNUSED(event);
        qDebug() << "MyRectItem がフォーカスを失いました";
        // フォーカスを失ったときの処理(例:色を元に戻す)
        setBrush(Qt::red);
        update();
    }

    // キープレスイベントハンドラ
    void keyPressEvent(QKeyEvent *event) override
    {
        qDebug() << "MyRectItem でキーが押されました:" << event->text();
        // キー入力に応じた処理
        QGraphicsRectItem::keyPressEvent(event); // デフォルトの処理も呼び出す
    }
};

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    // MyRectItem のインスタンスを作成
    MyRectItem *rect = new MyRectItem(0, 0, 100, 50);
    rect->setBrush(Qt::red);
    scene.addItem(rect);

    // 初期フォーカスを rect に設定
    scene.setFocus(rect);

    view.setSceneRect(scene.itemsBoundingRect());
    view.show();

    return a.exec();
}

説明

  1. MyRectItem クラス
    QGraphicsRectItem を継承し、ItemIsFocusable フラグを設定しています。
  2. focusInEvent()
    このアイテムがフォーカスを得たときに呼び出されます。ここではコンソールにメッセージを出力し、背景色を緑に変更しています。
  3. focusOutEvent()
    このアイテムがフォーカスを失ったときに呼び出されます。ここではコンソールにメッセージを出力し、背景色を赤に戻しています。
  4. keyPressEvent()
    このアイテムにフォーカスがある状態でキーが押されると呼び出されます。押されたキーのテキストをコンソールに出力しています。
  5. main() 関数
    • QGraphicsSceneQGraphicsView を作成します。
    • MyRectItem のインスタンスを作成し、シーンに追加します。
    • scene.setFocus(rect); を呼び出すことで、初期状態でこの矩形にキーボードフォーカスが設定されます。

例2: 複数のフォーカス可能なアイテム間でフォーカスを移動する

この例では、シーンに複数のフォーカス可能な矩形アイテムを追加し、Tabキーを使ってフォーカスを移動できるようにします。

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

class FocusableRectItem : public QGraphicsRectItem
{
public:
    FocusableRectItem(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
    {
        Q_UNUSED(event);
        qDebug() << "FocusableRectItem がフォーカスを得ました:" << this;
        setBrush(Qt::blue);
        update();
    }

    void focusOutEvent(QFocusEvent *event) override
    {
        Q_UNUSED(event);
        qDebug() << "FocusableRectItem がフォーカスを失いました:" << this;
        setBrush(Qt::lightGray);
        update();
    }
};

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    // 複数の FocusableRectItem を作成して追加
    FocusableRectItem *rect1 = new FocusableRectItem(0, 0, 100, 50);
    rect1->setBrush(Qt::lightGray);
    scene.addItem(rect1);

    FocusableRectItem *rect2 = new FocusableRectItem(150, 0, 100, 50);
    rect2->setBrush(Qt::lightGray);
    scene.addItem(rect2);

    FocusableRectItem *rect3 = new FocusableRectItem(0, 100, 100, 50);
    rect3->setBrush(Qt::lightGray);
    scene.addItem(rect3);

    // 最初のアイテムに初期フォーカスを設定
    scene.setFocus(rect1, Qt::TabFocusReason);

    view.setSceneRect(scene.itemsBoundingRect().adjusted(-10, -10, 10, 10));
    view.show();

    return a.exec();
}

説明

  1. FocusableRectItem クラス
    ItemIsFocusable フラグを設定し、フォーカスを得たり失ったりしたときに背景色を変更するように focusInEvent()focusOutEvent() を実装しています。
  2. main() 関数
    • 複数の FocusableRectItem のインスタンスを作成し、それぞれ異なる位置に配置してシーンに追加します。
    • scene.setFocus(rect1, Qt::TabFocusReason); を呼び出すことで、最初の矩形に初期フォーカスを設定します。Qt::TabFocusReason は、フォーカスがTabキーによる移動であることを示唆するヒントです(主に視覚的なフォーカス表示に使われることがあります)。
  3. フォーカスの移動
    このコードを実行すると、最初の矩形が青くハイライトされ、フォーカスを持っていることがわかります。Tabキーを押すと、フォーカスがシーン内の次のフォーカス可能なアイテム(この場合は rect2rect3 の順)に移動し、それぞれの背景色が青く変わります。Shift+Tab を押すと、逆順にフォーカスが移動します。これは、QGraphicsView がデフォルトでTabキーによるフォーカス移動を処理してくれるためです。

例3: フォーカスをプログラム的に変更する

この例では、ボタンをクリックすることで、特定のアイテムにプログラム的にフォーカスを設定します。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>

class FocusableRectItem : public QGraphicsRectItem
{
public:
    FocusableRectItem(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
    {
        Q_UNUSED(event);
        qDebug() << "FocusableRectItem がフォーカスを得ました:" << this;
        setPen(QPen(Qt::green, 3));
        update();
    }

    void focusOutEvent(QFocusEvent *event) override
    {
        Q_UNUSED(event);
        qDebug() << "FocusableRectItem がフォーカスを失いました:" << this;
        setPen(QPen(Qt::black, 1));
        update();
    }
};

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

    QWidget window;
    QVBoxLayout layout(&window);
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    QPushButton button1("矩形1にフォーカス");
    QPushButton button2("矩形2にフォーカス");

    FocusableRectItem *rect1 = new FocusableRectItem(0, 0, 100, 50);
    scene.addItem(rect1);
    rect1->setPos(50, 50);

    FocusableRectItem *rect2 = new FocusableRectItem(0, 0, 100, 50);
    scene.addItem(rect2);
    rect2->setPos(200, 50);

    layout.addWidget(&view);
    layout.addWidget(&button1);
    layout.addWidget(&button2);
    window.setLayout(&layout);

    QObject::connect(&button1, &QPushButton::clicked, [&]() {
        scene.setFocus(rect1, Qt::MouseFocusReason); // マウス操作によるフォーカス移動
    });

    QObject::connect(&button2, &QPushButton::clicked, [&]() {
        scene.setFocus(rect2, Qt::MouseFocusReason);
    });

    view.setSceneRect(scene.itemsBoundingRect().adjusted(-10, -10, 10, 10));
    window.show();

    return a.exec();
}
  1. FocusableRectItem クラス
    例2と同様に、フォーカス状態に応じて枠線の色を変えるように実装しています。
  2. main() 関数
    • QWidget をメインウィンドウとし、垂直レイアウト (QVBoxLayout) を設定します。
    • QGraphicsSceneQGraphicsView を作成し、レイアウトに追加します。
    • 2つの QPushButton を作成し、それぞれ「矩形1にフォーカス」「矩形2にフォーカス」というラベルを設定してレイアウトに追加します。
    • 2つの FocusableRectItem を作成し、シーンに追加してそれぞれ異なる位置に配置します。
    • シグナルとスロットの接続
      • ボタン1がクリックされると、ラムダ関数が実行され scene.setFocus(rect1, Qt::MouseFocusReason); が呼び出されます。これにより、矩形1にフォーカスが設定されます。Qt::MouseFocusReason は、フォーカスがマウス操作によって移動したことを示唆します。
      • 同様に、ボタン2がクリックされると、矩形2にフォーカスが設定されます。


QGraphicsView::setFocus() とフォーカスポリシーの利用


  • 利点

    • シーン全体としてのフォーカス管理に役立ちます。
    • ユーザーがマウスなどでアイテムを操作した際に、自然にフォーカスが移動するような挙動を実装しやすいです。
  • 関連する要素

    • QGraphicsView::setFocusPolicy()
      ビューがどのようにフォーカスを受け取るかを制御します (Qt::TabFocusPolicy など)。
    • QGraphicsItem::setFocusPolicy()
      個々のアイテムがどのようにフォーカスを受け取るかを制御します。デフォルトでは Qt::NoFocus なので、明示的に設定する必要があります (Qt::ItemIsFocusable は内部的にこのポリシーを設定します)。
    • フォーカスイベント (focusInEvent, focusOutEvent): ビューまたはアイテムがフォーカスを得たり失ったりしたときに発生するイベントです。
  • 説明
    QGraphicsView は、シーンを表示するビューであり、それ自体もフォーカスを持つことができます。ビューにフォーカスがある場合、ビュー内で最後にインタラクションがあった(例えば、マウスでクリックされた)フォーカス可能なアイテムが暗黙的にフォーカスを持つことがあります。QGraphicsView::setFocus() を呼び出すことで、ビュー自身にフォーカスを設定できます。

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

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

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

    QGraphicsRectItem *rect2 = new QGraphicsRectItem(150, 0, 100, 50);
    rect2->setFlag(QGraphicsItem::ItemIsFocusable);
    scene.addItem(rect2);

    view.setSceneRect(scene.itemsBoundingRect());
    view.setFocusPolicy(Qt::StrongFocus); // ビューがキーボードフォーカスを受け取れるように設定
    view.setFocus(); // ビューにフォーカスを設定

    view.show();
    return a.exec();
}

この例では、ビューに StrongFocus ポリシーを設定し、ビュー自身にフォーカスを与えています。これにより、ビュー内のフォーカス可能なアイテムがTabキーなどでフォーカスを受け取れるようになります。

シグナルとスロットによる間接的なフォーカス制御


  • (前述の例3を参照 - ボタンクリックで setFocus() を呼び出すのはこの一種です)

  • 利点

    • フォーカスの設定ロジックを特定のイベントや状態に結びつけることができ、より柔軟な制御が可能になります。
    • GUI要素(ボタンなど)の操作によってフォーカスを間接的に制御できます。
  • 説明
    直接 setFocus() を呼び出す代わりに、特定の条件やイベントが発生したときに、シグナルを発行し、そのシグナルを受け取ったスロット内で setFocus() を呼び出す方法です。

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


  • 利点

    • アプリケーションの要件に完全に合致した、高度なフォーカス制御を実装できます。
    • 特殊なUIパターンやインタラクションを実現できます。
  • 方法

    • キーイベント (keyPressEvent) をビューまたは特定のアイテムで捕捉し、押されたキーに応じて setFocus() を呼び出すアイテムを決定します。
    • 特定のアイテムの状態変化に応じて、プログラム的に setFocus() を呼び出すアイテムを変更します。
  • 説明
    デフォルトのフォーカス移動の挙動(Tabキーなど)に頼らず、アプリケーション独自のロジックに基づいてフォーカスを管理する方法です。

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

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

protected:
    void keyPressEvent(QKeyEvent *event) override
    {
        QList<QGraphicsItem*> focusableItems = scene()->items(Qt::AscendingOrder);
        QGraphicsItem *currentFocusItem = scene()->focusItem();
        int currentIndex = -1;

        for (int i = 0; i < focusableItems.size(); ++i) {
            if (focusableItems[i]->isFocusable() && focusableItems[i] == currentFocusItem) {
                currentIndex = i;
                break;
            }
        }

        if (event->key() == Qt::Key_Right) {
            int nextIndex = (currentIndex + 1) % focusableItems.size();
            for (int i = 0; i < focusableItems.size(); ++i) {
                int indexToCheck = (nextIndex + i) % focusableItems.size();
                if (focusableItems[indexToCheck]->isFocusable()) {
                    scene()->setFocus(focusableItems[indexToCheck], Qt::KeyboardFocusReason);
                    return;
                }
            }
        } else if (event->key() == Qt::Key_Left) {
            int prevIndex = (currentIndex - 1 + focusableItems.size()) % focusableItems.size();
            for (int i = 0; i < focusableItems.size(); ++i) {
                int indexToCheck = (prevIndex - i + focusableItems.size()) % focusableItems.size();
                if (focusableItems[indexToCheck]->isFocusable()) {
                    scene()->setFocus(focusableItems[indexToCheck], Qt::KeyboardFocusReason);
                    return;
                }
            }
        }
        QGraphicsView::keyPressEvent(event); // 他のキーイベントはデフォルト処理に渡す
    }
};

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

    QGraphicsScene scene;
    MyView view(&scene);

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

    QGraphicsRectItem *rect2 = new QGraphicsRectItem(0, 0, 100, 50);
    rect2->setFlag(QGraphicsItem::ItemIsFocusable);
    scene.addItem(rect2);
    rect2->setPos(200, 50);

    view.setSceneRect(scene.itemsBoundingRect().adjusted(-10, -10, 10, 10));
    view.show();

    return a.exec();
}

この例では、MyView クラスで keyPressEvent をオーバーライドし、左右の矢印キーが押されたときに、シーン内のフォーカス可能なアイテムを順番に巡回してフォーカスを設定するカスタムロジックを実装しています。

フォーカスチェインの利用 (高度なトピック)

  • 方法
    • QWidget::setTabOrder() を使用して、QWidget間のフォーカス移動順序を設定できます。
    • QGraphicsProxyWidget を使用して、QWidgetをQGraphicsSceneに埋め込むことで、グラフィックスアイテムとQWidget間のフォーカス移動をある程度統合できます。ただし、QGraphicsScene内の純粋な QGraphicsItem 同士のフォーカスチェインを直接的に制御する標準的なAPIは限られています。カスタムフォーカス管理ロジック(上記3)を組み合わせることが一般的です。
  • 説明
    より複雑なUI構造を持つアプリケーションでは、フォーカスチェイン(フォーカスの移動順序)を明示的に制御する必要がある場合があります。Qtの標準的なフォーカス管理は通常十分ですが、カスタムウィジェットやグラフィックスアイテムが混在するような場合には、より詳細な制御が必要になることがあります。