日本語解説: Qt QWidget::focusPolicyとは?設定方法からトラブルシューティングまで

2025-05-16

このプロパティは、Qt::FocusPolicyという列挙型(enum)の値を取ります。主な値とその意味は以下の通りです。

  • Qt::StrongFocus: これは最も一般的な設定で、このウィジェットはTabキーでの移動とマウスのクリックの両方でフォーカスを受け取ることができます。多くのインタラクティブなウィジェット(LineEdit、PushButton、ListWidgetなど)のデフォルトのポリシーです。

  • Qt::ClickFocus: このウィジェットはマウスでクリックされたときにのみフォーカスを受け取ります。Tabキーではフォーカス移動の対象となりません。あまり一般的ではありませんが、特定の操作で一時的にキーボード入力を受け付けたい場合などに使用されることがあります。

  • Qt::TabFocus: このウィジェットはTabキーを押すことでフォーカスを受け取ることができます。ユーザーがTabキーを押していくと、このポリシーが設定されたウィジェットに順番にフォーカスが移動します。一般的な入力フィールドやボタンなど、キーボード操作の対象となるウィジェットによく設定されます。

  • Qt::NoFocus: このウィジェットはキーボードフォーカスを受け取りません。マウス操作には反応しますが、Tabキーでの移動やショートカットキーなどによるフォーカス移動の対象外となります。主に、ラベルや装飾的なウィジェットなど、ユーザーが直接キー入力しないものに設定されます。

focusPolicyの設定方法

QWidgetオブジェクトのsetFocusPolicy()メソッドを使って、プログラムの中でこのポリシーを設定できます。例えば、あるQLineEditウィジェットにTabキーでフォーカスできるように設定するには、次のように記述します。

QLineEdit *lineEdit = new QLineEdit(this);
lineEdit->setFocusPolicy(Qt::TabFocus);

また、Qt DesignerなどのGUIデザインツールを使用している場合は、プロパティエディタからGUI上でfocusPolicyを設定することも可能です。

focusPolicyの重要性

適切なfocusPolicyを設定することは、アプリケーションの使いやすさを向上させる上で非常に重要です。

  • ユーザーエクスペリエンス: ユーザーが意図しないウィジェットにフォーカスが移動したり、必要なウィジェットにフォーカスを移動できなかったりすると、操作性が悪くなります。適切なfocusPolicyを設定することで、直感的でスムーズな操作体験を提供できます。
  • キーボードアクセシビリティ: Qt::TabFocusQt::StrongFocusを適切に設定することで、マウスを使用できないユーザーでもキーボードだけでアプリケーションを操作できるようになります。これはアクセシビリティの観点から非常に重要です。


意図しないウィジェットへのフォーカス移動 (Unintended Focus Changes)

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

    • 各ウィジェットのfocusPolicyを確認し、本当にキーボードフォーカスを受け取る必要があるのかどうかを見直します。ラベルや装飾的なウィジェットにはQt::NoFocusを設定するのが適切です。
    • Tabキーを押してフォーカスがどのように移動するかを確認し、意図しない移動がある場合は、親ウィジェット内でのウィジェットの順序を見直すか、setTabOrder()を使用して明示的にフォーカスチェインを設定します。
    • プログラム全体でsetFocus()がどこで呼び出されているかを確認し、本当にそのタイミングでフォーカスを移動させる必要があるのか、また対象のウィジェットが正しいかを確認します。条件分岐が正しく、意図しない状況でsetFocus()が呼ばれていないか注意が必要です。
    • 誤ったfocusPolicyの設定: 本来フォーカスを受け取るべきでないウィジェット(例えば、単なるラベル)にQt::TabFocusQt::StrongFocusが設定されている。
    • フォーカスチェインの不適切な設定: ウィジェット間のフォーカス移動順序が意図した通りになっていない。これは、親ウィジェット内での子ウィジェットの追加順序や、明示的にsetTabOrder()を使用していない場合に起こりえます。
    • プログラムによる意図しないフォーカス変更: setFocus()メソッドが、予期しないタイミングやウィジェットに対して呼び出されている。

特定のウィジェットにフォーカスが当たらない (Cannot Focus on a Specific Widget)

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

    • 問題のウィジェットのfocusPolicyQt::TabFocusまたはQt::StrongFocusに設定されているか確認します。
    • ウィジェットがisEnabled()で有効になっているか確認します。親ウィジェットも有効であることを確認します。
    • ウィジェットがisVisible()で表示されているか、またレイアウトによって他のウィジェットに隠れていないかを確認します。
    • カスタムウィジェットでフォーカスイベントをオーバーライドしている場合は、実装を見直し、必要に応じてスーパークラスのメソッドを呼び出すように修正します。デバッグを行い、イベントが正しく処理されているかを確認します。
  • 原因

    • focusPolicyが適切に設定されていない: フォーカスを受け取るべきウィジェットにQt::NoFocusが設定されている。
    • ウィジェットが無効になっている (setEnabled(false)): 無効なウィジェットはフォーカスを受け取ることができません。
    • 親ウィジェットがフォーカスを受け取れない: 親ウィジェットのfocusPolicyQt::NoFocusになっている場合、その子ウィジェットもフォーカスを受け取れないことがあります。
    • ウィジェットが隠されている (setVisible(false)またはレイアウトによって非表示になっている): 非表示のウィジェットはフォーカスを受け取れません。
    • フォーカスイベントが正しく処理されていない: カスタムウィジェットでフォーカス関連のイベント(focusInEvent(), focusOutEvent())をオーバーライドしている場合に、スーパークラスの処理を呼び出していないなど、実装に誤りがある。

フォーカスがウィジェットから離れない (Focus Remains Stuck on a Widget)

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

    • カスタムウィジェットでfocusOutEvent()をオーバーライドしている場合は、その実装を確認し、フォーカスを適切に解放または移動させる処理が含まれているか確認します。
    • モーダルダイアログが意図せず表示されたままになっていないか、また、閉じられるべきタイミングで正しく閉じられているかを確認します。
    • アプリケーション全体でフォーカスを制御するロジックを確認し、特定の状態でフォーカスが固定されたままになる条件や、その解除処理に問題がないかを確認します。
  • 原因

    • フォーカスアウトイベントの処理ミス: カスタムウィジェットでfocusOutEvent()をオーバーライドしている場合に、フォーカスを次に移動させる処理が正しく実装されていない。
    • モーダルダイアログの表示: モーダルダイアログが表示されている間は、通常、そのダイアログ内のウィジェットにフォーカスが限定されます。ダイアログが適切に閉じられていない可能性があります。
    • 特定の状態でのフォーカス制御: アプリケーションの状態によってフォーカスを特定のウィジェットに固定するような処理が実装されている場合に、その状態からの解除が適切に行われていない。

ショートカットキーが期待通りに動作しない (Shortcuts Not Working as Expected)

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

    • ショートカットが関連付けられているウィジェットのfocusPolicyを確認し、必要に応じてフォーカスを受け取れるように設定します。
    • ショートカットのコンテキスト(Qt::ShortcutContext)を確認し、アプリケーションの要件に合っているか見直します。
    • 同じショートカットが複数のウィジェットやコンテキストで定義されていないか確認し、競合がないようにします。
  • 原因

    • ショートカットが関連付けられたウィジェットがフォーカスを持っていない: ショートカットは、通常、フォーカスを持っているウィジェット(またはその親ウィジェット)に関連付けられます。意図したウィジェットがフォーカスを持っていない場合、ショートカットは発動しません。
    • ショートカットのコンテキストが間違っている: ショートカットがどのコンテキスト(例えば、ウィジェットがアクティブな時のみ、アプリケーション全体など)で有効になるかの設定が意図したものではない。

デバッグのヒント

  • フォーカス関連のイベント (focusInEvent(), focusOutEvent()) をオーバーライドしている場合は、qDebug()などでログを出力し、イベントの発生タイミングやパラメータを確認します。
  • QWidget::focusWidget() (QApplicationクラスの静的メソッド): アプリケーション内で現在フォーカスを持っているウィジェットを取得できます。
  • QWidget::hasFocus(): 現在、特定のウィジェットがフォーカスを持っているかどうかを確認できます。


例1: さまざまな focusPolicy を持つウィジェットの作成

この例では、異なる focusPolicy を持ついくつかのウィジェットを作成し、Tabキーを押したときのフォーカスの移動や、クリックによるフォーカスの取得の違いを示します。

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QDebug>

class FocusPolicyExample : public QWidget {
public:
    FocusPolicyExample(QWidget *parent = nullptr) : QWidget(parent) {
        QLabel *noFocusLabel = new QLabel("NoFocus (Tab不可, クリック不可)");
        noFocusLabel->setFocusPolicy(Qt::NoFocus);

        QLineEdit *tabFocusEdit = new QLineEdit("TabFocus (Tab可, クリック不可)");
        tabFocusEdit->setFocusPolicy(Qt::TabFocus);

        QPushButton *clickFocusButton = new QPushButton("ClickFocus (Tab不可, クリック可)");
        clickFocusButton->setFocusPolicy(Qt::ClickFocus);

        QLineEdit *strongFocusEdit = new QLineEdit("StrongFocus (Tab可, クリック可)");
        strongFocusEdit->setFocusPolicy(Qt::StrongFocus);
        strongFocusEdit->setFocus(); // 初期フォーカスを設定

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(noFocusLabel);
        layout->addWidget(tabFocusEdit);
        layout->addWidget(clickFocusButton);
        layout->addWidget(strongFocusEdit);

        setLayout(layout);
        setWindowTitle("Focus Policy Example");
    }

protected:
    void focusInEvent(QFocusEvent *event) override {
        QWidget::focusInEvent(event);
        qDebug() << sender()->objectName() << ": フォーカスイン (" << event->reason() << ")";
    }

    void focusOutEvent(QFocusEvent *event) override {
        QWidget::focusOutEvent(event);
        qDebug() << sender()->objectName() << ": フォーカスアウト (" << event->reason() << ")";
    }
};

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

説明

  • Qt::StrongFocusQLineEdit: これはTabキーを押しても、マウスでクリックしてもフォーカスされます。多くのインタラクティブなウィジェットのデフォルトです。setFocus()で初期フォーカスを設定しています。
  • Qt::ClickFocusQPushButton: このボタンはTabキーを押してもフォーカスされませんが、マウスでクリックするとフォーカスされます。クリックしてフォーカスを得た際に、コンソールにフォーカスインのメッセージが出力されます。Tabキーで移動しようとしてもスキップされます。
  • Qt::TabFocusQLineEdit: このLineEditはTabキーを押すとフォーカスされますが、マウスでクリックしてもフォーカスは移動しません。Tabキーでフォーカスが移動する際に、コンソールにフォーカスイン/アウトのメッセージが出力されます。
  • Qt::NoFocusQLabel: このラベルはTabキーを押してもフォーカスされず、クリックしてもフォーカスされません。focusInEventfocusOutEventも発生しません。

このコードを実行すると、ウィンドウが表示され、Tabキーを押したり、各ウィジェットをクリックしたりすることで、それぞれの focusPolicy の違いを体験できます。コンソールにはフォーカスが移動するたびにメッセージが出力され、フォーカスの理由(Tabキーによる移動、マウスのクリックなど)も表示されます。

例2: setTabOrder() を使用したフォーカス順序の制御

この例では、デフォルトのTabキーによるフォーカス移動の順序を変更するために setTabOrder() を使用します。

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>

class TabOrderExample : public QWidget {
public:
    TabOrderExample(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit1 = new QLineEdit("LineEdit 1");
        lineEdit2 = new QLineEdit("LineEdit 2");
        button1 = new QPushButton("Button 1");
        button2 = new QPushButton("Button 2");

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

        setLayout(layout);
        setWindowTitle("Tab Order Example");

        // 明示的にTabキーの移動順序を設定
        setTabOrder(lineEdit1, button1);
        setTabOrder(button1, lineEdit2);
        setTabOrder(lineEdit2, button2);
        setTabOrder(button2, lineEdit1); // 循環させる
    }

private:
    QLineEdit *lineEdit1;
    QLineEdit *lineEdit2;
    QPushButton *button1;
    QPushButton *button2;
};

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

説明

  • setTabOrder() を使用して、Tabキーを押したときのフォーカスの移動順序をデフォルトの垂直方向の順序から変更しています。ここでは、lineEdit1 -> button1 -> lineEdit2 -> button2 -> lineEdit1 という順序でフォーカスが移動するように設定しています。
  • 4つのウィジェット(2つのQLineEditと2つのQPushButton)を作成し、垂直レイアウトに配置しています。

このコードを実行すると、Tabキーを押すたびに、ウィジェットが指定された順序でフォーカスされていくのが確認できます。

例3: 特定の条件で focusPolicy を動的に変更する

この例では、ボタンがクリックされたときに、QLineEditの focusPolicyQt::TabFocus から Qt::NoFocus へ、またはその逆へ動的に変更します。

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>

class DynamicFocusPolicyExample : public QWidget {
public:
    DynamicFocusPolicyExample(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit = new QLineEdit("編集可能");
        lineEdit->setFocusPolicy(Qt::TabFocus);

        toggleButton = new QPushButton("編集不可にする");
        connect(toggleButton, &QPushButton::clicked, this, &DynamicFocusPolicyExample::toggleLineEditFocus);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(lineEdit);
        layout->addWidget(toggleButton);

        setLayout(layout);
        setWindowTitle("Dynamic Focus Policy Example");
    }

private slots:
    void toggleLineEditFocus() {
        if (lineEdit->focusPolicy() == Qt::TabFocus) {
            lineEdit->setFocusPolicy(Qt::NoFocus);
            toggleButton->setText("編集可能にする");
            lineEdit->setText("編集不可");
        } else {
            lineEdit->setFocusPolicy(Qt::TabFocus);
            toggleButton->setText("編集不可にする");
            lineEdit->setText("編集可能");
        }
    }

private:
    QLineEdit *lineEdit;
    QPushButton *toggleButton;
};

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

説明

  • このスロット内では、QLineEditの現在の focusPolicy をチェックし、Qt::TabFocus であれば Qt::NoFocus に変更し、ボタンのテキストとQLineEditのテキストも変更します。逆に、Qt::NoFocus であれば Qt::TabFocus に戻します。
  • ボタンがクリックされると、toggleLineEditFocus() スロットが呼び出されます。
  • QLineEditとQPushButtonを作成しています。QLineEditの初期 focusPolicyQt::TabFocus です。

この例を実行すると、ボタンをクリックするたびにQLineEditがTabキーによるフォーカスの対象になったり、ならなくなったりするのが確認できます。



QWidget::setFocus() を使用した明示的なフォーカス設定

setFocus()メソッドを呼び出すことで、プログラムの任意のタイミングで特定のウィジェットに強制的にキーボードフォーカスを与えることができます。これは、ユーザーのアクションに応じて特定のウィジェットにフォーカスを移動させたい場合に便利です。

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>

class ExplicitFocusExample : public QWidget {
public:
    ExplicitFocusExample(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit1 = new QLineEdit("LineEdit 1");
        lineEdit2 = new QLineEdit("LineEdit 2");
        focusButton = new QPushButton("LineEdit 2 にフォーカス");
        connect(focusButton, &QPushButton::clicked, this, &ExplicitFocusExample::setFocusToLineEdit2);

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

        setLayout(layout);
        setWindowTitle("Explicit Focus Example");
    }

private slots:
    void setFocusToLineEdit2() {
        lineEdit2->setFocus();
    }

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

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

この例では、ボタンがクリックされると、setFocus()メソッドが呼び出され、lineEdit2にフォーカスが移動します。この方法は、focusPolicyの設定に関わらず、強制的にフォーカスを設定できます。

フォーカスイベントの処理 (focusInEvent(), focusOutEvent())

QWidgetクラスの仮想関数である focusInEvent()focusOutEvent() をオーバーライドすることで、ウィジェットがフォーカスを受け取ったり失ったりする際の挙動をカスタマイズできます。これにより、フォーカスが移動した際に特定のアクションを実行したり、条件によってはフォーカスの移動を制御したりすることが可能です。

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QDebug>

class CustomFocusLineEdit : public QLineEdit {
public:
    CustomFocusLineEdit(const QString &text, QWidget *parent = nullptr) : QLineEdit(text, parent) {
        setFocusPolicy(Qt::StrongFocus);
    }

protected:
    void focusInEvent(QFocusEvent *event) override {
        QLineEdit::focusInEvent(event);
        qDebug() << "LineEdit にフォーカスが入りました (理由: " << event->reason() << ")";
        // フォーカスが入った時のカスタム処理
        selectAll(); // フォーカスが入ったらテキストを全選択する例
    }

    void focusOutEvent(QFocusEvent *event) override {
        QLineEdit::focusOutEvent(event);
        qDebug() << "LineEdit からフォーカスが出ました (理由: " << event->reason() << ")";
        // フォーカスが失われた時のカスタム処理
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    CustomFocusLineEdit lineEdit("初期テキスト");
    lineEdit.show();
    return a.exec();
}

この例では、CustomFocusLineEdit クラスで focusInEvent() をオーバーライドし、LineEditにフォーカスが入ったときにテキストを全選択するようにしています。focusOutEvent() も同様にオーバーライドして、フォーカスが失われた際の処理を記述できます。

フォーカスチェインの明示的な設定 (QWidget::setTabOrder())

setTabOrder() は、Tabキーを押した際のフォーカスの移動順序を明示的に設定する方法です。これは、ウィジェットの物理的な配置とは異なる論理的な順序でフォーカスを移動させたい場合に便利です。focusPolicyQt::TabFocus または Qt::StrongFocus に設定されているウィジェット間で有効です。

グラブ (grabKeyboard(), releaseKeyboard())

特定のウィジェットがキーボード入力を独占的に受け取るようにすることができます。grabKeyboard() を呼び出すと、そのウィジェット(またはその子ウィジェット)が解放されるまで、アプリケーション全体のキーボードイベントを受け取ります。通常は、一時的に特定のウィジェットにすべてのキー入力を集中させたい場合(例えば、カスタムの入力ダイアログなど)に使用されます。使用後は releaseKeyboard() を呼び出して解放する必要があります。

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QDebug>

class KeyboardGrabber : public QWidget {
public:
    KeyboardGrabber(QWidget *parent = nullptr) : QWidget(parent) {
        lineEdit = new QLineEdit("テキスト入力");
        grabButton = new QPushButton("キーボードグラブ");
        releaseButton = new QPushButton("キーボード解放");
        releaseButton->setEnabled(false); // 初期状態では解放ボタンは無効

        connect(grabButton, &QPushButton::clicked, this, &KeyboardGrabber::grabKeyboardInput);
        connect(releaseButton, &QPushButton::clicked, this, &KeyboardGrabber::releaseKeyboardInput);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(lineEdit);
        layout->addWidget(grabButton);
        layout->addWidget(releaseButton);

        setLayout(layout);
        setWindowTitle("Keyboard Grabber Example");
    }

private slots:
    void grabKeyboardInput() {
        lineEdit->grabKeyboard();
        grabButton->setEnabled(false);
        releaseButton->setEnabled(true);
        qDebug() << "キーボードグラブを開始";
    }

    void releaseKeyboardInput() {
        lineEdit->releaseKeyboard();
        grabButton->setEnabled(true);
        releaseButton->setEnabled(false);
        qDebug() << "キーボードグラブを解放";
    }

private:
    QLineEdit *lineEdit;
    QPushButton *grabButton;
    QPushButton *releaseButton;
};

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

この例では、「キーボードグラブ」ボタンを押すと、LineEditがキーボード入力を独占し、他のウィジェットはキーイベントを受け取らなくなります。「キーボード解放」ボタンを押すと、この状態が解除されます。