Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.
Getting Started Programming with QML/ja
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine. Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean. |
宣言型UI言語QMLの世界ようこそ。このスタートガイドではQMLを使って簡単なテキストエディタを作ります。このガイドが終わるころには、 QMLとC+を使ってアプリケーションを自分で書けるようになっているはずです。
インストール
まずはQt Quickの入っている最新のQt 4.7をインストールしましょう。インストールガイドに様々なプラットフォームでのインストール方法が書いてあります。
UIビルドにQMLを使う
ここで作るのは読みこみ、保存、いくつかのテキスト編集機能がついた簡単なテキストエディタです。 このガイドは2つの部分に分かれています。 最初の部分では宣言型言語QMLでアプリケーションのレイアウトと挙動をデザインします。 二つめの部分ではファイルの読みこみと保存とをQt Cで実装します。 Qtのメタオブジェクトシステムを使うことで、 Cの関数をQMLの要素が使うプロパティにすることができます。 QMLとQt Cを使うことで、効率的にインターフェイスとアプリケーションのロジックとを分離することができるのです。
完成版のソースは examples/tutorials/gettingStarted/gsQml ディレクトリにあります。 examples/tutorials/gettingStarted/gsQml/ ディレクトリのCプラグインをコンパイルする必要があるかもしれません。 これでQMLがCプラグインを見つけられるようになります。
QML ファイルを引数にして qmlviewer を起動するだけでテキストエディタが起動します。 このガイドのCの部分はQtのコンパイルについての基本的な知識はあると仮定しています。
ボタンとメニューの定義
基本的な部品 - ボタン
ボタンを作ることから始めましょう。ボタンにはマウスに反応するエリアとラベルがあり、 ユーザが押すとなんらかのアクションをとるものです。
QMLの目に見える基本的なアイテムは Rectangle です。 Rectangle エレメントにはその見ためと位置を調整できるプロパティがあります。
import QtQuick 1.0
Rectangle {
id: simplebutton
color: "grey"
width: 150; height: 75
Text{
id: buttonLabel
anchors.centerIn: parent
text: "button label"
}
}
最初の import QtQuick 1.0 は 後で使う QML エレメントを qmlviewer に読みこませるためのものです。 Qtモジュールのバージョンが import 文に入っていることに注目してください。
このシンプルな Rectangle には id プロパティに指定されているように simplebutton というユニークなIDがついています。 プロパティ名を書き、コロンの後に値を書くことで、 Rectangle エレメントのプロパティに値を設定することができます。 サンプルコードですと grey が Rectangle の colon プロパティに設定されています。 同様に Rectangle の width と height が設定されています。
Textエレメントは編集できないテキスト領域です。ここでは、この Text エレメントを buttonLabel と名付けました。 text プロパティを設定することでテキスト領域に表示される文字列を変更できます。 このラベル(訳註: Textエレメント)は Rectangle の中に置かれます。ラベルを中央に配置するために、 Text エレメントの anchors を親エレメントに設定しています。親エレメントは simplebutton になります。 anchors には他のエレメントの anchors を設定できるのでレイアウトの設定が簡潔に書けます。
このコードを SimpleButton.qml という名前をつけて保存しましょう。 qmlviewer を保存したファイルを引数にして起動すると灰色の Rectangle と Text とが表示されていることでしょう。
QMLのイベント処理でボタンをクリックする機能を実装できます。 QMLのイベント処理は Qtのシグナルとスロットのメカニズムによく似ています。 シグナルが発せられると、接続されているスロットが呼び出されます。
Rectangle{
id:simplebutton
…
MouseArea{
id: buttonMouseArea
anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
//onClicked handles valid mouse button clicks
onClicked: console.log(buttonLabel.text'' " clicked" )
}
}
ここで MouseArea エレメントを simplebutton に追加しました。 MouseArea エレメントは マウスの動きが感知されるエリアになっています。このボタンでは、 MouseArea を親エレメントの simplebutton を占めるようにしました。 anchors.fill という書き方は anchors というプロパティグループの fill というプロパティを指定するのに使われる書き方です。 QMLは アンカーによるレイアウト を使い、エレメントが他のエレメントに「アンカー」できるようにしています。これにより信頼性の高いレイアウトが構築できます。
MouseArea には指定した範囲内でのマウスの動きによって呼び出されるシグナルがたくさんあります。 そのひとつに onClicked があります。このシグナルは設定によって受け取ると決めたマウスのボタンがクリックされた時に呼ばれます。 デフォルトのボタンは左クリックになっています。 onClicked のハンドラにはアクションを設定することができます。 このサンプルでは console.log() によってマウスエリアがクリックされるとテキストが出力されるようになっています。 console.log() 関数はデバッグやテキストの出力に便利です。
ボタンを表示しマウスでクリックされた時にテキストを出力するのには、 Simplebutton.qml のコードで十分です。
Rectangle {
id: button
…
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"
signal buttonClick()
onButtonClick: {
console.log(buttonLabel.text + " clicked" )
}
MouseArea{
onClicked: buttonClick()
hoverEnabled: true
onEntered: parent.border.color = onHoverColor
onExited: parent.border.color = borderColor
}
//determines the color of the button by using the conditional operator
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}
完全な動作をするボタンのコードは Button.qml にあります。このガイドにあるコードは 三点で示してある部分を省略しています。その部分は前の項目で紹介してあるか、そこでの議論には関係ないからです。
property 型 名前 と書くことで、カスタムプロパティを定義できます。 サンプルでは color 型の buttonColor というプロパティが定義され、 "lightblue" という値が設定されています。 buttonColor は後ろの方の 条件分岐の部分でボタンの塗りつぶしの色を設定するのに使われています。 カスタムプロパティの値は : コロンだけでなく = を使っても、設定ができます。 カスタムプロパティを使うと内部のアイテムが Rectangle の外のスコープから参照できるようになります。 int, string, real や variant と呼ばれる基本的な QMLの型があります。
onEntered と onExited のシグナルハンドラを色と結びつけたので、 マウスがボタン内にあれば縁が黄色になり、ボタンの外に出るともとの色に戻るようになります。
Button.qml に signal キーワードをシグナル名の前につけて書くことで buttonClick() シグナルが定義されています。 全てのシグナルには、それぞれの名前の先頭に on をつけたハンドラが自動的に定義されます。 つまり、 onButtonClick が buttonClick のハンドラになります。 onButtonClick にはアクションが設定されています。サンプルでは onClicked のハンドラが onButtonClick を呼び出し、テキストが出力されています。 onButtonClick があるので外部のオブジェクトからも Button のマウス操作に簡単にアクセスできます。 たとえば、アイテムに複数の MouseArea がある時に buttonClick があれば の シグナルハンドラをよりよく扱えることでしょう。
さて、マウスの動きを扱えるボタンをQMLで実装するための基礎知識は整いました。 の中に Text を作り、プロパティを設定し、マウスの動きに対するアクションを実装しました。 このエレメントの中にエレメントを作っていく、というやり方がテキストエディタの完成まで繰り返しでてきます。
なにかアクションをとるように組み込まれなければ、このボタンもあまり役に立ちません。 次のセクションではこのボタンを複数使ったメニューを作ります。
メニューの作成
ここまででは、一つのQMLファイルでエレメントを作り、その挙動を変更してきました。 ここでは、QMLのエレメントをインポートし、既存のエレメントを使って別のエレメントを作ることを学びます。
メニューとは、アクションを持ったアイテムのリストを表示するためのものです。 QMLではいろいろな方法でメニューを作ることができます。まずはそれぞれ違ったアクションをするボタンを 表示するメニューを作りましょう。このコードは FileMenu.qml にあります。
import QtQuick 1.0 Qt QMLのメインモジュールのインポート
import "folderName" ディレクトリの中身を全てインポート
import "script.js" as Script JavaScriptのファイルを読みこみ、それを Script と名付ける
import キーワードはこのように使われます。インポートは JavaScript のファイルや、 同じディレクトリにないQMLファイルを使う時に必要になります。 Button.qml と FileMenu.qml は同じディレクトリにあるため Button.qml をインポートする必要はありません。 Rectangle{} の定義をするように、 Button{} の定義をするだけで、 直接 Button を使うことができます。
FileMenu.qml:
Row{
anchors.centerIn: parent
spacing: parent.width/6
Button{
id: loadButton
buttonColor: "lightgrey"
label: "Load"
}
Button{
buttonColor: "grey"
id: saveButton
label: "Save"
}
Button{
id: exitButton
label: "Exit"
buttonColor: "darkgrey"
onButtonClick: Qt.quit()
}
}
FileMenu.qml で3つの Button を定義しています。 Button は Row の中に定義されています。 Row は子の要素を縦の列に配置します。 Button の定義は Button.qml の中にあります。 前の項目での Button.qml と同じものです。 Button.qml で定義されているプロパティを上書きすることで、 新しく作った のプロパティに新たな値を設定することができます。 exitButton と呼ばれている Button はクリックされるとプログラムを終了し、 ウィンドウを閉じます。 Button.qml の onButtonClick も、 exitButton の と同様に呼び出されることに注意してください。
Row の定義は Rectangle の中にあって Rectangle の中のボタンを整列させています。 このように Row を追加することで間接的にメニューの中のボタンの列を制御します。
この時点では、編集メニューの定義もよく似ています。このメニューには Copy と Paste と Select All のボタンがあります。
前に作ったコンポーネントをインポートしてカスタマイズする方法はもう 学んだわけですし、今度はこれらのメニューをメニューバーを作るために使えます。 メニューバーはメニューを選択するためのボタンでできています。では、どわやって QMLでデータを作るか見てみましょう。
メニューバーの実装
テキストエディタにはメニューを選択するためにメニューバーが必要です。 メニューバーは複数のメニューを切り替え、どのメニューを表示するかを選択するためのものです。 メニューの切り替えをするにはメニューを全てただ単に一列にならべるのでなく、 メニューの構造化が必要になっていきます。QMLではデータを構造化するのに「モデル」を、 そして構造化されたデータを表示するのに「ビュー」を使います。
データのモデルとビューを使う
QMLはデータモデルを様々なビューで表示できます。 メニューバーはヘッダとしてメニュー名を並べて表示し、実際のメニューの中身をリストにして表示します。 メニューのリストは VisualItemModel で定義されます。 VisualItemModel はすでに出てきた Rectangle やインポートしたUIエレメントを中に持ちます。 ListModelなどのその他のモデルにはデータを表示するための「デリゲータ」が必要になります。
menuListModel の中に FileMenu と EditMenu との二つのアイテムを定義しています。 二つのメニューをカスタマイズして ListViewで表示するようにしています。 QMLの定義は MenuBar.qml に簡潔な編集メニューは EditMenu.qml に書いてあります。
VisualItemModel{
id: menuListModel
FileMenu{
width: menuListView.width
height: menuBar.height
color: fileColor
}
EditMenu{
color: editColor
width: menuListView.width
height: menuBar.height
}
}
ListViewは指定された「デリゲート」にもとづいてモデルを表示します。 デリゲートではモデルのアイテムを Row の要素として表示したり、 グリッド状にして表示したりするように指定します。menuListModel にはすでに目に見えるアイテムがあるので、 新しくデリゲートを作る必要はありません。
ListView{
id: menuListView
//ウィンドウのアンカーと同じふるまいになるようにする
anchors.fill:parent
anchors.bottom: parent.bottom
width:parent.width
height: parent.height
//データモデル
model: menuListModel
//メニューのスイッシの指定
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 5000
highlightFollowsCurrentItem: true
highlightMoveDuration:240
highlightRangeMode: ListView.StrictlyEnforceRange
}
さらに ListView は、マウスのドラッグやその他の動きに反応する Flickable というリストの一種になっています。 上のコードの最後の部分では、 Flickable のプロパティを設定して、 このビューが思い通りの動作をするようにしています。特に highlightMoveDuration は フリックの移行する間隔を指定しています。 highlightMoveDuration を大きくするほど、 メニューのスイッチがゆっくりになります。
ListView は index でモデルのアイテムを管理するので、それぞれのアイテムは 定義の順番に index でアクセスすることができます。 currentIndex を変えれば、 ただちに ListView でハイライトされているアイテムが切り替わります。 メニューバーのヘッダ部分がちょうどこの例になります。二つのボタンが並んでいて、 クリックされると開いているメニューが切り替わります。 fileButton は開いているメニューを ファイルメニューに切り替え index を 0 にします。この 0 というのは fileMenu が menuListModel の最初に定義されているからです。同じように editButton は クリックされると開いているメニューを EditMenu に切り替えます。
Rectangle labelList は z に 1 の値が設定されています。つまり、メニューバーよりも前に 表示されることになります。z の値が高いアイテムが、 z の値が低いアイテムの前に表示されます。 z の値はデフォルトにでは 0 になっています。
Rectangle{
id: labelList
…
z: 1
Row{
anchors.centerIn: parent
spacing:40
Button{
label: "File"
id: fileButton
…
onButtonClick: menuListView.currentIndex = 0
}
Button{
id: editButton
label: "Edit"
…
onButtonClick: menuListView.currentIndex = 1
}
}
}
できあがったメニューバーをフリックしたり、上のメニュー名をクリックすることでメニューを 開くことができます。直感的できびきびとメニューが切り替わることでしょう。
テキストエディタを作りあげる
TextArea の定義
テキストを編集する領域がなければ、テキストエディタとは言えません。QMLの TextEditで 複数行のテキスト編集領域を定義することができます。 TextEdit[TextEdit} ではユーザが直接テキストを 編集することができますが、 Text ではそれができません。
TextEdit{
id: textEditor
anchors.fill:parent
width:parent.width; height:parent.height
color:"midnightblue"
focus: true
wrapMode: TextEdit.Wrap
onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
}
このテキストエディタではフォントの色を設定し、テキストが折り返すようにしています。 TextEdit はカーソルが見えている部分の外に出るとスクロールするフリック可能な領域の中にあります。 ensureVisible() 関数が見えている範囲にカーソルがあるかどうかをチェックし、 その位置に従ってテキストエリアをスクロールします。QMLではJavascriptの文法を使えます。 また、前に書いたようにQMLファイルにJavascriptのファイルをインポートできます。
function ensureVisible®{
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
コンポーネントを合わせてテキストエディタを作る
さて、QMLでテキストエディタのレイアウトを作る準備ができました。 ここで作るテキストエディタは二つの部分からなります。いま作ったメニューバーと テキストエリアです。QMLでは作った部品を再利用し必要に応じて部品をインポートし カスタマイズすることで、コードを簡潔にすることができます。 テキストエディタのウィンドウは二つの部分にわけることにします。3分の1をメニューバーに、 3分の2をテキスト領域を表示するのに使います。メニューバーは一番前に表示されます。
Rectangle{
id: screen
width: 1000; height: 1000
//ウィンドウを2つにわける。 3分の1をメニューバーに使う。
property int partition: height/3
MenuBar{
id:menuBar
height: partition
width:parent.width
z: 1
}
TextArea{
id:textArea
anchors.bottom:parent.bottom
y: partition
color: "white"
height: partition*2
width:parent.width
}
}
部品を再利用することで、 TextEditor のコードはとても簡潔になりました。 あとはすでに定義されているふるまいは気にせずに、メインのアプリケーションのために部品をカスタマイズできます。 こうすることでアプリケーションのレイアウトとUIコンポーネントを簡単に作ることができます。
テキストエディタの装飾
Drawerインタフェースの実装
いまのままではテキストエディタはそっけないので装飾しましょう。QMLでエディタに変化をつけて アニメーションをするようにしましょう。メニューはウィンドウの3分の1を占めていますが、 必要な時だけ表示されるようにしましょう。
クリックすると閉じたり開いたりするドロワーを追加することができるでしょう。 この実装ではマウスのクリックに反応する薄い四角形を作ります。テキストエディタと同じように drawer にも二つの状態があります。 「ドロワーが開いている」状態と、「ドロワーが閉じている」状態です。 drawer は小さい高さの帯状の矩形になっています。ドロワーの中には中央に矢印の 画像が入っています。 ユーザがマウスでクリックすると、 drawer は screen というアプリケーション全体の ]c state というプロパティを編集します。
Rectangle{
id:drawer
height:15
Image{
id: arrowIcon
source: "images/arrow.png"
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea{
id: drawerMouseArea
anchors.fill:parent
onClicked:{
if (screen.state "DRAWER_CLOSED"){
screen.state = "DRAWER_OPEN"
}
else if (screen.state "DRAWER_OPEN"){
screen.state = "DRAWER_CLOSED"
}
}
…
}
}
state はたんなる設定のコレクションで 状態 として定義されています。 状態をリストアップしたり、state プロパティで状態を設定したりできます。 ここでは二つの状態を DRAWER_CLOSED と DRAWER_OPEN と呼ぶことにします。 PropertyChangesエレメントでアイテムの設定をします。 DRAWER_OPEN の状態になった時は、この状態の変更を受け取るエレメントが4つあります。 ひとつめ menuBar は y プロパティが 0 になります。同じように textArea は DRAWER_OPEN になると下の位置に移動します。 textArea と drawer とそのアイコンは 状態の変化に合うようにプロパティが変更されます。
states:[
State {
name: "DRAWER_OPEN"
PropertyChanges { target: menuBar; y: 0}
PropertyChanges { target: textArea; y: partition + drawer.height}
PropertyChanges { target: drawer; y: partition}
PropertyChanges { target: arrowIcon; rotation: 180}
},
State {
name: "DRAWER_CLOSED"
PropertyChanges { target: menuBar; y:-height; }
PropertyChanges { target: textArea; y: drawer.height; height: screen.height- drawer.height }
PropertyChanges { target: drawer; y: 0 }
PropertyChanges { target: arrowIcon; rotation: 0 }
}
]
状態の変化は一瞬で起こりますが、その移行はスムーズに起こってほしいものです。状態の移行は transition プロパティを Transitionで定義することで設定できます。 このテキストエディタは状態が DRAWER_OPEN か DRAWER_CLOSED のどちらに変わる時も 移行が起きます。移行の定義には from と to が必要なのに注意してください。 全ての状態の変化に対応する、と書くためにワイルドカード * を使うことができます。
状態の移行の間にプロパティの変化にアニメーションを設定できます。 menuBar は y:0 から y:-partition に変化します。この変化には NumberAnimation を使うことで アニメーションをつけることができます。ここでは menuBar がある程度の時間をかけてアニメーションし、 また easing curve を設定することができます。 easing curve は状態の移行の間のアニメーションのレートと 補間を設定するためのものです。(訳註: ようするにアニメーションの挙動をいじれるものらしい?) ここでは Easing.OutQuint を選びました。 これはアニメーションの最後に動きをゆっくりにします。ぜひ QMLのアニメーションの記事を読んでください。
transitions: [
Transition {
to: "*"
NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
}
]
Behavior でもアニメーションを設定することができます。 Transition は状態の移行の時に働くものですが Behavior はその他の一般のプロパティの変化にも 働きます。このテキストエディタでは、ドロワーの矢印が rotation のプロパティが変化した時に NumberAnimation のアニメーションが起こるようにしています。
TextEditor.qml の中に:
Behavior{
NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
}
状態とアニメーションがわかったところでテキストエディタのコンポーネントの話に戻りましょう。 これでコンポーネントの見ためを良くすることができますね。 Button.qml ではボタンがクリックされると color と scale のプロパティが変わるようにすることができます。 色も ColorAnimationを使うことでアニメーションにできますし、 数値については NumberAnimationでアニメーションにできます。 以下の on プロパティ名という書き方はひとつのプロパティを対象にする時に便利です。
In Button.qml:
…
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
Behavior on color { ColorAnimation{ duration: 55} }
scale: buttonMouseArea.pressed ? 1.1 : 1.00
Behavior on scale { NumberAnimation{ duration: 55} }
さらにグラデーションや透過度を設定して見ためをよくすることができます。 Gradient を定義すると color のプロパティが上書きされます。 グラデーションの色は Gradient の中に GradientStopを定義することで設定することができます。 グラデーションの位置は 0.0 から 1.0 の間の比率を使って指定します。
In MenuBar.qml
gradient: Gradient {
GradientStop { position: 0.0; color: "#8C8F8C" }
GradientStop { position: 0.17; color: "#6A6D6A" }
GradientStop { position: 0.98;color: "#3F3F3F" }
GradientStop { position: 1.0; color: "#0e1B20" }
}
これはメニューバーに色の濃さでグラデーションをかけます。グラデーションは 0.0 からはじまり、 1.0 までついています。
ここからどうするのか?
簡単なテキストエディタのUIができあがりました。先に進みましょう。UIは完成したので、 Qt と C++ を使ってロジックの部分を実装しましょう。QMLはロジックをUIから分離することができるので、 プロトタイプツールとして使えます。
QML を Qt C++ で拡張する
テキストエディタのレイアウトはできたので、機能の方を C++ で実装していきましょう。 QMLとC+を使えばロジックはQtを使って書くことができます。 Qtの Declarativeクラスを使えば CのアプリケーションでQMLのコードを使うことができます。また、QMLのエレメントを Graphics Scene で表示することもできます。逆に、Cのコードをqmlviewerの プラグインにすることもできます。このサンプルでは、保存と読みこみの機能をCで書き、 それをプラグインにすることにします。こうすれば実行ファイルを起動するのではなく、 QMLを読みこむだけでアプリケーションを実行できます。
C+ のクラスをQMLにエクスポートする
これから Qt と C++ を使ってファイルの保存と読みこみを実装します。登録すればC+のクラスと関数を QMLから使うことができます。QMLに読みこむには、クラスをQtプラグインとしてコンパイルし、 QMLからそのプラグインがある場所がわかっていなければなりません。
このテキストエディタでは以下の項目を作ります。
- ディレクトリ関連の動作を行なう Directory クラス
- ディレクトリ内のファイルをシミュレートする File クラス (QObject にします)
- QMLに登録するために作るプラグインクラス
- プラグインをコンパイルするのに用いるQtプロジェクトファイル
- qmlviewer にプラグインの位置を指定するのに用いる qmldir ファイル
Qtプラグインを作る
プラグインを作るには以下の項目をQtプロジェクトファイルで設定しなければなりません。 まずは必要なソースコードとヘッダ、Qtモジュールをプロジェクトファイルに書きます。 Cコードとプロジェクトファイルは filedialog ディレクトリに置かれています。
In filedialog.pro:
TEMPLATE = lib
CONFIG''= qt plugin
QT ''= declarative
DESTDIR''= ../plugins
OBJECTS_DIR = tmp
MOC_DIR = tmp
TARGET = FileDialog
HEADERS ''= directory.h file.h dialogPlugin.h
SOURCES''= directory.cpp file.cpp dialogPlugin.cpp
このプロジェクトファイルでは Qt を declarative モジュールを有効にしてコンパイルし、 それを plugin として設定し、 lib テンプレートを使っています。コンパイルしたプラグインは 親ディレクトリの plugins ディレクトリに置かれます。
クラスをQMLに登録する
In dialogPlugin.h:
#include <QDeclarativeExtensionPlugin>
class DialogPlugin : public QDeclarativeExtensionPlugin
{
Q_OBJECT
public:
void registerTypes(const char *uri);
};
サンプルのプラグインクラス DialogPlugin は QDeclarativeExtensionPlugin のサブクラスになっています。 ここで継承関数 registerTypes() を実装しなくてはいけません。 dialogplugin.cpp はこんなふうになっています。
DialogPlugin.cpp:
#include "dialogPlugin.h"
#include "directory.h"
#include "file.h"
#include <qdeclarative.h>
void DialogPlugin::registerTypes(const char *uri){
qmlRegisterType<Directory>(uri, 1, 0, "Directory");
qmlRegisterType<File>(uri, 1, 0,"File");
}
Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);
registerTypes() は、作ったクラス File と Directory を QMLに登録しています。この qmlRegisterType の関数はテンプレート引数にクラス名をとり、 クラスのメジャーバージョン、マイナーバージョン、名前を引数にとります。
プラグインを Q_EXPORT_PLUGIN2 でエクスポートします。 dialogPlugin.h でプラグインクラスの冒頭に、 Q_OBJECTマクロを書いています。 さて qmake をプロジェクトファイルに動かして、メタオブジェクトコードを生成しましょう。
== C+のクラスにQMLのプロパティを追加
CとQtのメタオブジェクトシステムを使って、 QMLのエレメントを作ることができます。シグナルとスロットを使ってプロパティは実装され、 Qtがプロパティの変化に対応できるようになっています。実装したプロパティは QMLから使うことができます。
このテキストエディタにファイルの保存と読みこみができるようにしなくてはいけません。 一般にこういった機能はファイルダイアログで行なわれています。さいわい ディレクトリの読みこみと入出力は QDir や QFile や QTextStream を使って実装することができます。
class Directory : public QObject{
Q_OBJECT
Q_PROPERTY(int filesCount READ filesCount CONSTANT)
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
…
Directory クラスはQtのメタオブジェクトシステムを使って、 ファイルをあつかうために必要なプロパティを登録しています。 Directory クラスは プラグインとしてエクスポートされ、 Directory エレメントとしてQMLで使用できます。 Q_PROPERTYを使って列挙されているのがQMLのプロパティになります。
Q_PROPERTYはプロパティを定義し、そのプロパティへの読みこみ/書きこみ関数を Qtのメタオブジェクトシステムに登録します。たとえば filename プロパティは、 QString 型で、 filename() で読みこみができ、 setFilename() で書きこみができる プロパティです。さらに、 filename プロパティに filenameChanged() というシグナルが 結びつけられていて、プロパティの値が変化した時にこのシグナルが発行されます。 filename() と setFilename() はヘッダで public 関数として宣言されています。
同じように用途に応じて、他のプロパティも定義しています。 filesCount プロパティはディレクトリの 中のファイル数をしめすものです。 filename プロパティは選択されているファイルの名前をしめし、 fileContent プロパティにそのファイルの内容が入ります。
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )<code>
files のプロパティは指定されたディレクトリの中のフィルタされたファイルの一覧になります。 Directory クラスは無効なテキストファイルを除外するように作っています。 ここでは .txt の拡張子のものだけが有効なテキストファイルとしています。さらに QDeclarativeListProperty として 宣言することで QList をQMLファイルの中から使うことができます。テンプレート引数になったクラスは QObject を継承していなくてはいけませんから、 File も QObject を継承しています。 Directory クラスでは File のリストが m_fileList という QList に保存されます。
class File : public QObject{
Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
…
};
Cで宣言したこのプロパティはQMLで Directory エレメントのプロパティとして使うことができます。 id プロパティは C''+ で作る必要はありません。 ==
Directory{
id: directory
filesCount
filename fileContent files
files[0].name
}
QMLはJavascript の文法と構造を使っています。ファイルのリストを走査して、 プロパティを取得することができます。 最初のファイルの name プロパティを取得するには files[0].name と書きます。
一般のC+''の関数もQMLからアクセス可能です。ファイルの保存と読みこみの関数はCで書かれ Q_INVOKABLEマクロを使って宣言されています。ほかにも、 slot として宣言してもQMLからアクセスできるようになります。
In Directory.h:
Q_INVOKABLE void saveFile();
Q_INVOKABLE void loadFile();
Directory クラスはディレクトリの中身が変わった時に通知を出さなくてはいけません。 signal を使ってこの機能を実現します。前に書いたように、QMLのシグナルは名前の前に on をつけたハンドラと結びつけられています。ここではシグナルに directoryChanged という名前をつけました。 このシグナルはディレクトリの内容が更新された時に発行されます。更新というのは、ただたんに ディレクトリを開きなおし、有効なファイルのリストを更新しているだけです。アクションを onDirectoryChanged に設定することで、QMLのアイテムはそのシグナルを受け取ることが できます。
list プロパティをもう少し見ておきましょう。このプロパティはアクセスや値の変更の時に コールバックを使います。 list は QDeclarativeListProperty<File> 型になっています。 リストがアクセスされると、アクセサ関数は QDeclarativeListProperty<File> を返します。 テンプレート型引数の File は QObject を継承していなくてはいけません。 QDeclarativeListProperty を作るには リストにアクセスする関数、リストを編集する関数へのポインタをコンストラクタにわたします。 アクセスされるリスト(ここでは QList) は File ポインタのリストになっていなければいけません。
QDeclarativeListPropertyのコンストラクタとここでの実装です。
QDeclarativeListProperty ( QObject * object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 ) QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt, &clearFilesPtr );
コンストラクタには、リストに追加する・リストの内を数える・インデックスを指定してリストから取得する・ リストを空にするといった操作を行なう関数へのポインタを引数にあたえます。 関数ポインタは AppendFunction, CountFunction, AtFunction, ClearFunction の定義に合っていなくてはいけません。
void appendFiles(QDeclarativeListProperty<File> * property, File * file)
File* fileAt(QDeclarativeListProperty<File> * property, int index) int filesSize(QDeclarativeListProperty<File> * property)
void clearFilesPtr(QDeclarativeListProperty<File> *property)
Directory クラスは .txt が拡張子でないファイルをフィルタで落してダイアログを 簡潔にします。 ようするに .txt が拡張子でないとダイアログには表示されません。 同様に保存されるファイルが .txt の拡張子を持つようにします。 Directory は QTextStream を使ってファイルを読み書きします。
ここまでで作った Directory エレメントを使えば、ファイルをリストとして取得し、 ディレクトリにいくつのテキストファイルがあるかを知り、ファイル名とその内容を文字列で取得し、 ディレクトリの内容に変化があった時に通知を受けることができます。
プラグインを作るには qmake をプロジェクトファイル filedialog.pro に対して実行し、 make を動かしてプラグインをビルドし、 plugins ディレクトリに 設置させます。
== プラグインをQMLにインポートする ==
qmlviewer はアプリケーションと同じディレクトリにあるファイルをインポートします。 またインポートしたいQMLファイルのあるディレクトリを列挙した qmldir というファイルを作って 設定することもできます。 qmldir ファイルはプラグインやその他のリソースのディレクトリを 指定するのにも用いられます。
In qmldir:
Button ./Button.qml FileDialog ./FileDialog.qml TextArea ./TextArea.qml TextEditor ./TextEditor.qml EditMenu ./EditMenu.qml
plugin FileDialog plugins
いま作ったプラグインはプロジェクトファイルの TARGET で指定されていたように FileDialog と呼ばれます。コンパイルされたプラグインは plugins ディレクトリに置かれています。
== ファイルダイアログとファイルメニューの統合 ==
FileMenu が、ディレクトリのテキストファイルのリストを表示し、 ユーザにクリックしてファイルを選択してもらう FileDialog を表示するようにしなくてはいけません。 また、保存・読みこみ・新規といったボタンもそれにあったアクションをとるようにしなてはいけません。 FileMenu にはユーザからファイル名をキーボードで入力してもらうテキスト領域があります。
Directory は FileMenu.qml の中で FileDialog にディレクトリの中身が 更新されたことを通知するために使われています。この通知はシグナルハンドラ onDirectoryChanged で 処理されます。
In FileMenu.qml:
Directory{ id:directory filename: textInput.text onDirectoryChanged: fileDialog.notifyRefresh()
}
簡単にするために、ダイアログは常に表示されていて、 .txt の拡張子を持たないファイルは 全く表示されることがないようにします。
In FileDialog.qml:
signal notifyRefresh()
onNotifyRefresh: dirView.model = directory.files
FileDialog は files プロパティを読みこむことで、ディレクトリのファイル一覧を 表示しています。 個々の表示されるファイルは GridView のモデルとして使われます。 GridView は指定されたデリゲータによってデータをグリッド状に表示するものです。 このデリゲータが見ためを左右するわけですが、ここではテキストを中央に表示するだけの簡単な グリッドとしておきます。ファイル名をクリックすると、その部分がハイライトされます。 FileDialog は notifyRefresh シグナルを受信し、ディレクトリの中身をリフレッシュします。
In FileMenu.qml:
Button{
id: newButton
label: "New"
onButtonClick:{
textArea.textContent = ""
}
}
Button{
id: loadButton
label: "Load"
onButtonClick:{
directory.filename = textInput.text
directory.loadFile()
textArea.textContent = directory.fileContent
}
}
Button{
id: saveButton
label: "Save"
onButtonClick:{
directory.fileContent = textArea.textContent
directory.filename = textInput.text
directory.saveFile()
}
}
Button{
id: exitButton
label: "Exit"
onButtonClick:{
Qt.quit()
}
}
FileMenu が行なうべき仕事を行なうようになりました。 saveButton が、 TextEdit のテキストを directory の fileContent プロパティにコピーし、 またファイル名もテキスト入力エリアからコピーします。最後に saveFile() が 呼び出され、ファイルが保存されます。 loadButton も同じように動作します。 また、 New によって TextEdit の内容は消去されます。
さらに EditMenu も TextEdit のコピー・ペースト・全て選択などの機能と結びつけます。
テキストエディタの完成
とうとうアプリケーションが簡単なテキストエディタとして動くようになりました。 入力されたものをファイルに保存することができます。ファイルから読みこんでテキスト編集を することもできます。
テキストエディタの実行
テキストエディタを実行する前にファイルダイアログのC+プラグインをコンパイルしなくてはいけません。 コンパイルするには gsQml ディレクトリに入って qmake を実行し make または nmake (お使いのOSによって変わります) を使ってコンパイルします。 その後、 qmlviewer で texteditor.qml を開くとテキストエディタが起動します。