Qt 初心者向け: focusNextPrevChild() を使ったフォーカス制御入門

2025-05-27

引数として bool next を取ります。

  • nextfalse の場合、フォーカスはタブチェーンにおける 前の ウィジェットに移動します。
  • nexttrue の場合、フォーカスはタブチェーンにおける 次の ウィジェットに移動します。

この関数は、フォーカス移動が成功したかどうかを示す bool 型の値を返します。

  • フォーカス移動が失敗した場合(例えば、次のまたは前のウィジェットが存在しない場合や、フォーカスを受け取ることができない状態の場合)、false を返します。
  • フォーカス移動が成功した場合(次のまたは前のウィジェットが存在し、フォーカスを受け取ることができた場合)、true を返します。

具体例

例えば、ウィンドウ内に複数の入力フィールド(QLineEdit など)やボタン(QPushButton など)が配置されているとします。これらのウィジェットは、通常、作成された順序やレイアウトマネージャによって、暗黙的なタブ順序が決定されます。

ユーザーがキーボードの Tab キーを押すと、Qtはアクティブなウィジェットに対して focusNextPrevChild(true) を内部的に呼び出し、フォーカスをタブ順序における次のウィジェットに移動させます。Shift + Tab キーを押した場合は、focusNextPrevChild(false) が呼び出され、前のウィジェットにフォーカスが移動します。

  • アクセシビリティ
    スクリーンリーダーなどの支援技術を使用しているユーザーにとって、論理的なフォーカス移動を提供するために重要です。
  • キーイベント処理
    ウィジェット内で特定のキーイベント(例えば、Enter キーを押したときに次の入力フィールドにフォーカスを移動させるなど)を処理する際に、この関数を利用できます。
  • カスタムフォーカス移動
    デフォルトのタブ順序を変更したり、特定の条件に基づいてフォーカスを移動させたりする場合に、この関数を明示的に呼び出すことができます。


フォーカスが意図したウィジェットに移動しない

  • フォーカスチェインのループ
    • タブ順序の設定によっては、意図せずフォーカスがループしてしまうことがあります。特に setTabOrder() を複雑に設定している場合に起こりやすいです。論理的なタブ順序になるように見直してください。
  • カスタムイベントフィルタ
    • アプリケーションや特定のウィジェットにカスタムイベントフィルタ (QObject::installEventFilter()) を設定している場合、そのフィルタがフォーカスイベントを横取りしている可能性があります。イベントフィルタの処理内容を確認してください。
  • 親ウィジェットの影響
    • 親ウィジェットの状態がフォーカス移動に影響を与えることがあります。例えば、親ウィジェットが無効になっている場合、その子ウィジェットもフォーカスを受け取ることができません。
  • フォーカスを受け取れないウィジェット
    • 移動先のウィジェットがフォーカスを受け取ることができる状態であるか確認してください。例えば、QWidget::setFocusPolicy()Qt::NoFocus に設定されている場合、そのウィジェットはキーボードフォーカスを受け取ることができません。Qt::TabFocusQt::ClickFocusQt::StrongFocus などの適切なポリシーが設定されているか確認してください。
    • ウィジェットが非表示 (QWidget::setVisible(false)) や無効 (QWidget::setEnabled(false)) になっている場合も、フォーカスを受け取れません。
  • タブ順序の確認
    • ウィジェットのタブ順序が意図した通りに設定されているか確認してください。タブ順序は、親ウィジェット内でのウィジェットの作成順序や、明示的に setTabOrder() 関数を使って設定できます。
    • Qt Designer を使用している場合は、タブ順序エディタで確認・編集できます。

focusNextPrevChild() が false を返す

  • 移動先のウィジェットがフォーカスを受け取れない
    • 上記の「フォーカスを受け取れないウィジェット」の状態にある場合、focusNextPrevChild()false を返します。
  • 次の/前のウィジェットが存在しない
    • 現在のウィジェットがタブ順序の最初または最後にある場合、next = false または next = true でそれぞれ移動しようとしても、次の/前のウィジェットが存在しないため false が返ります。

特定の状況でのみフォーカス移動が失敗する

  • 非同期処理
    • フォーカス移動の処理が非同期に行われている場合、予期しないタイミングでフォーカスが移動したり、移動に失敗したりすることがあります。
  • シグナルとスロットの接続
    • フォーカス関連のシグナル(例えば、focusChanged())に接続されたスロット内で、さらにフォーカスを移動させる処理を行っている場合、意図しない動作を引き起こす可能性があります。無限ループや競合状態に注意してください。
  • ウィジェットの状態
    • 特定のウィジェットの状態(例えば、編集中、エラー状態など)にある場合に、フォーカス移動を禁止するようなロジックが実装されていないか確認してください。
  • ドキュメントの参照
    Qt の公式ドキュメントで QWidget::focusNextPrevChild() および関連する関数(setTabOrder()setFocusPolicy() など)の説明を再度確認します。
  • イベントリスナ
    event() 関数をオーバーライドして、フォーカス関連のイベント (QEvent::FocusInQEvent::FocusOut) を監視し、どのウィジェットがフォーカスを得たり失ったりしているかを確認します。
  • シンプルなテスト
    問題を切り分けるために、最小限のウィジェットで構成されたシンプルなテストアプリケーションを作成し、そこで focusNextPrevChild() の動作を確認します。
  • タブ順序の可視化
    Qt Designer のタブ順序エディタを利用して、GUI 上でタブ順序を確認します。
  • デバッグ出力
    qDebug() を使用して、focusNextPrevChild() の呼び出し前後でアクティブなフォージェット (QApplication::focusWidget()) を出力し、フォーカスがどのように変化しているかを確認します。


例1: Enterキーで次の入力フィールドにフォーカスを移動する

この例では、QLineEdit ウィジェット内で Enter キーが押されたときに、タブ順序における次のウィジェットにフォーカスを移動させます。

#include <QtWidgets>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit1 = new QLineEdit(this);
        lineEdit2 = new QLineEdit(this);
        lineEdit3 = new QLineEdit(this);
        button = new QPushButton("送信", this);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(lineEdit1);
        layout->addWidget(lineEdit2);
        layout->addWidget(lineEdit3);
        layout->addWidget(button);

        // タブ順序を明示的に設定 (addWidget の順序が意図通りであれば不要)
        setTabOrder(lineEdit1, lineEdit2);
        setTabOrder(lineEdit2, lineEdit3);
        setTabOrder(lineEdit3, button);

        lineEdit1->installEventFilter(this);
        lineEdit2->installEventFilter(this);
        lineEdit3->installEventFilter(this);
    }

protected:
    bool eventFilter(QObject *watched, QEvent *event) override {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
                // フォーカスを持っているウィジェットを取得
                QWidget *currentFocus = QApplication::focusWidget();
                if (currentFocus == lineEdit1 || currentFocus == lineEdit2 || currentFocus == lineEdit3) {
                    // 次のウィジェットにフォーカスを移動
                    focusNextPrevChild(true);
                    return true; // イベントを処理済みとして伝播を防ぐ
                }
            }
        }
        return QWidget::eventFilter(watched, event);
    }

private:
    QLineEdit *lineEdit1;
    QLineEdit *lineEdit2;
    QLineEdit *lineEdit3;
    QPushButton *button;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

解説

  1. MainWindow クラスは、3つの QLineEdit と 1つの QPushButton を持つシンプルなウィンドウです。
  2. setTabOrder() を使用して、ウィジェット間の明示的なタブ順序を設定しています(addWidget の順序が意図通りであれば省略可能です)。
  3. QLineEditinstallEventFilter(this) を呼び出し、この MainWindow オブジェクトでこれらのウィジェットのイベントを監視できるようにしています。
  4. eventFilter() 関数をオーバーライドし、監視対象のウィジェットで KeyPress イベントが発生したときに処理を行います。
  5. 押されたキーが Enter キーまたは Return キーであるかどうかを確認します。
  6. フォーカスを持っているウィジェットが lineEdit1lineEdit2、または lineEdit3 のいずれかである場合、focusNextPrevChild(true) を呼び出して、タブ順序における次のウィジェットにフォーカスを移動させます。
  7. return true; を返すことで、このキーイベントがさらに親ウィジェットに伝播するのを防ぎます。

例2: 特定の条件で前のウィジェットにフォーカスを移動する

この例では、ある QLineEdit のテキストが空になったときに、タブ順序における前のウィジェットにフォーカスを移動させます。

#include <QtWidgets>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit1 = new QLineEdit("初期テキスト", this);
        lineEdit2 = new QLineEdit(this);
        button = new QPushButton("送信", this);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(lineEdit1);
        layout->addWidget(lineEdit2);
        layout->addWidget(button);

        setTabOrder(lineEdit1, lineEdit2);
        setTabOrder(lineEdit2, button);
        setTabOrder(button, lineEdit1); // ループするように設定

        connect(lineEdit2, &QLineEdit::textChanged, this, &MainWindow::checkLineEdit2);
    }

private slots:
    void checkLineEdit2(const QString &text) {
        if (text.isEmpty()) {
            // 前のウィジェットにフォーカスを移動
            focusNextPrevChild(false);
        }
    }

private:
    QLineEdit *lineEdit1;
    QLineEdit *lineEdit2;
    QPushButton *button;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

解説

  1. MainWindow クラスは、2つの QLineEdit と 1つの QPushButton を持ちます。
  2. setTabOrder() で明示的なタブ順序を設定し、最後のボタンから最初の lineEdit1 へとループするようにしています。
  3. lineEdit2textChanged シグナルを、checkLineEdit2() スロットに接続しています。
  4. checkLineEdit2() スロットでは、lineEdit2 のテキストが空になった場合に focusNextPrevChild(false) を呼び出し、タブ順序における前のウィジェット(この場合は lineEdit1)にフォーカスを移動させます。

例3: カスタムのフォーカス移動ロジックを実装する

focusNextPrevChild() をオーバーライドすることで、ウィジェットのサブクラス内で独自のフォーカス移動ロジックを実装できます。

#include <QtWidgets>

class CustomWidget : public QWidget {
public:
    CustomWidget(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
    bool focusNextPrevChild(bool next) override {
        // カスタムのフォーカス移動ロジックをここに実装
        // 例えば、特定の条件を満たすウィジェットにのみフォーカスを移動させるなど

        // デフォルトの動作に戻す場合は、親クラスの関数を呼び出す
        // return QWidget::focusNextPrevChild(next);

        // 簡単な例として、常に次の子ウィジェットにフォーカスを移動させる
        QWidget *currentFocus = focusProxy() ? focusProxy() : this;
        QList<QWidget *> childrenList = findChildren<QWidget *>();
        int currentIndex = childrenList.indexOf(currentFocus);
        int nextIndex = next ? currentIndex + 1 : currentIndex - 1;

        if (nextIndex >= 0 && nextIndex < childrenList.size()) {
            childrenList.at(nextIndex)->setFocus();
            return true;
        } else {
            return false;
        }
    }
};

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        customWidget = new CustomWidget(this);
        lineEdit1 = new QLineEdit(customWidget);
        lineEdit2 = new QLineEdit(customWidget);
        button = new QPushButton(customWidget);

        QVBoxLayout *layout = new QVBoxLayout(customWidget);
        layout->addWidget(lineEdit1);
        layout->addWidget(lineEdit2);
        layout->addWidget(button);

        QVBoxLayout *mainLayout = new QVBoxLayout(this);
        mainLayout->addWidget(customWidget);
    }

private:
    CustomWidget *customWidget;
    QLineEdit *lineEdit1;
    QLineEdit *lineEdit2;
    QPushButton *button;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
  1. CustomWidget クラスは QWidget を継承し、focusNextPrevChild() 関数をオーバーライドしています。
  2. オーバーライドされた focusNextPrevChild() 関数内で、独自のフォーカス移動ロジックを実装できます。上記の例では、単純に子ウィジェットのリストを取得し、next の値に基づいて次のまたは前のウィジェットにフォーカスを設定しています。
  3. デフォルトの動作に戻したい場合は、return QWidget::focusNextPrevChild(next); を呼び出すことができます。
  4. MainWindow では、この CustomWidget を含み、その中にいくつかのウィジェットを配置しています。CustomWidget がフォーカスを受け取ると、その focusNextPrevChild() の実装が使用されます。


QWidget::setFocus() を直接使用する

最も直接的な方法は、特定のウィジェットに対して setFocus() 関数を呼び出すことです。これにより、タブ順序に関係なく、指定したウィジェットに強制的にキーボードフォーカスを移動させることができます。

#include <QtWidgets>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit1 = new QLineEdit(this);
        lineEdit2 = new QLineEdit(this);
        button = new QPushButton("フォーカス移動", this);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(lineEdit1);
        layout->addWidget(lineEdit2);
        layout->addWidget(button);

        connect(button, &QPushButton::clicked, this, &MainWindow::moveFocusToLineEdit2);
    }

private slots:
    void moveFocusToLineEdit2() {
        lineEdit2->setFocus(); // 直接 lineEdit2 にフォーカスを設定
    }

private:
    QLineEdit *lineEdit1;
    QLineEdit *lineEdit2;
    QPushButton *button;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

利点

  • タブ順序に依存しないフォーカス制御が可能。
  • 特定のウィジェットに直接フォーカスを移動させられるため、柔軟性が高い。

欠点

  • タブナビゲーションの標準的な動作(Tab/Shift+Tab キー)を自分で実装する必要がある場合がある。
  • フォーカス移動のロジックを自分で管理する必要がある。

QTabWidget や QStackedWidget などのコンテナウィジェットを使用する

これらのコンテナウィジェットは、内部でフォーカス管理をある程度自動的に行ってくれます。例えば、QTabWidget では、タブを切り替えることで内部のウィジェットにフォーカスが移動します。QStackedWidget でも、表示されるウィジェットが変更される際にフォーカスが適切に設定されます。

#include <QtWidgets>

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        tabWidget = new QTabWidget(this);

        QWidget *tab1 = new QWidget();
        QLineEdit *lineEdit1_1 = new QLineEdit(tab1);
        QLineEdit *lineEdit1_2 = new QLineEdit(tab1);
        QVBoxLayout *layout1 = new QVBoxLayout(tab1);
        layout1->addWidget(lineEdit1_1);
        layout1->addWidget(lineEdit1_2);

        QWidget *tab2 = new QWidget();
        QPushButton *button2_1 = new QPushButton("ボタン1", tab2);
        QPushButton *button2_2 = new QPushButton("ボタン2", tab2);
        QVBoxLayout *layout2 = new QVBoxLayout(tab2);
        layout2->addWidget(button2_1);
        layout2->addWidget(button2_2);

        tabWidget->addTab(tab1, "タブ 1");
        tabWidget->addTab(tab2, "タブ 2");

        QVBoxLayout *mainLayout = new QVBoxLayout(this);
        mainLayout->addWidget(tabWidget);
    }

private:
    QTabWidget *tabWidget;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

利点

  • 視覚的にも構造化されたユーザーインターフェースを提供できる。
  • タブやページの切り替えに伴うフォーカス管理が自動化される。

欠点

  • 細かいフォーカス制御はコンテナウィジェットの機能に依存する。
  • レイアウトが特定の構造に限定される。

イベントフィルタ (QObject::installEventFilter()) を使用する

focusNextPrevChild() の例でも示しましたが、イベントフィルタを使用することで、特定のウィジェットで発生するキーイベントを監視し、独自のフォーカス移動ロジックを実装できます。これにより、Tab キーや Shift+Tab キーのデフォルトの動作をカスタマイズしたり、他のキー操作でフォーカスを移動させたりすることが可能です。

// (上記の focusNextPrevChild() の例1を参照)

利点

  • 特定のウィジェットの動作に影響を与えずにフォーカス制御を実装できる。
  • 柔軟なキーイベント処理による高度なフォーカス制御が可能。

欠点

  • 複雑なロジックの場合、管理が難しくなる可能性がある。
  • イベント処理のロジックを自分で実装する必要がある。

フォーカスプロキシ (QWidget::setFocusProxy()) を使用する

あるウィジェットへのフォーカスを別のウィジェットに委譲することができます。これは、複合ウィジェットを作成する際に、内部の特定のウィジェットにフォーカスが当たるようにしたい場合に便利です。

#include <QtWidgets>

class CompoundWidget : public QWidget {
public:
    CompoundWidget(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit = new QLineEdit(this);
        label = new QLabel("入力:", this);

        QHBoxLayout *layout = new QHBoxLayout(this);
        layout->addWidget(label);
        layout->addWidget(lineEdit);

        // CompoundWidget にフォーカスが当たった際に lineEdit にフォーカスを移す
        setFocusProxy(lineEdit);
        setFocusPolicy(Qt::StrongFocus); // CompoundWidget 自体もフォーカスを受け取れるように設定
    }

private:
    QLineEdit *lineEdit;
    QLabel *label;
};

class MainWindow : public QWidget {
public:
    MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        compoundWidget1 = new CompoundWidget(this);
        compoundWidget2 = new CompoundWidget(this);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(compoundWidget1);
        layout->addWidget(compoundWidget2);
    }

private:
    CompoundWidget *compoundWidget1;
    CompoundWidget *compoundWidget2;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

利点

  • 論理的なフォーカス対象を明確にできる。
  • 複合ウィジェットのフォーカス管理を簡素化できる。

欠点

  • 単純なウィジェットの集まりでない場合に有効。
  • フォーカス委譲の概念を理解する必要がある。

カスタムフォーカスルーティングロジックの実装

より複雑なアプリケーションでは、独自のフォーカスルーティングロジックを実装する必要がある場合があります。これには、アプリケーションの状態やユーザーの操作に基づいて、次にどのウィジェットにフォーカスを移動するかを決定するアルゴリズムが含まれることがあります。この場合、上記の setFocus() やイベントフィルタなどの手法を組み合わせて実現することが一般的です。