Qt Quick におけるキーボード・フォーカス
キーが押されたり離されたりすると、キーイベントが生成され、フォーカスされた Qt QuickItem に送られます。再利用可能なコンポーネントの構築を容易にし、流動的なユーザーインターフェースに特有のいくつかのケースに対応するために、Qt QuickアイテムはQtの従来のキーボードフォーカスモデルにスコープベースの拡張を追加します。
キー操作の概要
ユーザーがキーを押したり離したりすると、次のことが起こります:
- Qt はキーアクションを受け取り、キーイベントを生成します。
- Qt はキーアクションを受け取り、キーイベントを生成します。QQuickWindow がアプリケーションのfocus window である場合、キーイベントはそのアプリケーションに配信されます。
- キーイベントは、シーンによってアクティブなフォーカスを持つItem に配信されます。アクティブフォーカスを持つアイテムがない場合、キーイベントは無視される。
- アクティブ・フォーカスを持つQQuickItem がキー・イベントを受け入れると、伝搬は停止する。そうでない場合は、イベントが受け入れられるか、ルートアイテムに到達するまで、イベントはItemの親に送られる。
次の例の
Rectangle
タイプがアクティブなフォーカスを持ち、A
キーが押された場合、イベントはそれ以上伝搬されない。B
キーが押されると、イベントはルートアイテムに伝搬され、無視されます。Rectangle { width: 100; height: 100 focus: true Keys.onPressed: (event)=> { if (event.key == Qt.Key_A) { console.log('Key A was pressed'); event.accepted = true; } } }
- ルートItem に到達すると、キーイベントはignored となり、通常の Qt キー処理が続行されます。
Keys attached property とKeyNavigation attached property も参照してください。
アクティブフォーカスアイテムの照会
Item にアクティブ・フォーカスがあるかどうかは、Item::activeFocus
プロパティで調べることができます。例えば、Text タイプのテキストは、アクティブ・フォーカスがあるかどうかで決まります。
Text { text: activeFocus ? "I have active focus!" : "I do not have active focus" }
フォーカスの取得とフォーカススコープ
Item はfocus
プロパティをtrue
に設定することでフォーカスを要求します。
非常に単純なケースでは、focus
プロパティを設定するだけで十分なこともあります。以下の例をqmlツールで実行すると、keyHandler
タイプがアクティブ・フォーカスを持ち、A
、B
、C
キーを押すとテキストが適切に変更されることがわかります。
Rectangle { color: "lightsteelblue"; width: 240; height: 25 Text { id: myText } Item { id: keyHandler focus: true Keys.onPressed: (event)=> { if (event.key == Qt.Key_A) myText.text = 'Key A was pressed' else if (event.key == Qt.Key_B) myText.text = 'Key B was pressed' else if (event.key == Qt.Key_C) myText.text = 'Key C was pressed' } } }
しかし、上記の例を再利用可能なコンポーネントやインポート・コンポーネントとして使用する場合、focus
プロパティのこの単純な使い方ではもはや十分ではありません。
その例を示すために、先に定義したコンポーネントのインスタンスを2つ作成し、1つ目のインスタンスにフォーカスを設定します。この意図は、A
、B
、C
のいずれかのキーが押されると、2つのコンポーネントのうち最初のコンポーネントがイベントを受信し、それに応じて応答するというものです。
2つのMyWidgetインスタンスをインポートして作成するコード:
//Window code that imports MyWidget Rectangle { id: window color: "white"; width: 240; height: 150 Column { anchors.centerIn: parent; spacing: 15 MyWidget { focus: true //set this MyWidget to receive the focus color: "lightblue" } MyWidget { color: "palegreen" } } }
MyWidgetコード:
Rectangle { id: widget color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true Text { id: label; anchors.centerIn: parent} focus: true Keys.onPressed: (event)=> { if (event.key == Qt.Key_A) label.text = 'Key A was pressed' else if (event.key == Qt.Key_B) label.text = 'Key B was pressed' else if (event.key == Qt.Key_C) label.text = 'Key C was pressed' } }
最初のMyWidget
オブジェクトにフォーカスを持たせたいので、そのfocus
プロパティをtrue
に設定します。しかし、コードを実行することで、2番目のウィジェットがフォーカスを受け取ることを確認できます。
MyWidget
とwindow
の両方のコードを見ると、問題は明らかです。focus
プロパティをtrue
に設定するタイプが3つあります。2つのMyWidget
はfocus
をtrue
に設定し、window
コンポーネントもフォーカスを設定します。結局、キーボード・フォーカスを持つことができるのは1つのタイプだけであり、システムはどのタイプがフォーカスを受け取るかを決定しなければなりません。2番目のMyWidget
が作成されると、focus
プロパティをtrue
に設定した最後のタイプであるため、フォーカスを受け取ります。
この問題は可視性に起因します。MyWidget
コンポ ーネントはフォーカスを持ちたいのですが、インポートされたり再利用されたりすると、フォーカ スを制御できません。同様に、window
コンポーネントには、インポートされたコンポーネントがフォーカスを要求しているかどうかを知る機能はありません。
この問題を解決するために、QMLではフォーカススコープという概念を導入しています。既存の Qt ユーザーにとって、フォーカススコープは自動的なフォーカス・プロキシ のようなものです。フォーカススコープは、FocusScope タイプを宣言することで作成されます。
次の例では、FocusScope 型をコンポーネントに追加し、その結果を表示しています。
FocusScope { //FocusScope needs to bind to visual properties of the Rectangle property alias color: rectangle.color x: rectangle.x; y: rectangle.y width: rectangle.width; height: rectangle.height Rectangle { id: rectangle anchors.centerIn: parent color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true Text { id: label; anchors.centerIn: parent } focus: true Keys.onPressed: (event)=> { if (event.key == Qt.Key_A) label.text = 'Key A was pressed' else if (event.key == Qt.Key_B) label.text = 'Key B was pressed' else if (event.key == Qt.Key_C) label.text = 'Key C was pressed' } } }
概念的に、フォーカススコープは非常に単純です。
- 各フォーカススコープ内では、1つのオブジェクトに
Item::focus
がtrue
に設定されることがあります。複数のItem にfocus
プロパティが設定されている場合、最後にfocus
を設定した型がフォーカスを持ち、他の型は未設定となり、これはフォーカススコープがない場合と同様です。 - フォーカススコープがアクティブフォーカスを受けると、
focus
が設定されている(あれば)含まれる型もアクティブフォーカスを受けます。この型がFocusScope でもある場合、プロキシ動作は継続する。フォーカススコープとサブフォーカスされたアイテムの両方にactiveFocus
プロパティが設定されます。
FocusScope タイプはビジュアルタイプではないので、その子のプロパティはFocusScope の親アイテムに公開される必要があることに注意してください。 レイアウトとポジショニングタイプは、これらのビジュアルプロパティとスタイリングプロパティを使用してレイアウトを作成します。この例では、FocusScope にそれ自身の視覚プロパティがないため、Column
タイプは 2 つのウィジェットを正しく表示できません。MyWidgetコンポーネントは、rectangle
プロパティに直接バインドして、Column
タイプがFocusScope の子を含むレイアウトを作成できるようにします。
ここまでの例では、2番目のコンポーネントが静的に選択されています。このコンポーネントを拡張してクリック可能にし、元のアプリケーションに追加するのは簡単です。ウィジェットの1つをデフォルトでフォーカスするように設定します。これで、どちらかのMyClickableWidgetをクリックすると、そのウィジェットにフォーカスが当たり、もう一方のウィジェットはフォーカスを失います。
つのMyClickableWidgetインスタンスをインポートして作成するコードです:
Rectangle { id: window color: "white"; width: 240; height: 150 Column { anchors.centerIn: parent; spacing: 15 MyClickableWidget { focus: true //set this MyWidget to receive the focus color: "lightblue" } MyClickableWidget { color: "palegreen" } } }
MyClickableWidgetのコードです:
FocusScope { id: scope //FocusScope needs to bind to visual properties of the children property alias color: rectangle.color x: rectangle.x; y: rectangle.y width: rectangle.width; height: rectangle.height Rectangle { id: rectangle anchors.centerIn: parent color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true Text { id: label; anchors.centerIn: parent } focus: true Keys.onPressed: (event)=> { if (event.key == Qt.Key_A) label.text = 'Key A was pressed' else if (event.key == Qt.Key_B) label.text = 'Key B was pressed' else if (event.key == Qt.Key_C) label.text = 'Key C was pressed' } } MouseArea { anchors.fill: parent; onClicked: { scope.focus = true } } }
QMLItem が明示的にフォーカスを失った場合(フォーカスがアクティブな状態でfocus
プロパティをfalse
に設定した場合)、システムは自動的にフォーカスを受け取る別のタイプを選択しません。つまり、現在アクティブなフォーカスがないこともあり得ます。
FocusScope タイプを使用して複数の領域間でキーボード・フォーカスを移動するデモについては、「Qt Quick Examples - Key Interaction」を参照してください。
フォーカススコープの高度な使い方
フォーカススコープを使うと、フォーカスの割り当てを簡単に分割することができます。いくつかのQMLアイテムがこの効果を利用しています。
ListView例えば、"Focus Scope "はそれ自体がフォーカススコープです。通常、ListView は手動でビジュアルチルドレンを追加することはないので、これは目立ちません。フォーカススコープであることで、ListView 、アプリケーションの他の部分への影響を気にすることなく、現在のリストアイテムにフォーカスを当てることができます。これにより、カレントアイテムデリゲートがキー操作に反応できるようになります。
この例では、これがどのように機能するかを示しています。Return
キーを押すと、現在のリスト項目の名前が表示されます。
Rectangle { color: "lightsteelblue"; width: 100; height: 50 ListView { anchors.fill: parent focus: true model: ListModel { ListElement { name: "Bob" } ListElement { name: "John" } ListElement { name: "Michael" } } delegate: FocusScope { width: childrenRect.width; height: childrenRect.height x:childrenRect.x; y: childrenRect.y TextInput { focus: true text: name Keys.onReturnPressed: console.log(name) } } } }
この例は単純ですが、舞台裏では多くのことが行われています。現在の項目が変更されるたびに、ListView はデリゲートのItem::focus
プロパティを設定します。ListView はフォーカススコープなので、アプリケーションの他の部分には影響しません。しかし、ListView 自体がアクティブ・フォーカスを持つと、デリゲート自体がアクティブ・フォーカスを受けることになります。この例では、デリゲートのルートタイプもまたフォーカススコープであり、その結果、Return
キーを処理する作業を実際に行うTextInput タイプにアクティブフォーカスが与えられます。
PathView やGridView のようなQMLのビュークラスはすべて、それぞれのデリゲートでキー処理を可能にするために、同じような振る舞いをします。
本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。