QMLのUI操作を極める!Item.focusを使ったフォーカス管理の実践コード例
以下に「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
がアクティブなフォーカスを持っている場合、activeFocus
も true
になります。
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.tab
やKeyNavigation.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 プロパティを確認する
フォーカスを持たせたいアイテムのenabled
とvisible
がtrue
になっていることを確認します。 - Qt CreatorのQMLインスペクタを使用する
Qt CreatorのQMLインスペクタは、アプリケーションの実行中に各アイテムのプロパティ(focus
やactiveFocus
を含む)を視覚的に確認できる強力なツールです。現在のフォーカスを持つアイテムも強調表示されます。 - 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でItem
にEvent.filter
プロパティを設定して、イベントをアプリケーション全体で監視・変更できます。 - イベントハンドラ内での event.accepted の制御
キーイベントハンドラ内でevent.accepted
を明示的に設定し、イベントの伝播を制御します。 - activeFocus の状態を確認する
前述のactiveFocus
監視で、イベントを受け取ってほしいアイテムが実際にフォーカスを持っているかを確認します。
フォーカスがウィンドウの非アクティブ化で失われる、または戻らない
よくある原因
- 特定のUI要素(例: ComboBox)クリック後にフォーカスが失われる
ComboBox
などの一部のコントロールは、ドロップダウンが開いている間、一時的にフォーカスを奪うことがあります。ドロップダウンが閉じられた後、フォーカスが元のアイテムに戻らないことがあります。 - ウィンドウ全体のフォーカス管理の欠如
アプリケーションのウィンドウが非アクティブになったり、他のアプリケーションにフォーカスが移ったりした場合、QtアプリケーションのルートアイテムのactiveFocus
はfalse
になります。再びアクティブになったときに、どのアイテムにフォーカスを戻すべきかをQtが自動的に判断しますが、必ずしも意図通りとは限りません。
トラブルシューティング
- 特定のコントロールの動作を理解する
ComboBox
など、内部的に複雑なフォーカス管理を行うコントロールについては、そのドキュメントを確認するか、テストによって動作を把握します。必要であれば、カスタムコンポーネントを作成してフォーカス挙動を制御します。 - ルートアイテムの activeFocus 変更を監視する
ルートのWindow
(またはApplicationWindow
) アイテムのactiveFocus
プロパティの変更を監視し、アプリケーションがアクティブになったときに、特定のアイテムにforceActiveFocus()
を呼び出すことで、フォーカスを明示的に設定し直すことができます。
フォーカスリング(視覚的なフォーカスインジケータ)が表示されない
よくある原因
- カスタム描画でフォーカスリングを上書きしている
Rectangle
などのカスタムアイテムでPainted
やShaderEffect
を使用して描画を完全に制御している場合、デフォルトのフォーカスリングが描画されなくなります。 - スタイルまたはテーマの問題
使用しているQt Quick Controlsのスタイル(例:Material
,Fusion
)によっては、デフォルトのフォーカスリングが目立たない、または表示されない場合があります。
- Qt Quick Controlsのスタイルを確認する
使用しているスタイルのドキュメントを確認し、フォーカスインジケータに関する設定があるか調べます。 - カスタムのフォーカスインジケータを実装する
activeFocus
プロパティに応じて、アイテムのborder.color
やborder.width
を変更したり、Rectangle
やBorderImage
などの要素を重ねて表示することで、独自のフォーカスリングを実装できます。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
シグナルは、そのアイテムが実際にフォーカスを得たり失ったりしたときに発火し、背景色とテキストを変更します。focusableRectangle
はfocus: 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ツリーでの配置順や親のフォーカス状態によって、どちらが最初にフォーカスを得るかが決まります。上記の例では、registrationForm
のfocus
はfalse
なので、初期フォーカスは外側のRectangle
に行きます。- Tabキーを押すと、
nameInput
からemailInput
、そしてsubmitButton
へとフォーカスが移動します。これはFocusScope
が管理してくれるデフォルトの動作です。 FocusScope
がregistrationForm
を囲んでいます。これにより、アプリケーションがregistrationForm
のFocusScope
にフォーカスを移すと、その内部で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
は、現在フォーカスを持っているアイテムのactiveFocus
がtrue
の場合にのみ表示され、クリックするとプログラム的に次のアイテムにforceActiveFocus()
を呼び出します。これにより、ユーザーの操作なしにフォーカスを移動させることができます。- 各
Rectangle
にKeyNavigation.left
とKeyNavigation.right
を設定することで、矢印キーによるフォーカス移動の順序を定義しています。tab
もKeyNavigation.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.onPressed
とKeys.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.State
と QtQuick.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以降では、Item
に focusPolicy
プロパティが導入され、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バージョンでは利用できない。