Qt 5.15 の新しい QML 言語機能

本記事は「New QML language features in Qt 5.15」の抄訳です。

Qt 6.0では大きな変更が予定されていますが、QMLは5.15ですでにいくつかの新しい言語機能を追加しています。本記事で必須プロパティ、インラインコンポーネント、Null合体について学びましょう。 

必須プロパティ

コンポーネントの特定プロパティを必須にする必要性があり、そのプロパティの適切なデフォルト値がない場合があります。例えば、ボタンのアクセシビリティを気にする場合、AccessibleButtonを作成し、常にdescriptionプロパティを持たせるようにします。

// AccessibleButton.qml
Button {
    property string description
    Accessible.description: description
}

しかし、ボタンが説明プロパティを持つということは、必ずしもその値を設定されるとは限りません。したがって、このコンポーネントは次のようにインスタンス化されるかもしれません。

AccessibleButton {
    onClicked: (mouse) => { /* ビジネスロジックはこちら */ }
}

アクセシビリティはもう終わり、説明文は単なる空文字列になりました!もちろん、プロパティにデフォルト値を与えることも可能ですが、どの値を与えますか?「ボタン」?ほとんど役に立ちません。「これは聞こないべき」?少なくとも品質保証は今、それを気付くするかもしれませんね。しかし、QMLエンジンがこのプロパティを必ず設定べきことを検知できた方がより便利でしょう。

Qt 5.15以前は、あいにくdescriptionを強制的にセットさせる方法はありませんでした。しかし、Qt 5.15からは以下のような指定が可能になりました。

Button {
    required property string description
    Accessible.description: description
}

これで、AccessibleButtonが作成されdescriptionが設定されていないと、アプリケーション全体の起動に失敗することになります。しかし、Loaderなどでコンポーネントを動的に作成する場合は、この方法は使えません。その場合、実行時の警告しか表示されません。また、qmllintとQtCreatorに、必須プロパティが設定されていないときの警告を表示するよう、ツール機能を追加する予定です。

必須プロパティとデリゲート

さらに、必須プロパティはデリゲートにおいて特別な役割を果たします。ご存知のように、デリゲートは提供されたモデルのロールに直接名前でアクセスすることができ、さらにモデルやインデックスのような幾つかのプロパティにもアクセスすることができます。

ListView {
    model: root.myModel
    delegate: Text {
        id: delegate
        color: index % 2 ? "gray" : "black"
        text: description
    }
}

また、デリゲートに必須のプロパティがない場合は、ここで何も変わりません。しかし、一つでも必須プロパティを含んでいる場合、その名前にはアクセスできなくなります。その場合、必須プロパティとして指定することで、明示的にアクセスを宣言する必要があります。

ListView {
    model: root.myModel
    delegate: Text {
        id: delegate
        required property int index
        required property string description
        color: index % 2 ? "gray" : "black"
        text: description
    }
}

そしてQMLエンジンは、それに応じて必要なプロパティを設定します。新しいアプローチと古いアプローチには、モデルが編集可能である場合、1つの大きな違いがあることに注意してください。古いアプローチでは、以下のように書けば、それに応じてモデルが更新されます。

Text {
    id: delegate
    Component.onCompleted: description = "My fancy new text"
}

しかし、以下のようにすると、descriptionとのバインディングが壊れてしまい(QMLエンジンは警告を表示します)、モデルが更新されなくなります。

Text {
    id: delegate
    required property string description
    Component.onCompleted: delegate.description = "My fancy new text"
}

デリゲートで使用するときと、デリゲートの外で使用するときで、コンポーネントの動作があまり変わらないようにするために、このような動作にしたのです。さらに、プロパティに命令的な代入をすることを奨励したくはありませんでした (一般的にバインドを壊すため)。

実際にモデルの値を更新したい場合でも、もちろん実現する方法はあります。modelをrequiredプロパティにして、以下のように記述します。

Component.onCompleted: model.description= "My fancy new text"

デリゲートでは、常に必須プロパティを使用することをお勧めします。これにより、ツールで問題となる非限定ルックアップを回避でき、処理速度も遅くなる傾向も避けます。

インラインコンポーネント

5.15でのもう一つの新機能は、インラインコンポーネントです。その名の通り、ファイルの中に新しいコンポーネントを定義することができます。基本的な構文は以下の通りです。

component <component name>: BaseType {
    // declare properties and bindings here
} 

このファイルの中では、新しいコンポーネントをその名前で参照することができ、独自のファイルで定義されているのと同じようになります。ここでは、LabeledImageコンポーネントを例にして、この仕組みを説明します。

// Images.qml
import QtQuick 2.15

Item {
    component LabeledImage: Column {
        property alias source: image.source
        property alias caption: text.text

        Image {
            id: image
            width: 50
            height: 50
        }
        Text {
            id: text
            font.bold: true
        }
    }

    Row {
        LabeledImage {
            id: before
            source: "before.png"
            caption: "Before"
        }
        LabeledImage {
            id: after
            source: "after.png"
            caption: "After"
        }
    }
    property LabeledImage selectedImage: before
}

また、他のファイルからコンポーネントを参照することも可能です。その場合、そのコンポーネントの名前の前に、それを含むコンポーネントの名前を付ける必要があります。

// LabeledImageBox.qml
import QtQuick 2.15

Rectangle {
    property alias caption: image.caption
    property alias source: image.source
    border.width: 2
    border.color: "black"
    Images.LabeledImage {
        id: image
    }
}

QMLにはすでにComponentという型があるのに、なぜインラインコンポーネントが必要なのかと思うかもしれません。上記例を見ると、インラインコンポーネントではComponentにはない次のようなことができることがわかります。

  • Loaderを使用するオーバーヘッドなしに、コンポーネントのインスタンスを作成できます。
  • プロパティ宣言内でコンポーネントタイプを使用できます。
  • コンポーネントが定義されているファイル以外のファイルでコンポーネントを参照できます。

インラインコンポーネントの利便性をぜひ実感してください。

Null合体

最後の新しい言語機能は、インターンの Maximilian Goldstein が実装しました。QML は一般的に EcmaScript 6 のみサポートしていますが、Max は現在最新の EcmaScript 標準に追加されつつある次期言語機能である nullish coalescing をサポートするようにしました。以下MDNを引用します。

Null 合体演算子 (??) は論理演算子の一種です。この演算子は左辺が null または undefined の場合に右の値を返し、それ以外の場合に左の値を返します。

詳しくはMDNページをご覧ください。ここでは、QMLでJSONからプロパティを設定し、それらが提供されなかった場合に、正常なデフォルトを提供するために使用する方法を例示します。

Item {
    property var settings
    property int brightness: settings.brightness ?? 100
    property color color: settings.color ?? "blue"
    Component.onCompleted: settings = JSON.parse(settingsString)
}

brightnessには、?? の代わり に|| を使用することはできません。なぜなら、settings.brightness が0であれば、デフォルト値が取得されてしまうからです。

今後について

Qt 6のリリースを目前に控え、QMLにはさらに多くの変更が加えられることでしょう。QMLエンジンの内部を改良することに加え、静的型付けを活用して、より高速なコードの生成(C++へのコンパイルを含む)とツールの改良を行いたいと考えています。さらに、最初の6.0リリースではこれらの大きな機能に焦点を当てますが、小さな改善についても柔軟に対応します。オプションチェーンやフェッチAPIのサポート追加はコミュニティからの機能要求の2つの例ですが、(最初の6.0リリースではありませんが)6.xタイムラインで検討されています。QMLに欲しい機能はありますか?コメントや、バグトラッカーへの提案など、お気軽にお寄せください。


Blog Topics:

Comments