QML 第4章
Quick Starter
ここから、QMLの概観が提供されるようです。ようやく本番が来たか…という感じですね。
QMLは、宣言型の言語で、オブジェクトがお互いにどのように関連しているのかを描写するために使われます。QtQuickはQML内に組みこまれているフレームワークでアプリケーションのユーザーインターフェースを組み立てるために使います。コンポーネントでつなぎ合わせができるように、より細かく分解することができます。QtQuickは、ユーザーインターフェースの外観とか、振る舞いを記述するのです。
JavaScriptコードを使えば、より複雑な動作を提供できます。JavaScriptコードは単純なものから、とても複雑なものまで多岐にわたります。書き方は、HTMLとJavaScriptの組み合わせなのですが、これは(Webページのような)ドキュメントを作成しているのではなく、ユーザーインターフェースを作成しているのです。
最も単純な型を言えば、QtQuickは、要素同士の階層構造を記述させるものになります。子要素は、親要素から座標を引き継ぎます。x, yは親要素を基準として決定されます。
Tip QtQuickはQMLの上に構築されています。QML言語は、要素、プロパティ、シグナル、およびバインディングしか認識しません。QtQuickはQMLの上に構築されたフレームワークです。デフォルトプロパティを使用すると、QtQuick要素の階層を優雅に構築することができます。
今から、右のようなGUIを表示するプログラムを書きます。最初の風車のコードと同じく、Qt Quick Prototypeで、プロジェクトを作ってください。
画像は、ダウンロードしたソースコードの中に入っていますよ。
画像ファイルは、その新しく作成したプロジェクトと同じディレクトリに置くと、自動で読み込まれるようになっています。qmlprojectファイルの内容を読み解ければ、それも簡単にわかります。
QmlFiles {
directory: "."
}
JavaScriptFiles {
directory: "."
}
ImageFiles {
directory: "."
}
大体こうなっています。『.』が、カレントディレクトリを指しているので、読み取れるようになっているのです。
その上に、mainFile: "***.qml"とあります。これが、実行されるファイルです。他のファイルがあっても、これを中心として読み込まれて行きます。
import QtQuick
Rectangle {
id: root
width: 120; height: 240
color: "#4A4A4A"
Image {
id: triangle
x: (
parent.width - width)/2; y: 40
source: 'assets/triangle_red.png'
}
Text {
y: triangle.y + triangle.height
width: root.width
color: 'white'
horizontalAlignment: Text.AlignHCenter
text: 'Triangle'
}
}
import文は、モジュールをインポートします。オプショナルバージョンは、major.minorの形式で追加できます。
import QtQuick 2.15 オプショナルバージョンは2.15です。
2がmajorで、 15がminorです。
ちなみに、2.15を省略すると、取りうる最大のバージョンが設定されたことになります。
C/C++やJavaScript言語のようにコメントは、シングルラインに対しては//を、マルチラインについては、/**/を使います。
全てのQMLファイルはHTMLのように、ちょうど一つのroot要素を持つ必要があります。
An element is declared by its type followed by { }
要素は、{}が続く型によって宣言されます。
例:Text {
}
これは、テキスト型です。
要素は、プロパティを持っています。 name: valueという形式です。
QMLドキュメント内の任意の要素は、id (クォート無き識別子)を使うことでアクセスすることができます。
要素はネストできます。親要素は、子要素を持つことができます。
親要素には、parentというキーワードを使ってアクセスできます。
Qt5では、import文では、QtQuick 2.15と、バージョンを書くのが必須でしたが、Qt6では省略可能になっています。
この本(原著)では、私たちが選択したキットの最大のバージョンになるようにしたいので、バージョン番号は書かないでおく、と言っています。
ルート要素は、idを『root』にする習慣をつけておいた方がいいです。
コマンドラインから実行することができます。例えば、
$QTDIR/bin/qml RectangleExample.qml
これは、Unix系のOSでしょう。
Windowsでもほとんど同じです。プロジェクトのある所までカレントディレクトリを移動し、
C:\Qt\src_self\RectangleExample>qml RectangleExample.qml
つまり、最初に『qml』と打って、qmlファイルを実行しろ、ということですね。
プロパティについて
エレメント(要素)は、要素の名前によって宣言されますけれども、プロパティはキーと値の組み合わせです。
width: 100, height: 200, text: ‘Greetings’, color: ‘#FF0000' 等。
プロパティは、明確に定義された型を持ち、初期値を持つことができます。
Text {
// (1) identifier (識別子)
id: thisLabel
// (2) set x- and y-position (x、yの位置)
x: 24; y: 16
// (3) bind height to 2 * width (高さは幅の2倍にバインド)
height: 2 * width
// (4) custom property (カスタムプロパティ)
property int times: 24
// (5) property alias (プロパティのエイリアス)
property alias anotherTimes: thisLabel.times
// (6) set text appended by value (値に付加されたテキスト)
text: "Greetings " + times
// (7) font is a grouped property
(フォントはグループ化されたプロパティ)
font.family: "Ubuntu"
font.pixelSize: 24
// (8) KeyNavigation is an attached property
(付属プロパティ)
KeyNavigation.tab: otherLabel
// (9) signal handler for property changes
(プロパティ変更のためのシグナルハンドラ)
onHeightChanged: console.log('height:', height)
// focus is need to receive key events
(フォーカスはキーイベントを受け取るために必要)
focus: true
// change color based on focus value
(フォーカスの値を基礎として色を変化させる)
color: focus ? "red" : "black"
}
(1)idは、非常に特殊な、プロパティライクな値です。QMLファイル(QMLではドキュメントと呼ばれる)内の要素を参照するのに利用されます。idは、string型ではない識別子であり、QMLシンタックスの一部です。
idは一つのドキュメントの中で唯一無二のものである必要があり、別の値にリセットされることはできませんし、①クエリ(問い合わせ)もできません。(C++の世界での参照のように振る舞います)
①クエリが出来ないっていうのは、こういうことだと思います。
Rectangle {
id: rect1
width: rect1.width
// 不可、idの値を取得して使用することはできない }
Rectangle { id: rect1 }
// 例: idを変更しようとする
rect1.id = "newId";
// 不可、idの値は変更できない
(2) プロパティは型に依存して値をセットすることができます。プロパティのために与えられる値がないならば、初期値が選択されます。①プロパティの初期値についてのより多くの情報、特別な要素のドキュメントを読み込む必要があります。
①公式リファレンスを読んでくださいということです。
(3) プロパティは、1つ、あるいは多くの他のプロパティに依存させることができます。これをプロパティバインディングといいます。依存しているプロパティの値が切り替わったら、バインドされたプロパティの値も変化するようになっています。上のText要素の例でいえば、高さは幅の2倍になっているため、幅が変化すれば、高さも変化するようになっています。
(4) 新しいプロパティを追加するには、propertyを型の前に置きます。そして、名前と、初期値を設定します。<property> <type> <name>: <value> もしvalueが設定されなければ、デフォルトの初期値が入れられます。
(defaultキーワードを使用して、あるプロパティをデフォルトプロパティとして宣言することもできます。もし他の要素がこの要素の中に作成され、そのプロパティに明示的にバインドされていない場合、それはデフォルトプロパティにバインドされます。例えば、これは子要素を追加する場合に使用されます。子要素が可視要素であれば、自動的にlist型のデフォルトプロパティであるchildrenに追加されます。)
(5) プロパティを宣言するときに、他に必要かつ重要なのは、aliasキーワードを使うことです。aliasキーワードを使用すると、オブジェクトのプロパティやオブジェクト自体を、そのタイプ内から外部スコープに転送することができます。①この技法は、コンポーネントを定義する際に、内部プロパティや要素IDをルートレベルにエクスポートするために使用します。プロパティエイリアスには型指定が不要で、参照されるプロパティやオブジェクトの型が使用されます。
①難しいですね。コンポーネントを定義する際に、というのは、簡単に考えて、別ファイルを作る際、と考えてもいいです。rootから読み取れるスコープは、別ファイルのトップ部分だけです。aliasを利用して、別ファイルのトップ部分で宣言を行うと、そのidへアクセスできます。
Main.qml
Root {
id:root
}
Other.qml
other_root {
id: other_1
property alias child_1: child_1 #これでアクセス可能になる。
other_child {
id: child_1 # rootからchild_1へアクセスできない。
}
}
(6) textプロパティは、int型のカスタムプロパティtimesに依存しています。int型の値は自動的に文字列型に変換されます。この式自体もバインディングの一例であり、timesプロパティが変更されるたびにtextが更新される結果となります。
(7) いくつかのプロパティはグループ化されたプロパティです。この機能は、プロパティがより構造化されていて、関連するプロパティを一緒にグループ化する必要がある場合に使用されます。グループ化されたプロパティのもう一つの書き方として、font { family: "Ubuntu"; pixelSize: 24 } があります。
(8) ①一部のプロパティは、要素クラス自体に属しています。これは、アプリケーション内で一度しか現れないグローバル設定要素(例: キーボード入力)に対して行われます。書き方は <要素>.<プロパティ>: <値> です。
①おそらく、attached propertyと言われるものです。例えば、Itemという要素クラスがあります。このクラスが持つfocusプロパティをtrueにすると、Keyという別の要素クラスを使用することができるようになります。このクラスは独立したクラスとしてまとめられているものの、単独では使うことができません。他にも、KeyNavigationクラスなんかもあり、本書で説明されますから、注意してお読みください。
(9) ①すべてのプロパティに対して、シグナルハンドラーを提供することができます。このハンドラーは、プロパティが変更された後に呼び出されます。例えば、ここでは高さが変更されるたびに通知を受け取り、組み込みのコンソールを使用してシステムにメッセージをログに記録したいとします。
①例えば、height: 10というプロパティがあれば、onHeightChangedというシグナルを作ることができます。プロパティは
on<propertyName>Changed
という形でシグナルを作れます。これはデバッグの際にも非常に有用です。
警告
要素IDは、ドキュメント内の要素(例: 現在のファイル)を参照するためにのみ使用すべきです。QMLは「動的スコープ」と呼ばれるメカニズムを提供しており、後から読み込まれたドキュメントが以前に読み込まれたドキュメントの要素IDを上書きします。これにより、まだ上書きされていない場合、以前に読み込まれたドキュメントの要素IDを参照することが可能になります。これはグローバル変数のようなものです。残念ながら、これを無効にすることはできません。このメカニズムは慎重に使用するか、それよりもさらに良い方法として、全く使用しないことが望ましいです。ドキュメントのルート要素のプロパティを使用して、外部に提供したい要素をエクスポートする方が良いです。
※つまり、idは一つにしましょう。グローバル変数のようなものなので。ということです。
ただし、別ファイルであれば、別のidをセットすることができます。そして、同一ファイル内で一意のidにすることになります。
先ほど、aliasの時にお話しましたが、
Main.qml
Root {
id : root
Other{
id: another
#Other.qmlではotherだが、別idにできる。
#もちろん、otherにしたほうがよい。
}
}
Other.qml
Other{
id: other
}