QMLのUI操作を極める!Item.focusを使ったフォーカス管理の実践コード例

2025-06-06

以下に「Item.focus」について詳しく説明します。

focus プロパティとは

Item はQMLにおけるすべての視覚的な要素の基底となる型です。Item には focus というブール型のプロパティがあり、これを true に設定することで、そのアイテムがキーボードイベントを受け取る対象となるように要求します。

例:

Rectangle {
    width: 100
    height: 50
    color: "lightgray"
    focus: true // このRectangleがフォーカスを持つように要求
    Keys.onPressed: {
        console.log("キーが押されました!");
    }
}

この例では、アプリケーションが起動すると、この Rectangle がキーボードフォーカスを持ち、ユーザーがキーを押すと "キーが押されました!" と出力されます。

activeFocus との関連

Item.focus は「このアイテムがフォーカスを持ちたい」という意思表示ですが、実際にフォーカスを持っているかどうかは Item.activeFocus プロパティで確認できます。

  • activeFocus: true: そのアイテムが現在アクティブなフォーカス(つまり、キーボード入力が送られる対象)であることを示します。これは読み取り専用のプロパティであり、直接設定することはできません。
  • focus: true: アイテムがフォーカスを持つことを要求します。システムは、この要求に基づいてフォーカスを割り当てます。

通常、focus: true に設定されたアイテムは、その親の FocusScope がアクティブなフォーカスを持っている場合、activeFocustrue になります。

FocusScope (フォーカスコープ)

QMLにおけるフォーカス管理の重要な概念として「FocusScope」があります。

  • 動作:
    • FocusScope は、それ自身がアクティブなフォーカスを得ると、その内部で focus: true に設定されているアイテムにフォーカスを渡します。
    • これにより、コンポーネントの内部で独立したフォーカス管理を行うことができます。
    • 例えば、複数の入力フィールドを持つフォームコンポーネントを考えた場合、そのフォーム全体を FocusScope で囲むことで、フォーム内の入力フィールド間でのTabキーによる移動などをスムーズに実現できます。
  • 何のために?: アプリケーションが複雑になり、多くのUIコンポーネントを持つ場合、キーボードフォーカスの管理が煩雑になります。FocusScope は、特定の領域内でのフォーカスを区切る役割を果たします。
FocusScope {
    id: myForm
    width: 300
    height: 200
    color: "lightblue"

    Column {
        spacing: 10
        TextField {
            id: input1
            placeholderText: "名前"
            focus: true // フォーム内で最初にフォーカスを受け取る
        }
        TextField {
            id: input2
            placeholderText: "メールアドレス"
        }
        Button {
            text: "送信"
        }
    }
}

この例では、myForm がアクティブなフォーカスを得ると、内部の input1 にフォーカスが移動します。ユーザーがTabキーを押すと、input1 から input2、そして Button へとフォーカスが移動します。

キーボードイベントは、activeFocus を持つ Item に最初に送られます。そのアイテムがイベントを受け入れなかった場合(event.accepted = false のままの場合)、イベントは親アイテムへと「バブリングアップ」(伝播)されます。このプロセスは、イベントが受け入れられるか、またはルートアイテムに到達するまで続きます。

  • focusPolicy: Qt 6.7以降で導入されたプロパティで、アイテムがフォーカスをどのように取得するかを定義します(例: Qt.StrongFocus, Qt.NoFocus など)。
  • forceActiveFocus() メソッド: プログラム的に特定のアイテムに強制的にフォーカスを設定したい場合に使用します。ただし、通常は focus: true を使う方がQMLの宣言的な性質に合致しています。
  • KeyNavigation 添付プロパティ: Tabキーや矢印キーによるフォーカス移動の順序をカスタマイズするために使用します。
  • Keys 添付プロパティ: キーボードイベントを処理するためのシグナル(例: onPressed, onReturnPressed など)を提供します。


フォーカスが意図しないアイテムに当たる(または当たらない)

これは最もよくある問題です。

よくある原因

  • QMLとC++のフォーカス管理の混在
    QMLとC++の両方でフォーカスをプログラム的に操作しようとすると、競合が発生し、予期せぬ動作をすることがあります。一貫したアプローチを取ることが重要です。
  • KeyNavigation の設定ミス
    KeyNavigation.tabKeyNavigation.left などのプロパティが正しく設定されていないと、Tabキーや矢印キーによるフォーカス移動が意図通りに行われません。
  • イベントの accepted 状態
    Keys.onPressed などのキーイベントハンドラ内で event.accepted = true を設定しない場合、イベントは親アイテムに伝播していきます。意図せずイベントが「飲み込まれて」しまい、本来フォーカスを持つべきアイテムが反応しないことがあります。
  • visible プロパティが false になっている、またはアイテムが画面外にある
    visible: false のアイテムは通常、フォーカスを受け取りませんが、キーボードイベントの伝播チェーンからは除外されない場合があります。しかし、ユーザーからは見えないため、フォーカスを持っていることに気づきにくいです。完全に画面外にあるアイテムも同様です。
  • enabled プロパティが false になっている
    enabled: false のアイテムはフォーカスを受け取ることができません。キーボードナビゲーションの対象からも除外されます。
  • FocusScope の誤用または不使用
    FocusScope は、その内部のアイテムにフォーカスを限定するための重要なコンテナです。これを使用しない場合、グローバルなフォーカス管理になり、意図しないアイテムにフォーカスが移ってしまうことがあります。特に、複数の独立したコンポーネントがある場合、それぞれを FocusScope で囲むことが推奨されます。
  • 複数のアイテムが focus: true を設定している
    QMLシーン内で複数のアイテムが同時に focus: true を設定している場合、Qtは内部的な順序に基づいてフォーカスを割り当てます。これは、QMLのオブジェクトツリーにおけるアイテムの宣言順や、特定の親の子になっているかによって変わることがあり、予測が難しい場合があります。最後に focus: true に設定されたアイテムがフォーカスを得る傾向がありますが、保証されるものではありません。

トラブルシューティング

  • forceActiveFocus() の使用を検討する(ただし慎重に)
    特定の状況でプログラム的にフォーカスを強制的に移動させたい場合、myButton.forceActiveFocus() のように使用できます。ただし、これを多用すると、ユーザーの期待に反する動作になる可能性があるため、注意が必要です。
  • KeyNavigation の設定を見直す
    Tabキーや矢印キーでの移動がおかしい場合、KeyNavigation.tab などの設定が正しいかを確認します。循環するフォーカスが必要な場合は、最後のアイテムから最初のアイテムへの KeyNavigation.tab 設定も行います。
  • enabled と visible プロパティを確認する
    フォーカスを持たせたいアイテムの enabledvisibletrue になっていることを確認します。
  • Qt CreatorのQMLインスペクタを使用する
    Qt CreatorのQMLインスペクタは、アプリケーションの実行中に各アイテムのプロパティ(focusactiveFocus を含む)を視覚的に確認できる強力なツールです。現在のフォーカスを持つアイテムも強調表示されます。
  • activeFocus プロパティを監視する
    activeFocus プロパティが true になったときにログ出力を行うことで、どのアイテムが実際にフォーカスを持っているかをリアルタイムで確認できます。
    MyItem {
        id: myInteractiveItem
        focus: true
        onActiveFocusChanged: {
            if (activeFocus) {
                console.log(myInteractiveItem.objectName + "がフォーカスを得ました");
            } else {
                console.log(myInteractiveItem.objectName + "がフォーカスを失いました");
            }
        }
    }
    
  • FocusScope を適切に使用する
    論理的なグループ(例: フォーム、ナビゲーションバー)ごとに FocusScope を導入し、その内部でフォーカス管理を行います。これにより、フォーカスが特定の領域に限定され、全体的な挙動を予測しやすくなります。
  • focus: true の設定を確認する
    まず、すべてのアイテムで focus: true が本当に必要なのかを確認します。通常、最初にフォーカスを持たせたいアイテムにのみ設定します。

キーボードイベントが発火しない、または意図せず発火する

よくある原因

  • キーボードのグローバルショートカットやOSのショートカットとの競合
    アプリケーション外のシステムショートカットや、Qtアプリケーション内の他のグローバルショートカットと競合している可能性があります。
  • KeyNavigation とキーイベントの競合
    KeyNavigation.tab などが設定されているアイテムでTabキーイベントを処理しようとすると、KeyNavigation の方が優先されることがあります。
  • イベントの accepted 状態の管理ミス
    Keys.onPressed: { console.log("Pressed"); event.accepted = true; } event.accepted = true を設定することで、そのイベントがこのアイテムで処理され、それ以上親に伝播しないことをQtに伝えます。これを忘れると、意図しない親アイテムがイベントを処理してしまうことがあります。逆に、複数のアイテムが同じキーに反応してほしい場合、一部のアイテムで event.accepted = false のままにしておく必要があります。
  • フォーカスがないアイテムでイベントを期待している
    キーボードイベントは、activeFocus を持つアイテムにのみ直接送られます。そのアイテムがイベントを処理しない場合、イベントは親に伝播します。フォーカスを持たないアイテムに Keys.onPressed を設定しても、直接反応することはありません。

トラブルシューティング

  • シンプルなテストケースで問題を切り分ける
    問題のコンポーネントを最小限のQMLファイルに切り出し、他の要素の影響を受けない状態でキーイベントの動作を確認します。
  • イベントフィルタの使用
    より低レベルでイベントを捕捉・処理したい場合、C++側で QObject::installEventFilter を使用するか、QMLで ItemEvent.filter プロパティを設定して、イベントをアプリケーション全体で監視・変更できます。
  • イベントハンドラ内での event.accepted の制御
    キーイベントハンドラ内で event.accepted を明示的に設定し、イベントの伝播を制御します。
  • activeFocus の状態を確認する
    前述の activeFocus 監視で、イベントを受け取ってほしいアイテムが実際にフォーカスを持っているかを確認します。

フォーカスがウィンドウの非アクティブ化で失われる、または戻らない

よくある原因

  • 特定のUI要素(例: ComboBox)クリック後にフォーカスが失われる
    ComboBox などの一部のコントロールは、ドロップダウンが開いている間、一時的にフォーカスを奪うことがあります。ドロップダウンが閉じられた後、フォーカスが元のアイテムに戻らないことがあります。
  • ウィンドウ全体のフォーカス管理の欠如
    アプリケーションのウィンドウが非アクティブになったり、他のアプリケーションにフォーカスが移ったりした場合、Qtアプリケーションのルートアイテムの activeFocusfalse になります。再びアクティブになったときに、どのアイテムにフォーカスを戻すべきかをQtが自動的に判断しますが、必ずしも意図通りとは限りません。

トラブルシューティング

  • 特定のコントロールの動作を理解する
    ComboBox など、内部的に複雑なフォーカス管理を行うコントロールについては、そのドキュメントを確認するか、テストによって動作を把握します。必要であれば、カスタムコンポーネントを作成してフォーカス挙動を制御します。
  • ルートアイテムの activeFocus 変更を監視する
    ルートの Window (または ApplicationWindow) アイテムの activeFocus プロパティの変更を監視し、アプリケーションがアクティブになったときに、特定のアイテムに forceActiveFocus() を呼び出すことで、フォーカスを明示的に設定し直すことができます。

フォーカスリング(視覚的なフォーカスインジケータ)が表示されない

よくある原因

  • カスタム描画でフォーカスリングを上書きしている
    Rectangle などのカスタムアイテムで PaintedShaderEffect を使用して描画を完全に制御している場合、デフォルトのフォーカスリングが描画されなくなります。
  • スタイルまたはテーマの問題
    使用しているQt Quick Controlsのスタイル(例: Material, Fusion)によっては、デフォルトのフォーカスリングが目立たない、または表示されない場合があります。
  • Qt Quick Controlsのスタイルを確認する
    使用しているスタイルのドキュメントを確認し、フォーカスインジケータに関する設定があるか調べます。
  • カスタムのフォーカスインジケータを実装する
    activeFocus プロパティに応じて、アイテムの border.colorborder.width を変更したり、RectangleBorderImage などの要素を重ねて表示することで、独自のフォーカスリングを実装できます。
    Rectangle {
        id: myButton
        width: 100
        height: 40
        color: "white"
        border.color: myButton.activeFocus ? "blue" : "transparent" // フォーカスがあれば青い枠
        border.width: 2
        focus: true
    
        Text {
            anchors.centerIn: parent
            text: "ボタン"
        }
    }
    
  • Qt Creatorのデバッガを活用する
    QMLデバッガを使用すると、ブレークポイントを設定したり、プロパティの値をリアルタイムで確認したりできます。
  • console.log() を多用する
    onFocusChanged, onActiveFocusChanged, Keys.onPressed などのシグナルハンドラ内で console.log() を使用し、フォーカスの移動やイベントの発火タイミングを追跡します。
  • 最小限の再現コードを作成する
    問題が発生した場合、可能な限りシンプルなQMLコードで問題を再現し、不要な要素を排除することで、原因を特定しやすくなります。


単純なフォーカス設定とキーイベント処理

これは最も基本的な例です。focus: true を設定したアイテムがキーイベントを受け取ります。

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "Simple Focus Example"

    Rectangle {
        id: focusableRectangle
        width: 200
        height: 100
        color: "lightblue"
        anchors.centerIn: parent

        // このアイテムがキーボードフォーカスを持つように要求
        focus: true

        // activeFocus が変更されたときにコンソールに出力
        onActiveFocusChanged: {
            if (activeFocus) {
                console.log("Rectangle がフォーカスを得ました!");
                color = "lightgreen"; // フォーカス取得時に色を変更
            } else {
                console.log("Rectangle がフォーカスを失いました。");
                color = "lightblue"; // フォーカス喪失時に色を元に戻す
            }
        }

        // キーが押されたときの処理
        Keys.onPressed: (event) => {
            if (event.key === Qt.Key_Space) {
                console.log("スペースキーが押されました!");
                event.accepted = true; // イベントをここで処理済みとし、親には伝播させない
            } else if (event.key === Qt.Key_Escape) {
                console.log("エスケープキーが押されました!アプリケーションを終了します。");
                Qt.quit(); // アプリケーションを終了
                event.accepted = true;
            } else {
                console.log("他のキーが押されました: " + event.text);
                // event.accepted = false; // デフォルトでは false なので、必要に応じて親に伝播させる
            }
        }

        Text {
            anchors.centerIn: parent
            text: focusableRectangle.activeFocus ? "フォーカスあり (スペースキーを押してみて)" : "フォーカスなし"
            color: "black"
            font.pixelSize: 16
        }
    }
}

解説

  • Keys.onPressed ハンドラ内で、スペースキーが押された場合にメッセージを出力し、event.accepted = true とすることで、イベントがこのアイテムで処理され、それ以上親に伝播しないようにしています。
  • onActiveFocusChanged シグナルは、そのアイテムが実際にフォーカスを得たり失ったりしたときに発火し、背景色とテキストを変更します。
  • focusableRectanglefocus: true によってフォーカスを受け取る資格を持ちます。

FocusScope を使ったグループ内フォーカス管理

複数の入力フィールドなど、特定のグループ内でフォーカスを管理したい場合に FocusScope が役立ちます。

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15 // TextField を使用するため

Window {
    width: 640
    height: 480
    visible: true
    title: "FocusScope Example"

    Column {
        anchors.centerIn: parent
        spacing: 20

        Text {
            text: "氏名とメールアドレスを入力してください"
            font.pixelSize: 20
        }

        FocusScope { // この FocusScope が入力フォーム全体を囲む
            id: registrationForm
            width: 300
            height: 150
            color: "lightgray"
            border.color: activeFocus ? "blue" : "transparent"
            border.width: 2

            Column {
                anchors.fill: parent
                anchors.margins: 10
                spacing: 10

                TextField {
                    id: nameInput
                    placeholderText: "氏名"
                    width: parent.width - 20
                    focus: true // この FocusScope 内で最初にフォーカスを得る
                    onActiveFocusChanged: {
                        if (activeFocus) {
                            console.log("氏名入力がフォーカスを得ました");
                        }
                    }
                }

                TextField {
                    id: emailInput
                    placeholderText: "メールアドレス"
                    width: parent.width - 20
                    onActiveFocusChanged: {
                        if (activeFocus) {
                            console.log("メールアドレス入力がフォーカスを得ました");
                        }
                    }
                }

                Button {
                    id: submitButton
                    text: "送信"
                    width: parent.width - 20
                    onClicked: {
                        console.log("送信ボタンがクリックされました!");
                        console.log("氏名: " + nameInput.text);
                        console.log("メールアドレス: " + emailInput.text);
                    }
                }
            }
        } // End of FocusScope

        Rectangle { // FocusScope の外にある別のアイテム
            width: 200
            height: 50
            color: "lightcoral"
            focus: true // アプリケーション起動時にこのアイテムにフォーカスが当たる
                         // (上にある FocusScope の focus は false なので、FocusScope自体には初期フォーカスは当たらない)
            Text {
                anchors.centerIn: parent
                text: "別の場所 (Tabキーで移動)"
                color: "white"
            }
            onActiveFocusChanged: {
                if (activeFocus) {
                    console.log("別の場所がフォーカスを得ました");
                }
            }
        }
    }

    // Tabキーの移動順序を制御(オプション)
    // フォーカスがないときに最初にフォーカスを得るアイテムを強制的に設定することも可能
    Component.onCompleted: {
        // 例: アプリケーション起動後、フォームに強制的にフォーカスを当てる
        // registrationForm.forceActiveFocus();
    }
}

解説

  • registrationForm の外に別の Rectangle があり、これにも focus: true が設定されています。この場合、QMLツリーでの配置順や親のフォーカス状態によって、どちらが最初にフォーカスを得るかが決まります。上記の例では、registrationFormfocusfalse なので、初期フォーカスは外側の Rectangle に行きます。
  • Tabキーを押すと、nameInput から emailInput、そして submitButton へとフォーカスが移動します。これは FocusScope が管理してくれるデフォルトの動作です。
  • FocusScoperegistrationForm を囲んでいます。これにより、アプリケーションが registrationFormFocusScope にフォーカスを移すと、その内部で focus: true が設定されている nameInput にフォーカスが当たります。

forceActiveFocus() メソッドを使ってプログラム的にフォーカスを移動させたり、KeyNavigation 添付プロパティを使ってTabキー以外のキー(矢印キーなど)での移動順序をカスタマイズしたりできます。

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "Programmatic Focus & KeyNavigation Example"

    Column {
        anchors.centerIn: parent
        spacing: 15

        Text {
            text: "矢印キーとTabキーで移動します"
            font.pixelSize: 20
        }

        Row {
            spacing: 10
            Repeater {
                model: 4
                Rectangle {
                    id: itemRect
                    width: 80
                    height: 50
                    color: "lightgray"
                    border.color: activeFocus ? "purple" : "transparent"
                    border.width: 3
                    radius: 5

                    // 各アイテムにフォーカス可能であることを示す
                    focus: index === 0 ? true : false // 最初のアイテムに初期フォーカスを設定

                    Text {
                        anchors.centerIn: parent
                        text: "Item " + (index + 1)
                        color: "black"
                    }

                    // activeFocus が変更されたときにコンソールに出力
                    onActiveFocusChanged: {
                        if (activeFocus) {
                            console.log("Item " + (index + 1) + " がフォーカスを得ました");
                            // プログラム的に次のアイテムにフォーカスを移動させるボタンを表示
                            nextButton.visible = true;
                        } else {
                            nextButton.visible = false;
                        }
                    }

                    // KeyNavigation を使って矢印キーでの移動順序を定義
                    KeyNavigation.left: (index > 0) ? repeater.itemAt(index - 1) : null
                    KeyNavigation.right: (index < repeater.count - 1) ? repeater.itemAt(index + 1) : null
                    KeyNavigation.tab: (index < repeater.count - 1) ? repeater.itemAt(index + 1) : null // Tabキーも右に移動
                }
            }
        }

        Button {
            id: nextButton
            text: "次のアイテムへ (プログラムで移動)"
            visible: false // 初期状態では非表示
            onClicked: {
                // 現在フォーカスを持っているアイテムを見つけて、次のアイテムにフォーカスを移す
                for (var i = 0; i < repeater.count; i++) {
                    var item = repeater.itemAt(i);
                    if (item.activeFocus) {
                        if (i < repeater.count - 1) {
                            repeater.itemAt(i + 1).forceActiveFocus();
                        } else {
                            // 最後のアイテムの場合は、最初のアイテムに戻る
                            repeater.itemAt(0).forceActiveFocus();
                        }
                        break;
                    }
                }
            }
        }

        Button {
            text: "最初のアイテムに強制フォーカス"
            onClicked: {
                repeater.itemAt(0).forceActiveFocus(); // 最初のアイテムに強制的にフォーカス
            }
        }
    }
}
  • nextButton は、現在フォーカスを持っているアイテムの activeFocustrue の場合にのみ表示され、クリックするとプログラム的に次のアイテムに forceActiveFocus() を呼び出します。これにより、ユーザーの操作なしにフォーカスを移動させることができます。
  • RectangleKeyNavigation.leftKeyNavigation.right を設定することで、矢印キーによるフォーカス移動の順序を定義しています。tabKeyNavigation.right と同じように設定しており、Tabキーでも右に移動します。
  • 最初のアイテム (index === 0) に初期フォーカスを設定しています。
  • Repeater を使って4つの Rectangle アイテムを作成しています。


C++ によるフォーカス管理

QMLとC++を併用するアプリケーションでは、C++側からQMLのフォーカスを制御することも可能です。

方法

  • イベントフィルタ (QObject::installEventFilter): C++でアプリケーションレベルのイベントフィルタをインストールすることで、キーボードイベントをQMLに到達する前に捕捉・処理できます。これは、特定のキーシーケンスにグローバルに反応させたい場合や、複雑なフォーカスロジックをQMLではなくC++で管理したい場合に有用です。

    // C++ イベントフィルタのクラス例
    class MyEventFilter : public QObject {
    public:
        MyEventFilter(QObject *parent = nullptr) : QObject(parent) {}
    
        bool eventFilter(QObject *obj, QEvent *event) override {
            if (event->type() == QEvent::KeyPress) {
                QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
                if (keyEvent->key() == Qt::Key_F1) {
                    qDebug() << "C++: F1 key pressed globally!";
                    // ここで QML の特定のアイテムにフォーカスを移動させるなどの処理を行う
                    // 例: engine.rootObjects().first()->findChild<QQuickItem*>("someOtherItem")->setFocus(true);
                    return true; // イベントを処理済みとして、QMLへの伝播を止める
                }
            }
            return QObject::eventFilter(obj, event); // 他のイベントは通常通り処理
        }
    };
    
    // main.cpp でインストール
    // QGuiApplication::instance()->installEventFilter(new MyEventFilter(&app));
    
  • QQuickItem::setFocus()
    QMLの Item に対応するC++クラスは QQuickItem です。QQuickItem には setFocus() メソッドがあり、これを使って特定の QQuickItem インスタンスにプログラム的にフォーカスを設定できます。

    // C++ コード例
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQuickItem>
    #include <QDebug>
    
    int main(int argc, char *argv[]) {
        QGuiApplication app(argc, argv);
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        // QML オブジェクトがロードされた後に実行
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [&](QObject *obj, const QUrl &url) {
            if (!obj && url.isEmpty()) {
                qWarning("Error: No objects to start.");
                return;
            }
            // QML から特定の Item を id で取得
            QQuickItem *myTextInput = engine.rootObjects().first()->findChild<QQuickItem*>("myTextInputId");
            if (myTextInput) {
                myTextInput->setFocus(true); // フォーカスを設定
                qDebug() << "C++: Set focus to myTextInputId";
            } else {
                qWarning() << "C++: myTextInputId not found!";
            }
        });
    
        return app.exec();
    }
    
    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    
    Window {
        id: rootWindow
        width: 640
        height: 480
        visible: true
        title: "C++ Focus Example"
    
        TextField {
            id: myTextInputId // C++ から参照するための ID
            anchors.centerIn: parent
            placeholderText: "C++からフォーカスが設定されます"
            width: 300
            height: 40
            onActiveFocusChanged: {
                if (activeFocus) {
                    console.log("QML: TextField がアクティブフォーカスを得ました");
                }
            }
        }
    }
    

利点

  • QMLとC++の連携が密な場合、C++側でフォーカス管理のロジックを一元化できる。
  • より低レベルで、アプリケーション全体のキーイベントを制御できる。

欠点

  • C++側の変更がQML側のUIに与える影響を追跡するのが難しくなる場合がある。
  • QMLの宣言的な性質から逸脱するため、QMLとC++間のやり取りが複雑になる可能性がある。

Keys 添付プロパティの高度な使用

Item.focus と密接に関連していますが、Keys 添付プロパティ自体が、フォーカスされたアイテムでのキーイベント処理の主要なメカニズムです。

方法

  • Keys.onPressAndHold (QMLでは直接提供されないが、自作可能)
    特定のキーが押しっぱなしにされたときに反応したい場合、Keys.onPressedKeys.onReleased を組み合わせてタイマーを使用することで実装できます。

  • Keys.forwardTo
    キーイベントを特定の他のアイテムに転送できます。これは、親が子にイベントを処理させたいが、子が直接フォーカスを持っていない場合などに有用です。

    Rectangle {
        id: parentRect
        focus: true // 親がフォーカスを持つ
        Keys.forwardTo: [childItem] // 親にきたキーイベントを子にも転送
    
        Rectangle {
            id: childItem
            // childItem には focus: true は設定しない
            Keys.onPressed: {
                if (event.key === Qt.Key_Space) {
                    console.log("子アイテムがスペースキーイベントを受け取りました!");
                    event.accepted = true;
                }
            }
        }
    }
    
  • Keys.onPressed, Keys.onReleased, Keys.onReturnPressed など
    特定のキーが押された、離された、またはEnterキーが押された場合などに直接反応する。

    TextField {
        id: myInput
        focus: true
        Keys.onReturnPressed: {
            console.log("Enterキーが押されました: " + myInput.text);
            // 何らかの処理の後、次のフィールドにフォーカスを移す
            nextInput.forceActiveFocus();
            event.accepted = true;
        }
    }
    
    TextField {
        id: nextInput
        placeholderText: "次の入力フィールド"
    }
    

利点

  • 特定のキーイベントに細かく反応できる。
  • QML内で完結し、宣言的で分かりやすい。

欠点

  • グローバルなキーイベント処理には不向き(その場合はC++のイベントフィルタや、ルートアイテムでの Keys 処理を検討)。

ステートマシンによるフォーカス管理

特に複雑なUIフローや、モードによってフォーカスの挙動を変えたい場合、QtQuick.StateQtQuick.StateGroup を使ったステートマシンを導入することで、フォーカス管理をより構造化できます。

方法

  • 状態 (State) ごとにフォーカスを設定
    各UIモード(例: 編集モード、表示モード)を状態として定義し、その状態に入ったときに特定のアイテムにフォーカスを割り当てます。

    Item {
        id: root
        width: 600
        height: 400
    
        TextField {
            id: editField
            x: 50; y: 50; width: 200; height: 30
            placeholderText: "編集フィールド"
            onActiveFocusChanged: { if (activeFocus) console.log("編集フィールドがフォーカス"); }
        }
    
        Button {
            id: viewButton
            x: 50; y: 100; width: 100; height: 30
            text: "表示モードへ"
            onClicked: root.state = "viewMode"
            onActiveFocusChanged: { if (activeFocus) console.log("表示ボタンがフォーカス"); }
        }
    
        Button {
            id: editButton
            x: 160; y: 100; width: 100; height: 30
            text: "編集モードへ"
            onClicked: root.state = "editMode"
            onActiveFocusChanged: { if (activeFocus) console.log("編集ボタンがフォーカス"); }
        }
    
        // 状態定義
        states: [
            State {
                name: "editMode"
                // 編集モードに入ったら editField にフォーカスを移す
                PropertyChanges { target: editField; focus: true }
                PropertyChanges { target: editField; visible: true }
                PropertyChanges { target: viewButton; visible: false }
                PropertyChanges { target: editButton; visible: true }
            },
            State {
                name: "viewMode"
                // 表示モードに入ったら viewButton にフォーカスを移す
                PropertyChanges { target: viewButton; focus: true }
                PropertyChanges { target: editField; visible: false }
                PropertyChanges { target: viewButton; visible: true }
                PropertyChanges { target: editButton; visible: false }
            }
        ]
    
        // 初期状態
        state: "editMode"
    }
    

利点

  • 複雑なUIフローにおいて、状態とフォーカス管理を同期させ、コードの可読性と保守性を高める。

欠点

  • シンプルなフォーカス管理にはオーバーキルとなる可能性がある。

Custom Component とその内部でのフォーカス管理

カスタムコンポーネントを作成し、その内部で完全にフォーカスをカプセル化することで、外部からの影響を受けにくくし、再利用性を高めます。

方法

  • コンポーネント内で FocusScope を使用
    作成するカスタムコンポーネントのルート要素を FocusScope にすることで、そのコンポーネントがフォーカスを得たときに、内部の適切な要素にフォーカスを渡すことができます。

    MyCustomForm.qml

    import QtQuick 2.15
    import QtQuick.Controls 2.15
    
    FocusScope { // ルート要素を FocusScope にする
        id: root
        width: 250
        height: 150
        border.color: activeFocus ? "green" : "gray"
        border.width: 2
        radius: 5
    
        Column {
            anchors.fill: parent
            anchors.margins: 10
            spacing: 10
    
            TextField {
                id: firstNameInput
                placeholderText: "名"
                width: parent.width - 20
                focus: true // コンポーネント内で最初のフォーカス
            }
    
            TextField {
                id: lastNameInput
                placeholderText: "姓"
                width: parent.width - 20
            }
    
            Button {
                text: "登録"
                width: parent.width - 20
                onClicked: console.log("登録ボタンクリック!")
            }
        }
    
        // 外部からこのコンポーネントにフォーカスが当たったときに、内部の最初の入力フィールドに自動的にフォーカスが移動する
    }
    

    main.qml (MyCustomForm を使用)

    import QtQuick 2.15
    import QtQuick.Window 2.15
    
    Window {
        width: 640
        height: 480
        visible: true
        title: "Custom Component Focus Example"
    
        Column {
            anchors.centerIn: parent
            spacing: 30
    
            MyCustomForm {
                id: form1
                // focus: true // 必要に応じて初期フォーカスを設定
            }
    
            MyCustomForm {
                id: form2
                // focus: true
            }
    
            Button {
                text: "フォーム1に強制フォーカス"
                onClicked: form1.forceActiveFocus()
            }
        }
    }
    

利点

  • コンポーネントの内部実装が外部に漏れず、全体的な複雑さを軽減できる。
  • コンポーネントが自己完結型となり、再利用性が高まる。

欠点

  • 特にないが、FocusScope の正しい使い方を理解する必要がある。

Qt 6.7以降では、ItemfocusPolicy プロパティが導入され、Qt Widgetsの QWidget::focusPolicy に似た挙動でフォーカスを受け取る方法をより明示的に制御できるようになりました。

focusPolicy の値

  • Qt.WheelFocus: ホイールイベントでフォーカスを受け取る(通常は使われない)。
  • Qt.NoFocus: フォーカスを受け取らない。
  • Qt.StrongFocus: Tabキーとクリックの両方でフォーカスを受け取る。
  • Qt.ClickFocus: クリックでフォーカスを受け取る。
  • Qt.TabFocus: Tabキーでフォーカスを受け取る(デフォルトの挙動に近い)。


import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "Focus Policy Example"

    Column {
        anchors.centerIn: parent
        spacing: 20

        Text {
            text: "Tabキーで移動、クリックでフォーカスを確認"
            font.pixelSize: 18
        }

        TextField {
            id: input1
            placeholderText: "Tab Focus (デフォルト)"
            width: 250
            focusPolicy: Qt.TabFocus // デフォルトだが明示的に
            focus: true // 初期フォーカス
            onActiveFocusChanged: { if (activeFocus) console.log("Input 1 activeFocus"); }
        }

        Button {
            id: button1
            text: "Click Focus Button"
            width: 250
            // Tabキーではフォーカスを受け取らないが、クリックでは受け取る
            focusPolicy: Qt.ClickFocus
            onActiveFocusChanged: { if (activeFocus) console.log("Button 1 activeFocus"); }
            onClicked: console.log("Button 1 clicked");
        }

        Rectangle {
            id: rect1
            width: 250
            height: 50
            color: "lightgreen"
            border.color: activeFocus ? "red" : "transparent"
            border.width: 3
            // 強力なフォーカス:Tabキーでもクリックでも受け取る
            focusPolicy: Qt.StrongFocus
            Keys.onPressed: {
                console.log("Rectangle Key: " + event.text);
                event.accepted = true;
            }
            onActiveFocusChanged: { if (activeFocus) console.log("Rectangle 1 activeFocus"); }

            Text {
                anchors.centerIn: parent
                text: "Strong Focus Rectangle"
            }
        }
    }
}

利点

  • Qt Widgetsからの移行者には馴染みやすい概念。
  • フォーカスを受け取る条件をより細かく、宣言的に制御できる。

欠点

  • Qt 6.7以降の新しい機能であるため、古いQtバージョンでは利用できない。