Qt Quick におけるキーボード・フォーカス

キーが押されたり離されたりすると、キーイベントが生成され、フォーカスされた Qt QuickItem に送られます。再利用可能なコンポーネントの構築を容易にし、流動的なユーザーインターフェースに特有のいくつかのケースに対応するために、Qt QuickアイテムはQtの従来のキーボードフォーカスモデルにスコープベースの拡張を追加します。

キー操作の概要

ユーザーがキーを押したり離したりすると、次のことが起こります:

  1. Qt はキーアクションを受け取り、キーイベントを生成します。
  2. Qt はキーアクションを受け取り、キーイベントを生成します。QQuickWindow がアプリケーションのfocus window である場合、キーイベントはそのアプリケーションに配信されます。
  3. キーイベントは、シーンによってアクティブなフォーカスを持つItem に配信されます。アクティブフォーカスを持つアイテムがない場合、キーイベントは無視される。
  4. アクティブ・フォーカスを持つ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;
            }
        }
    }
  5. ルートItem に到達すると、キーイベントはignored となり、通常の Qt キー処理が続行されます。

Keys attached propertyKeyNavigation attached property も参照してください。

アクティブフォーカスアイテムの照会

Item にアクティブ・フォーカスがあるかどうかは、Item::activeFocus プロパティで調べることができます。例えば、Text タイプのテキストは、アクティブ・フォーカスがあるかどうかで決まります。

    Text {
        text: activeFocus ? "I have active focus!" : "I do not have active focus"
    }

フォーカスの取得とフォーカススコープ

Itemfocus プロパティをtrue に設定することでフォーカスを要求します。

非常に単純なケースでは、focus プロパティを設定するだけで十分なこともあります。以下の例をqmlツールで実行すると、keyHandler タイプがアクティブ・フォーカスを持ち、ABC キーを押すとテキストが適切に変更されることがわかります。

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つ目のインスタンスにフォーカスを設定します。この意図は、ABC のいずれかのキーが押されると、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番目のウィジェットがフォーカスを受け取ることを確認できます。

MyWidgetwindow の両方のコードを見ると、問題は明らかです。focus プロパティをtrue に設定するタイプが3つあります。2つのMyWidgetfocustrue に設定し、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::focustrue に設定されることがあります。複数のItemfocus プロパティが設定されている場合、最後に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 タイプにアクティブフォーカスが与えられます。

PathViewGridView のようなQMLのビュークラスはすべて、それぞれのデリゲートでキー処理を可能にするために、同じような振る舞いをします。

本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。