Qt Quick 入門 第5回: 状態遷移

少し前ですが、Qt Creator 2.1 のベータ版がリリースされました。これから、RC を経て正式リリースされるわけですが、Qt Quick の環境がそろうまであと少しとなりました。これが Qt Quick の正式な始まりとなるわけです。今後の発展が楽しみなところです。

さて、前回の「Qt Quick 入門: 第4回 画像やマウスを使おう」では QML の基本的な要素である [qml Image] と [qml MouseArea] を使ってみました。今回の「Qt Quick 入門」では QML で動きのある UI を作成するのに便利な状態遷移を扱います。状態遷移を使うと、状態に応じて処理を行ったり、UI の部品を変更したりすることが簡単に出来るようになります。

今回は、前回作成した qml-example をベースに進めていきます。前回は「Click」ボタンをクリックするとログを出力しましたが、今回はクリック時の動作を変更して、Qt のロゴ画像を動かしてみましょう。

qml-example

Qt Creator を立ち上げたら前回作成した qml-example プロジェクトをオープンして、qml-example.qml を読み込んでください。

状態の追加

「デザイン」ボタンを押して Qt Quick デザイナへ移行しましょう。状態遷移で使う状態(State)の作成は、もちろん QML エディタでも可能ですが、慣れるまでは Qt Quick デザイナで行う方が簡単です。

中央の上のペイン、アプリケーションのプレビューが表示されているペインの上がこの QML の状態を表示しているペインになります。最初は状態が一つしかないため、「初期状態」だけが表示されています。その「初期状態」の横にある「+」をクリックして状態を一つ追加しましょう。

Qt Quick デザイナ: State の追加

「1 状態」が作成され(変な状態名ですが、Qt Creator 2.1で修正できるように動いています)、下記のスクリーンショットのように「初期状態」と併せて二つの状態が表示されました。今回は「Click」をクリックする毎に「初期状態」とこの「1 状態」を行き来させます。どちらの状態にいるのか分かりやすいように、「1 状態」では画像の位置を移動させましょう。「1 状態」が選択された状態で Image 要素(image1)を右側に移動させてください。

Qt Quick デザイナ: Image 要素の移動

Qt Quick デザイナ: 画像の移動後

ここから先は現状の Qt Quick デザイナでは作業できませんので、「編集」ボタンを押して QML エディタに戻ります。

状態遷移の実装を進める前に、ここまでの変更内容を確認してみましょう。現在のリスト(リストA)は下記のようになっているはずです。

// リストA: 状態追加後
import Qt 4.7

Rectangle {
width: 200
height: 200
Text {
x: 66
y: 175
text: "Click"

MouseArea {
id: mouse_area1
anchors.fill: parent
onClicked: console.log("Clicked")
}
}

Image {
id: image1
x: 5
y: 5
source: "http://doc.qt.nokia.com/4.7/images/declarative-qtlogo.png"
}
states: [ // [a1]
State { // [a2]
name: "1 状態" // [a3]

PropertyChanges { // [a4]
target: image1 // [a5]
x: 108 // [a6]
y: 5 // [a7]
}
}
]
}

[a1]: [qml '' states e=item] は [qml Item] 要素のプロパティで、[qml State] 要素のリストを持ちます。すなわち、その Item 要素が取り得る状態のリストを所持するプロパティです。ここでは後述([a3])するように「1 状態」が定義されており、また states のリストには明示されない状態として「初期状態」が存在します。

[a2]: State 要素で一つの状態を定義します。

[a3]: [qml '' name e=state] プロパティはその状態の名前を定義します。ここでは「1 状態」という名前で状態を作成しています。この名前は現在の状態を確認したり、遷移先の状態の指定などに利用します。

[a4]: [qml PropertyChanges] 要素では、この状態で変化するプロパティを記述します。その状態が他の状態と何が違うのかはこの要素で確認できます。

[a5]: [qml '' target e=propertychanges] プロパティは PropertyChanges 要素がプロパティを変更する対象を指定します。この場合、 image1 要素のプロパティを変更します。

[a6][a7]: [qml '' x e=item], [qml '' y e=item] プロパティは PropertyChanges 要素そのもののプロパティではなく、PropertyChanges 要素が変更するプロパティです。[a5] で指定した変更対象である image1 要素の x, y プロパティをこの状態に遷移した際にそれぞれ 108 と 5 に変更します。(各プロパティの数値は操作によって異なっている可能性があります)

状態遷移の実行

状態遷移を実行する方法には二種類あります。state プロパティを変更する方法と State 要素の中で状態の条件を記述する方法です。

state プロパティの変更

Item 要素が持つ [qml '' state e=item] プロパティに遷移先の状態の名前を代入することで状態遷移をすることができます。

ここでは状態を変更したいのは [qml Rectangle] 要素ですが、このリストでは [qml '' id e=item] がついてないので、まずは Rectangle 要素に id を追加しましょう。リストの最初の Rectangle(4行目) とその width プロパティ(5行目)の間に次の行を追加して Rectangle に id を追加しましょう。

    id: rectangle1

これで Rectangle 要素を他の要素から簡単に参照できるようになりました。

それでは、mousearea1 要素からこの rectangle1 要素の state を変更してみましょう。リストAでは、mousearea1 要素の onClicked プロパティは以下のようになっています。

            onClicked: console.log("Clicked")

これを以下の行に書き換えます。

            onClicked: {
if (rectangle1.state == "1 状態")
rectangle1.state = ""
else
rectangle1.state = "1 状態"
}

onClicked シグナルが発生するたびに、すなわち mousearea1 がクリックされるたびに rectangle1 要素の state プロパティを "" と "1 状態" のそれぞれに入れ替えます。 "" は初期状態を示します。初期状態では image1 要素は左上に位置しています。 "1 状態" は今回追加した image1 要素が右側に移動している状態です。

変更後のリスト(リストB)を以下に示します。

// リストB: state プロパティ変更による状態遷移
import Qt 4.7

Rectangle {
id: rectangle1 // [b1]
width: 200
height: 200
Text {
x: 66
y: 175
text: "Click"

MouseArea {
id: mouse_area1
anchors.fill: parent
onClicked: { // [b2]
if (rectangle1.state == "1 状態") // [b3]
rectangle1.state = "" // [b4]
else // [b5]
rectangle1.state = "1 状態" // [b6]
}
}
}

Image {
id: image1
x: 5
y: 5
source: "http://doc.qt.nokia.com/4.7/images/declarative-qtlogo.png"
}
states: [
State {
name: "1 状態"

PropertyChanges {
target: image1
x: 108
y: 5
}
}
]
}

それでは、このコードを実行して状態遷移が働くことを確認してみましょう。「ビルド(B)」→「実行」やサイドバーの実行ボタンなどで編集した QML ファイルを実行してみてください。実行直後は前回と同じ内容が表示されますので、「Click」をマウスでクリックしてみてください。Qt のロゴが右側に移動したでしょうか。

qml-example (1 状態)

when プロパティを使用する

状態遷移するもう一つの方法として、State 要素にある [qml '' when e=state] というプロパティに条件式を記述する方法があります。この when プロパティが真になる場合に、その State に状態が遷移します。この記法では State 要素の状態をより宣言的に表すことが出来ます。今度はこの when プロパティを使って状態遷移を行ってみましょう。

まずは状態を記憶するためのプロパティを作成します。Rectangle に次の行を追加します。ここでは id の次の行に追加しました。

    property bool imageLeft: true

property では指定した型のプロパティをその要素に追加することが出来ます。書式は以下となります。

property 型 プロパティ名

もしくは

property 型 プロパティ名: 初期値

型は int や bool, real, string 等が指定できます。詳しくは "[qt 'QML Basic Types' l=qdeclarativebasictypes]" を参照してください。プロパティ名は [qt '小文字で始まる英数字' l=qdeclarativeintroduction m=#property-naming] にしてください。

これで Rectangle 要素にプロパティ imageLeft が追加されました。このプロパティの真偽で画像の位置を決定します。

次に onClicked での動作を変更します。

            onClicked: {
if (rectangle1.state == "1 状態")
rectangle1.state = ""
else
rectangle1.state = "1 状態"
}

を以下のように imageLeft を操作するように変更します。

            onClicked: imageLeft = !imageLeft

次に、この imageLeft プロパティを用いて State "1 状態" への遷移を決定します。State "1 状態" に下記の行を追加してください。例では name プロパティの次の行に追加しています。

            when: !imageLeft

全ての修正が終わったリスト(リストC)は下記のようになります。

// リストC: when プロパティによる状態遷移
import Qt 4.7

Rectangle {
id: rectangle1
property bool imageLeft: true // [c1]
width: 200
height: 200
Text {
x: 66
y: 175
text: "Click"

MouseArea {
id: mouse_area1
anchors.fill: parent
onClicked: imageLeft = !imageLeft // [c2]
}
}

Image {
id: image1
x: 5
y: 5
source: "http://doc.qt.nokia.com/4.7/images/declarative-qtlogo.png"
}
states: [
State {
name: "1 状態"
when: !imageLeft // [c3]

PropertyChanges {
target: image1
x: 108
y: 5
}
}
]
}

リストCも実行して、こちらでも状態遷移が発生することを確認してみてください。

おわりに

今回は動きを作るのに重要な状態遷移を使ってみました。二種類の実装方法を紹介しましたが、そのどちらを用いるかは作成する UI に合わせて検討してください。state プロパティの置き換えは状態が遷移する条件で考える場合にわかりやすいですし、when プロパティを用いる方法はその状態に在る条件で考える場合にわかりやすいと思います。

次回は今回作成した状態遷移にアニメーションを追加してみようと思います。


Blog Topics:

Comments