【Qt入門】QGraphicsViewでのTab移動をマスター!focusNextPrevChild徹底解説

2025-05-27

bool QGraphicsView::focusNextPrevChild(bool next) とは

この関数は、Qtのグラフィックスビューフレームワークで、QGraphicsView クラスに属するプロテクトされた仮想関数です。主にキーボード操作によるフォーカス(入力カーソル)の移動を制御するために使用されます。

  • 引数
    bool next
    • true: 次のフォーカス可能なウィジェット/アイテムに移動する場合(Tabキー相当)。
    • false: 前のフォーカス可能なウィジェット/アイテムに移動する場合(Shift+Tabキー相当)。
  • 戻り値
    bool 型を返します。
    • true: フォーカス移動が成功し、次の(または前の)ウィジェット/アイテムにフォーカスが移った場合。
    • false: フォーカス移動ができなかった場合(例:最後のウィジェット/アイテムに到達し、それ以上移動できない場合など)。
  • 機能
    ビューポート内のウィジェットやグラフィックスアイテムの間で、キーボードのTabキー(またはShift+Tab)によるフォーカス移動を処理します。

詳しい説明

QtのGUIアプリケーションでは、Tabキーを使って入力フィールドなどのウィジェット間を移動するのが一般的です。QGraphicsView は、QGraphicsScene 上に配置されたQGraphicsItemQGraphicsProxyWidgetQWidget をシーン上に表示するためのプロキシ)に対して、このフォーカス移動の仕組みを提供します。

QGraphicsView::focusNextPrevChild() は、QGraphicsView 自体がウィジェットツリーの一部としてフォーカスを受け取った際に、そのビュー内のグラフィックスアイテムやプロキシウィジェットに対してフォーカスを移す役割を担います。

この関数は通常、ユーザーがTabキーを押したときにQtのイベントシステムによって内部的に呼び出されます。デフォルトの実装では、シーン内のフォーカス可能なアイテムやウィジェットを検索し、適切な次の(または前の)アイテムにフォーカスを移動しようとします。

この関数はprotected virtual なので、QGraphicsView を継承したカスタムクラスでオーバーライド(再実装)することができます。これは以下のような場合に役立ちます。

  • フォーカス移動時の特殊な処理
    フォーカスが移動した際に、何かカスタムのアニメーションや視覚的なフィードバックを提供したい場合。
  • 特定の条件でのフォーカス制御
    ある条件が満たされた場合にのみフォーカス移動を許可したり、特定のアイテムをスキップしたりしたい場合。
  • カスタムのタブ順序
    シーン内のアイテムに対して、標準のTabキーの順序とは異なる、独自のフォーカス順序を実装したい場合。例えば、特定のアイテムのグループ内でのみTab移動を完結させたい場合など。
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QDebug>

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

protected:
    // focusNextPrevChild をオーバーライド
    bool focusNextPrevChild(bool next) override
    {
        qDebug() << "focusNextPrevChild called. next:" << next;

        // シーン内の現在のフォーカスアイテムを取得
        QGraphicsItem* currentFocusItem = scene()->focusItem();

        // ここにカスタムのフォーカス移動ロジックを記述
        // 例: 特定のアイテム群の中でだけTab移動を巡回させる
        if (currentFocusItem) {
            // 例として、現在のアイテムから次の(または前の)アイテムを探すロジック
            // これは非常に簡略化された例であり、実際のアプリケーションではより複雑なロジックが必要
            QList<QGraphicsItem*> focusableItems = scene()->items(Qt::TabFocus); // Tabフォーカス可能なアイテムを取得
            int currentIndex = focusableItems.indexOf(currentFocusItem);

            if (currentIndex != -1) {
                int nextIndex = -1;
                if (next) {
                    nextIndex = (currentIndex + 1) % focusableItems.size();
                } else {
                    nextIndex = (currentIndex - 1 + focusableItems.size()) % focusableItems.size();
                }

                if (focusableItems[nextIndex]->setFocus(Qt::TabFocusReason)) {
                    return true; // フォーカス移動成功
                }
            }
        }

        // 基本クラスの動作を呼び出す(これにより、Qtの標準的なフォーカス移動も考慮される)
        return QGraphicsView::focusNextPrevChild(next);
    }
};


フォーカスが期待通りに移動しない(Tabキーが効かないなど)

原因

  • QGraphicsProxyWidgetの使用忘れ
    QWidgetQGraphicsScene内でフォーカス可能な要素として扱いたい場合、直接addWidget()で追加するのではなく、QGraphicsProxyWidgetを介して追加する必要があります。
  • カスタムのfocusNextPrevChild()の実装ミス
    この関数をオーバーライドしている場合、そのロジックに誤りがある可能性があります。例えば、誤ったアイテムにフォーカスを移そうとしている、falseを返している、または基底クラスの呼び出しを忘れているなど。
  • イベントの横取り/消費
    QGraphicsView またはシーン内の他のアイテムで、キーイベント(特にQt::Key_Tab)が処理され、focusNextPrevChild()が呼び出される前にevent->accept()されている場合。
  • フォーカスポリシーが適切でない
    ウィジェットのfocusPolicyQt::NoFocusに設定されている場合、フォーカスを受け取れません。
  • アイテム/ウィジェットがフォーカス可能に設定されていない
    QGraphicsItemQGraphicsProxyWidget にフォーカスを受け取るためのフラグが設定されていない場合、Tabキーでの移動対象になりません。

トラブルシューティング

  • QGraphicsProxyWidgetの使用
    QWidgetをシーンに追加する場合は、必ずQGraphicsProxyWidgetを使用しているか確認します。
  • イベントフィルタリング/オーバーライドの確認
    • QGraphicsView::keyPressEvent()QGraphicsScene::keyPressEvent()、または各QGraphicsItem::keyPressEvent()がTabキーイベントを処理しているか確認します。もし処理している場合、必要に応じてevent->ignore()を呼び出して、親ウィジェットやビュー、シーンにイベントを伝播させます。
    • カスタムのfocusNextPrevChild()を実装している場合は、ステップ実行でデバッグし、ロジックが期待通りに動作しているか確認します。特に、trueが正しく返されているか、適切なアイテムが選択されているかを確認します。
  • focusPolicyの確認
    関連するQWidgetsetFocusPolicy()が適切に設定されていることを確認します。Qt::TabFocusQt::StrongFocusが一般的です。
  • フォーカスフラグの確認
    • QGraphicsItemの場合: item->setFlags(QGraphicsItem::ItemIsFocusable); を呼び出す。
    • QGraphicsProxyWidgetの場合: ラップしているQWidgetsetFocusPolicy(Qt::TabFocus)setFocusPolicy(Qt::StrongFocus)を確認する。

フォーカスがループする/特定のアイテムで止まる

原因

  • setTabOrder()との競合
    QWidget::setTabOrder()QGraphicsViewのフォーカス管理が競合している場合があります。QGraphicsView内のアイテムのフォーカスは通常、QGraphicsSceneが管理します。
  • カスタムのタブ順序の誤り
    focusNextPrevChild()をオーバーライドしてカスタムのタブ順序を実装している場合、最後のアイテムから最初のアイテムへの移行、または特定のアイテム群内でのループが正しく処理されていない可能性があります。

トラブルシューティング

  • setTabOrder()の影響
    QGraphicsViewの外部でsetTabOrder()を使用している場合、それがビュー内のフォーカス順序に影響を与えていないか確認します。通常、シーン内のアイテムにはsetTabOrder()は直接適用されません。
  • デバッグ出力の活用
    focusNextPrevChild()のオーバーライド内でqDebug()を使用して、現在フォーカスを持っているアイテム、次にフォーカスを移そうとしているアイテム、next引数の値などを出力し、フォーカスの流れを追跡します。
  • カスタムロジックの再確認
    focusNextPrevChild()のカスタム実装で、次の(または前の)アイテムを見つけるロジックを慎重に確認します。インデックス計算やリストの巡回が正しいかデバッグします。

フォーカスフレームが表示されない

原因

  • ItemIsFocusableフラグが設定されていない
    フォーカス可能でないアイテムにはフォーカスフレームは表示されません。
  • カスタムペイントイベント
    QGraphicsItem::paint()をオーバーライドしている場合、フォーカスフレームの描画ロジックが欠けているか、誤って上書きされている可能性があります。QStyleOptionGraphicsItem::stateQStyle::State_HasFocusをチェックして、フォーカスされているときにフレームを描画する必要があります。
  • スタイルシートの競合
    カスタムスタイルシートやグローバルスタイルシートが、フォーカスフレームの描画を上書きしている可能性があります。

トラブルシューティング

  • 最小限のコードでテスト
    新しいQtプロジェクトを作成し、ごく簡単なQGraphicsItem(例: QGraphicsRectItem)にItemIsFocusableを設定して、デフォルトのフォーカスフレームが表示されるか確認します。これにより、問題がアプリケーション全体のスタイルや他の複雑なロジックに起因するものなのかを切り分けられます。
  • カスタムペイントの確認
    QGraphicsItem::paint()内で、フォーカスフレームを描画する際にQStyleOptionGraphicsItemstateをチェックし、QStyle::State_HasFocusが含まれる場合にQApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, painter, widget);のような方法でフォーカスフレームを描画していることを確認します。
  • ItemIsFocusableの確認
    アイテムがItemIsFocusableフラグを持っていることを確認します。

QGraphicsProxyWidget内のウィジェットがフォーカスを受け取らない

原因

  • ウィジェットのフォーカスポリシー
    ラップされたウィジェット自体のfocusPolicyQt::NoFocusになっている。
  • QGraphicsProxyWidgetがクリックイベントを消費している
    QGraphicsProxyWidgetは、内部のウィジェットにイベントを伝播させる役割がありますが、何らかの理由でクリックやキーイベントを消費してしまっている場合があります。
  • イベントフィルタリング
    必要に応じて、QGraphicsProxyWidgetに対してイベントフィルタを設定し、イベントの伝播状況をデバッグします。
  • ウィジェットのフォーカスポリシー
    proxyWidget->widget()->setFocusPolicy(Qt::TabFocus); のように、ラップされたウィジェットのフォーカスポリシーを明示的に設定してみてください。
  • QGraphicsProxyWidgetの確認
    QGraphicsProxyWidgetが正しくシーンに追加され、表示されているか確認します。
  • Qtバージョン固有の挙動
    まれにQtのバージョンによってフォーカス動作に違いがある場合があります。使用しているQtのバージョンに対応するドキュメントを参照してください。
  • scene()->focusItem()の確認
    任意の時点でscene()->focusItem()を呼び出し、現在フォーカスを持っているアイテムが期待通りであるかを確認します。
  • シンプルな例でテスト
    複雑なアプリケーションで問題が発生した場合、問題を再現できる最もシンプルなQtアプリケーションを作成し、原因を特定するための切り分けを行います。
  • Qtのドキュメントを再確認
    QGraphicsViewQGraphicsSceneQGraphicsItemQWidgetのフォーカス関連のドキュメントを改めて読み直し、基本的な要件を満たしているか確認します。
  • イベント監視
    QApplication::installEventFilter()QObject::eventFilter()を使用して、アプリケーション全体のイベントフローを監視し、Tabキーイベントがどこで処理されているかを特定します。
  • デバッグ出力の活用
    qDebug()を積極的に利用し、focusNextPrevChild()がいつ呼び出され、どのような値を返し、どのアイテムがフォーカスを受け取ろうとしているかを確認します。


基本的なフォーカス可能なアイテムの作成

まず、QGraphicsView::focusNextPrevChildが機能するためには、シーン内のアイテムがフォーカス可能である必要があります。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsProxyWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug> // デバッグ出力用

// カスタムのQGraphicsRectItem
class CustomFocusRectItem : public QGraphicsRectItem
{
public:
    CustomFocusRectItem(const QRectF& rect, QGraphicsItem* parent = nullptr)
        : QGraphicsRectItem(rect, parent)
    {
        // アイテムがフォーカス可能であることを設定
        setFlags(ItemIsFocusable | ItemIsSelectable);
        setBrush(Qt::lightGray); // デフォルトの背景色
    }

protected:
    // フォーカスインイベントをオーバーライドして色を変更
    void focusInEvent(QFocusEvent* event) override
    {
        setBrush(Qt::blue);
        qDebug() << "Item focused:" << rect();
        QGraphicsRectItem::focusInEvent(event); // 基底クラスの処理を呼び出す
    }

    // フォーカスアウトイベントをオーバーライドして色を元に戻す
    void focusOutEvent(QFocusEvent* event) override
    {
        setBrush(Qt::lightGray);
        qDebug() << "Item unfocused:" << rect();
        QGraphicsRectItem::focusOutEvent(event); // 基底クラスの処理を呼び出す
    }

    // ペイントイベントをオーバーライドしてフォーカスフレームを描画
    void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override
    {
        QGraphicsRectItem::paint(painter, option, widget); // 基本の描画

        // フォーカスがある場合にフォーカスフレームを描画
        if (option->state & QStyle::State_HasFocus) {
            QPen focusPen(Qt::darkBlue, 2, Qt::DotLine);
            painter->setPen(focusPen);
            painter->drawRect(rect()); // アイテムの矩形にフォーカスフレームを描画
        }
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    // 1. フォーカス可能なQGraphicsRectItem
    CustomFocusRectItem* rect1 = new CustomFocusRectItem(QRectF(100, 100, 80, 50));
    scene.addItem(rect1);

    CustomFocusRectItem* rect2 = new CustomFocusRectItem(QRectF(200, 100, 80, 50));
    scene.addItem(rect2);

    // 2. QGraphicsProxyWidget を介してQWidgetsを追加
    QLineEdit* lineEdit = new QLineEdit("テキスト入力");
    lineEdit->setFocusPolicy(Qt::StrongFocus); // フォーカス可能に設定
    QGraphicsProxyWidget* proxyLineEdit = scene.addWidget(lineEdit);
    proxyLineEdit->setPos(100, 200);

    QPushButton* button = new QPushButton("クリック");
    button->setFocusPolicy(Qt::StrongFocus); // フォーカス可能に設定
    QGraphicsProxyWidget* proxyButton = scene.addWidget(button);
    proxyButton->setPos(250, 200);

    QGraphicsView view(&scene);
    view.setWindowTitle("QGraphicsView Focus Example");
    view.resize(800, 600);
    view.show();

    // 最初のアイテムに初期フォーカスを設定
    rect1->setFocus(Qt::ActiveWindowFocusReason);

    return a.exec();
}

解説

  • アプリケーション実行後、Tabキーを押すと、設定されたアイテムやウィジェットの間をフォーカスが移動するのを確認できます。
  • setFocusPolicy(Qt::StrongFocus)は、QWidgetがキーボードによるフォーカスを受け取れるようにするために重要です。
  • main関数では、CustomFocusRectItemのインスタンスと、QLineEditQPushButtonQGraphicsProxyWidgetを介してシーンに追加しています。
  • paint()関数内でoption->state & QStyle::State_HasFocusをチェックし、フォーカスがある場合に点線の矩形(フォーカスフレーム)を描画しています。これにより、デフォルトのフォーカスフレームがないカスタムアイテムでも、フォーカスを視覚的に確認できます。
  • focusInEvent()focusOutEvent()をオーバーライドして、フォーカス状態に応じてアイテムの色を変更しています。これは、フォーカスがどこにあるかを視覚的に分かりやすくするためのものです。
  • コンストラクタでsetFlags(ItemIsFocusable | ItemIsSelectable);を呼び出し、このアイテムがフォーカス可能で選択可能であることを示しています。
  • CustomFocusRectItemクラスはQGraphicsRectItemを継承しています。

デフォルトのTab順序はアイテムの追加順序や位置に依存しますが、focusNextPrevChild()をオーバーライドすることで、より複雑なカスタム順序を実装できます。

ここでは、特定のアイテム群の間でフォーカスを巡回させる例を示します。

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsProxyWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
#include <QKeyEvent> // QKeyEvent を使用するため

// CustomFocusRectItem は上記と同じものを使用します

// カスタムのQGraphicsView
class MyCustomGraphicsView : public QGraphicsView
{
public:
    MyCustomGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        // フォーカス可能なアイテムのリストを初期化 (カスタムの順序)
        // ここでは、Tabキーで巡回させたいアイテムを定義します
        // この例では、CustomFocusRectItemのインスタンスを格納すると仮定します
        // 実際のアプリケーションでは、QGraphicsItem* を保持するリストを使用します
    }

    // フォーカス可能なカスタムアイテムのリストを保持するためのヘルパー関数
    void setFocusableCustomItems(const QList<QGraphicsItem*>& items)
    {
        m_focusableCustomItems = items;
    }

protected:
    bool focusNextPrevChild(bool next) override
    {
        qDebug() << "MyCustomGraphicsView::focusNextPrevChild called. next:" << next;

        // シーン内の現在のフォーカスアイテムを取得
        QGraphicsItem* currentFocusItem = scene()->focusItem();

        // もしカスタムアイテムのリストが空でなければ、カスタムフォーカスロジックを適用
        if (!m_focusableCustomItems.isEmpty()) {
            int currentIndex = m_focusableCustomItems.indexOf(currentFocusItem);

            if (currentIndex != -1) {
                // カスタムリスト内で次の/前のアイテムを計算
                int nextIndex;
                if (next) { // 次のアイテム (Tab)
                    nextIndex = (currentIndex + 1) % m_focusableCustomItems.size();
                } else { // 前のアイテム (Shift+Tab)
                    nextIndex = (currentIndex - 1 + m_focusableCustomItems.size()) % m_focusableCustomItems.size();
                }

                QGraphicsItem* targetItem = m_focusableCustomItems[nextIndex];

                // 次のアイテムにフォーカスを設定
                if (targetItem && targetItem->setFocus(Qt::TabFocusReason)) {
                    qDebug() << "Focus moved to custom item.";
                    return true; // フォーカス移動成功
                }
            }
        }
        // カスタムロジックでフォーカス移動が処理されなかった場合、
        // またはカスタムアイテムリストにないアイテムがフォーカスされている場合、
        // 基底クラスのデフォルト動作を呼び出す
        qDebug() << "Calling base class focusNextPrevChild.";
        return QGraphicsView::focusNextPrevChild(next);
    }

private:
    QList<QGraphicsItem*> m_focusableCustomItems;
};


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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    // カスタムフォーカス順序を適用したいアイテム
    CustomFocusRectItem* customRect1 = new CustomFocusRectItem(QRectF(100, 100, 80, 50));
    CustomFocusRectItem* customRect2 = new CustomFocusRectItem(QRectF(250, 100, 80, 50));
    CustomFocusRectItem* customRect3 = new CustomFocusRectItem(QRectF(175, 200, 80, 50)); // 真ん中下

    scene.addItem(customRect1);
    scene.addItem(customRect2);
    scene.addItem(customRect3);

    // MyCustomGraphicsViewをインスタンス化
    MyCustomGraphicsView view(&scene);
    view.setWindowTitle("Custom Focus Order Example");
    view.resize(800, 600);

    // カスタムフォーカス順序のリストを設定 (例えば、customRect1 -> customRect3 -> customRect2 -> customRect1...)
    QList<QGraphicsItem*> customOrderItems;
    customOrderItems << customRect1 << customRect3 << customRect2;
    view.setFocusableCustomItems(customOrderItems);

    view.show();

    // 初期フォーカスを設定
    customRect1->setFocus(Qt::ActiveWindowFocusReason);

    return a.exec();
}
  • カスタムロジックでフォーカスが移動しなかった場合(例: currentFocusItemがカスタムリストにない場合や、setFocus()が失敗した場合)、必ず QGraphicsView::focusNextPrevChild(next); を呼び出して、基底クラスのデフォルトのフォーカス処理にフォールバックさせます。これにより、シーン内の他の標準的なウィジェット(例: QLineEditなど)へのフォーカス移動も引き続き機能します。
  • フォーカス移動が成功した場合(setFocus()trueを返した場合)、trueを返してそれ以上の処理が不要であることを示します。
  • もし含まれていれば、リスト内でnext引数に基づいて次の(または前の)アイテムのインデックスを計算し、そのアイテムにsetFocus()を呼び出します。
  • オーバーライドされたfocusNextPrevChild()内で、まず現在のフォーカスアイテムがカスタムリストに含まれているかをチェックします。
  • m_focusableCustomItemsというQList<QGraphicsItem*>を使って、カスタムのフォーカス順序を定義するアイテムのリストを保持しています。setFocusableCustomItems()ヘルパー関数でこのリストを設定できます。
  • MyCustomGraphicsViewクラスはQGraphicsViewを継承し、focusNextPrevChild(bool next)をオーバーライドしています。
  • QGraphicsScene::items(Qt::TabFocus)
    シーン内のTabフォーカス可能な全てのアイテムをリストとして取得することもできます。カスタム順序を厳密に制御したい場合は、このリストをフィルターしたり、独自に構築したりすることが可能です。
  • 複雑なレイアウト
    複雑なレイアウトや多数のアイテムがある場合、カスタムのフォーカス順序のロジックはより複雑になる可能性があります。アイテムの親子関係、Z-Value、または幾何学的な位置に基づいて順序を決定することも考えられます。
  • qgraphicsitem_cast<T>()
    QGraphicsItemのポインタを特定のサブクラスのポインタに安全にキャストするために使用します。これにより、ダウンキャスト時の型安全性が確保されます。
  • scene()->focusItem()
    現在シーンでフォーカスを持っているQGraphicsItemを取得するために使います。
  • Qt::TabFocusReason
    setFocus()に渡すQt::FocusReasonは、なぜフォーカスが移動したのかを示す情報です。Tabキーによる移動の場合はQt::TabFocusReasonが適切です。


QGraphicsScene::focusItem()とQGraphicsItem::setFocus()を直接操作する

これは最も直接的な方法で、キーイベントを捕捉し、手動で次のフォーカスアイテムを設定します。focusNextPrevChild()の内部で行われる処理を、より低いレベルで制御するイメージです。

アプローチ

  1. QGraphicsView または QGraphicsScenekeyPressEvent() をオーバーライドします。
  2. イベントがTabキー(またはShift+Tab)であるかをチェックします。
  3. 現在のフォーカスアイテムを取得し、カスタムのロジックに基づいて次のフォーカスアイテムを決定します。
  4. 決定したアイテムに対して setFocus(Qt::TabFocusReason) を呼び出します。
  5. イベントをevent->accept()して、それ以上伝播しないようにします。


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

// CustomFocusRectItem は前回の例と同じものを使用します
class CustomFocusRectItem : public QGraphicsRectItem
{
public:
    CustomFocusRectItem(const QRectF& rect, QGraphicsItem* parent = nullptr)
        : QGraphicsRectItem(rect, parent)
    {
        setFlags(ItemIsFocusable | ItemIsSelectable);
        setBrush(Qt::lightGray);
    }

protected:
    void focusInEvent(QFocusEvent* event) override { setBrush(Qt::blue); qDebug() << "Item focused:" << rect(); QGraphicsRectItem::focusInEvent(event); }
    void focusOutEvent(QFocusEvent* event) override { setBrush(Qt::lightGray); qDebug() << "Item unfocused:" << rect(); QGraphicsRectItem::focusOutEvent(event); }
    void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override {
        QGraphicsRectItem::paint(painter, option, widget);
        if (option->state & QStyle::State_HasFocus) {
            QPen focusPen(Qt::darkBlue, 2, Qt::DotLine);
            painter->setPen(focusPen);
            painter->drawRect(rect());
        }
    }
};

// QGraphicsViewのkeyPressEventをオーバーライド
class ManualFocusView : public QGraphicsView
{
public:
    ManualFocusView(QGraphicsScene* scene, QWidget* parent = nullptr)
        : QGraphicsView(scene, parent)
    {}

protected:
    void keyPressEvent(QKeyEvent* event) override
    {
        if (event->key() == Qt::Key_Tab) {
            QList<QGraphicsItem*> focusableItems;
            // シーン内のすべてのTabフォーカス可能なアイテムを取得
            // 実際には、カスタムの順序を持つリストを用意する方が良い
            for (QGraphicsItem* item : scene()->items(Qt::TabFocus)) {
                if (item->flags() & QGraphicsItem::ItemIsFocusable) {
                    focusableItems << item;
                }
            }

            if (focusableItems.isEmpty()) {
                event->ignore(); // 処理するアイテムがなければイベントを無視
                return;
            }

            // アイテムのソート (例: X座標に基づいて)
            std::sort(focusableItems.begin(), focusableItems.end(),
                      [](QGraphicsItem* a, QGraphicsItem* b) {
                          return a->pos().x() < b->pos().x();
                      });

            QGraphicsItem* currentFocusItem = scene()->focusItem();
            int currentIndex = -1;
            if (currentFocusItem) {
                currentIndex = focusableItems.indexOf(currentFocusItem);
            }

            int nextIndex = -1;
            if (event->modifiers() & Qt::ShiftModifier) { // Shift+Tab (前へ)
                if (currentIndex <= 0) {
                    nextIndex = focusableItems.size() - 1; // 最後のアイテムへ
                } else {
                    nextIndex = currentIndex - 1;
                }
            } else { // Tab (次へ)
                if (currentIndex >= focusableItems.size() - 1) {
                    nextIndex = 0; // 最初のアイテムへ
                } else {
                    nextIndex = currentIndex + 1;
                }
            }

            if (nextIndex != -1 && focusableItems[nextIndex]) {
                focusableItems[nextIndex]->setFocus(Qt::TabFocusReason);
                qDebug() << "Manual focus moved to:" << focusableItems[nextIndex]->pos();
                event->accept(); // イベントを消費
                return;
            }
        }
        // 他のキーイベントは基底クラスに渡す
        QGraphicsView::keyPressEvent(event);
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    CustomFocusRectItem* rect1 = new CustomFocusRectItem(QRectF(100, 100, 80, 50));
    CustomFocusRectItem* rect2 = new CustomFocusRectItem(QRectF(300, 100, 80, 50));
    CustomFocusRectItem* rect3 = new CustomFocusRectItem(QRectF(200, 200, 80, 50)); // 後から追加だがX座標は間

    scene.addItem(rect1);
    scene.addItem(rect2);
    scene.addItem(rect3);

    QLineEdit* lineEdit = new QLineEdit("QLineEdit");
    lineEdit->setFocusPolicy(Qt::StrongFocus);
    QGraphicsProxyWidget* proxyLineEdit = scene.addWidget(lineEdit);
    proxyLineEdit->setPos(150, 300);

    QPushButton* button = new QPushButton("QPushButton");
    button->setFocusPolicy(Qt::StrongFocus);
    QGraphicsProxyWidget* proxyButton = scene.addWidget(button);
    proxyButton->setPos(350, 300);

    ManualFocusView view(&scene);
    view.setWindowTitle("Manual Focus Management Example");
    view.resize(800, 600);
    view.show();

    // 初期フォーカス
    rect1->setFocus(Qt::ActiveWindowFocusReason);

    return a.exec();
}

利点

  • 他のキーイベントとの連携
    Tabキー以外のキー(例: 矢印キー)によるフォーカス移動も、この方法で実装できます。
  • 完全な制御
    フォーカス移動のロジックを完全に制御できます。特定の条件に基づいてフォーカスをスキップしたり、複雑な順序を実装したりするのに適しています。

欠点

  • 堅牢性の低下
    Qtの内部的なフォーカス管理の変更に対応しにくくなる可能性があります。
  • 手作業の増加
    デフォルトのfocusNextPrevChild()が提供する便利な自動処理が失われるため、すべてのフォーカスロジックを自分で記述する必要があります。

QGraphicsScene::event()またはQGraphicsItem::sceneEvent()をオーバーライドする

QGraphicsView::keyPressEvent()よりもさらに低レベルのイベント処理層でTabキーイベントを捕捉し、フォーカスを管理する方法です。

アプローチ

  1. QGraphicsScene を継承し、event() メソッドをオーバーライドします。
  2. 渡されてくるQEventがキーイベントであり、かつTabキーであるかをチェックします。
  3. 上記と同様に、カスタムロジックでフォーカスを移動させます。
  4. イベントを処理したらtrueを返し、処理しなければ基底クラスのevent()を呼び出します。

例 (概念)

// MyGraphicsScene.h
#ifndef MYGRAPHICSSCENE_H
#define MYGRAPHICSSCENE_H

#include <QGraphicsScene>
#include <QKeyEvent>
#include <QDebug>
#include <algorithm> // std::sort のため

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

protected:
    bool event(QEvent* event) override
    {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
            if (keyEvent->key() == Qt::Key_Tab) {
                QList<QGraphicsItem*> focusableItems = items(Qt::TabFocus); // シーン内のTabフォーカス可能なアイテム

                if (focusableItems.isEmpty()) {
                    return QGraphicsScene::event(event); // アイテムがなければデフォルト処理
                }

                // アイテムのソート (例: Y座標優先でX座標)
                std::sort(focusableItems.begin(), focusableItems.end(),
                          [](QGraphicsItem* a, QGraphicsItem* b) {
                              if (a->pos().y() != b->pos().y()) {
                                  return a->pos().y() < b->pos().y();
                              }
                              return a->pos().x() < b->pos().x();
                          });

                QGraphicsItem* currentFocusItem = focusItem();
                int currentIndex = -1;
                if (currentFocusItem) {
                    currentIndex = focusableItems.indexOf(currentFocusItem);
                }

                int nextIndex;
                if (keyEvent->modifiers() & Qt::ShiftModifier) {
                    nextIndex = (currentIndex <= 0) ? focusableItems.size() - 1 : currentIndex - 1;
                } else {
                    nextIndex = (currentIndex >= focusableItems.size() - 1) ? 0 : currentIndex + 1;
                }

                if (focusableItems[nextIndex]) {
                    focusableItems[nextIndex]->setFocus(Qt::TabFocusReason);
                    qDebug() << "Scene event: Focus moved to:" << focusableItems[nextIndex]->pos();
                    keyEvent->accept(); // イベントを消費
                    return true;
                }
            }
        }
        return QGraphicsScene::event(event); // 他のイベントは基底クラスに渡す
    }
};

#endif // MYGRAPHICSSCENE_H

// main.cpp (上記と同じ)
// MyCustomGraphicsViewの代わりにMyGraphicsSceneを使用
// MyGraphicsScene scene;
// MyGraphicsView view(&scene);

利点

  • イベントパイプラインのより早い段階
    QGraphicsViewkeyPressEventよりも早い段階でイベントを捕捉できます。
  • 集中管理
    シーンレベルでフォーカス管理を集中させることができます。

欠点

  • focusNextPrevChild()と比べると、ややイベント駆動の低レベルなアプローチになります。

QObject::installEventFilter()を使用する

特定のオブジェクトにイベントフィルタをインストールし、そのオブジェクトに送られるイベントを監視・処理する方法です。これにより、既存のクラスを継承せずにフォーカスイベントを操作できます。

アプローチ

  1. QObjectを継承したイベントフィルタクラスを作成し、eventFilter()メソッドをオーバーライドします。
  2. eventFilter()内で、ターゲットオブジェクト(例: QGraphicsViewのviewport)とイベントタイプ(例: QEvent::KeyPress)をチェックします。
  3. Tabキーイベントを捕捉し、カスタムロジックでフォーカスを移動させます。
  4. イベントを処理した場合はtrueを返し、処理しなかった場合はfalseを返します。
  5. QGraphicsViewのviewportにこのイベントフィルタをインストールします。

例 (概念)

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QLineEdit>
#include <QKeyEvent>
#include <QDebug>
#include <algorithm> // std::sort のため

// CustomFocusRectItem は前回の例と同じものを使用します

class FocusEventFilter : public QObject
{
    Q_OBJECT
public:
    FocusEventFilter(QGraphicsView* view, QObject* parent = nullptr)
        : QObject(parent), m_view(view) {}

protected:
    bool eventFilter(QObject* watched, QEvent* event) override
    {
        // 監視対象がビューのビューポートであり、キー押下イベントであるかを確認
        if (watched == m_view->viewport() && event->type() == QEvent::KeyPress) {
            QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);

            if (keyEvent->key() == Qt::Key_Tab) {
                QList<QGraphicsItem*> focusableItems = m_view->scene()->items(Qt::TabFocus);
                // ここでアイテムリストをソートするなど、カスタムロジックを適用
                std::sort(focusableItems.begin(), focusableItems.end(),
                          [](QGraphicsItem* a, QGraphicsItem* b) {
                              return a->pos().y() < b->pos().y();
                          });

                QGraphicsItem* currentFocusItem = m_view->scene()->focusItem();
                int currentIndex = -1;
                if (currentFocusItem) {
                    currentIndex = focusableItems.indexOf(currentFocusItem);
                }

                int nextIndex;
                if (keyEvent->modifiers() & Qt::ShiftModifier) {
                    nextIndex = (currentIndex <= 0) ? focusableItems.size() - 1 : currentIndex - 1;
                } else {
                    nextIndex = (currentIndex >= focusableItems.size() - 1) ? 0 : currentIndex + 1;
                }

                if (nextIndex != -1 && focusableItems[nextIndex]) {
                    focusableItems[nextIndex]->setFocus(Qt::TabFocusReason);
                    qDebug() << "Event Filter: Focus moved to:" << focusableItems[nextIndex]->pos();
                    return true; // イベントを処理し、それ以上伝播させない
                }
            }
        }
        return QObject::eventFilter(watched, event); // 他のイベントは基底クラスのフィルタを呼び出す
    }

private:
    QGraphicsView* m_view;
};

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

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    CustomFocusRectItem* rect1 = new CustomFocusRectItem(QRectF(100, 100, 80, 50));
    CustomFocusRectItem* rect2 = new CustomFocusRectItem(QRectF(300, 100, 80, 50));
    scene.addItem(rect1);
    scene.addItem(rect2);

    QGraphicsView view(&scene);
    view.setWindowTitle("Event Filter Focus Example");
    view.resize(800, 600);

    // イベントフィルタをビューポートにインストール
    FocusEventFilter* filter = new FocusEventFilter(&view, &view); // viewを親として設定
    view.viewport()->installEventFilter(filter);

    view.show();
    rect1->setFocus(Qt::ActiveWindowFocusReason);

    return a.exec();
}

利点

  • 分離されたロジック
    フォーカス管理ロジックを別のクラスにカプセル化できます。
  • 継承不要
    既存のクラスを継承する必要がないため、柔軟性が高まります。特に、複数の異なるビューやウィジェットに同じフォーカスロジックを適用したい場合に便利です。
  • eventFilter()内でオブジェクトの型キャストが必要になる場合がある。
  • イベントの伝播順序を理解する必要がある。
  • QObject::installEventFilter()
    • 既存のQGraphicsViewクラスを継承できない場合、または複数の異なるビューで同じフォーカスロジックを共有したい場合に便利です。
    • モジュール性と再利用性を高めるのに役立ちます。
  • event() (sceneEvent()) のオーバーライド
    • シーン内の全てのアイテムに対するフォーカスイベントを一元的に管理したい場合に適しています。
    • QGraphicsSceneレベルでTab順序を完全に再定義したい場合に考慮します。
  • keyPressEvent()の直接オーバーライド
    • focusNextPrevChild()の挙動が複雑で、キーイベントレベルでより詳細な制御が必要な場合に考慮します。
    • Tabキーだけでなく、矢印キーなど他のキーによるフォーカス移動も統一的に扱いたい場合に有効です。
  • focusNextPrevChild()のオーバーライド
    • 最も推奨されるアプローチです。Qtのグラフィックスビューフレームワークに最も自然に統合され、意図された設計パターンです。
    • ビュー固有のカスタムフォーカス順序を実装する場合に最適です。