見守られてるらしいので、がんばりますか。
通貨の取得と変換
昨日のアプリに、通貨情報の取得と変換のロジックを実装していきます。これから実装を追加していきますが、基本的にRectangleのブロックの内側に入れて行く必要がある事に注意して下さい。
とまぁ、チュートリアルでは追加行だけ書いてあるわけですが、つまるところこういう事です。
import QtQuick 2.0
import Ubuntu.Components 0.1
Rectangle {
id: root
width: units.gu(60)
height: units.gu(80)
color: "lightgray"
property real margins: units.gu(2)
property real buttonWidth: units.gu(9)
Label {
id: title
ItemStyle.class: "title"
text: i18n.tr("CurrencyConverter")
height: contentHeight + root.margins
anchors {
left: parent.left
right: parent.right
top: parent.top
}
}
ListModel {
id: currencies
ListElement {
currency: "EUR"
rate: 1.0
}
function getCurrency(idx) {
return (idx >= 0 && idx < count) ? get(idx).currency: ""
}
function getRate(idx) {
return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
}
}
}
import Ubuntu.Components 0.1
Rectangle {
id: root
width: units.gu(60)
height: units.gu(80)
color: "lightgray"
property real margins: units.gu(2)
property real buttonWidth: units.gu(9)
Label {
id: title
ItemStyle.class: "title"
text: i18n.tr("CurrencyConverter")
height: contentHeight + root.margins
anchors {
left: parent.left
right: parent.right
top: parent.top
}
}
ListModel {
id: currencies
ListElement {
currency: "EUR"
rate: 1.0
}
function getCurrency(idx) {
return (idx >= 0 && idx < count) ? get(idx).currency: ""
}
function getRate(idx) {
return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
}
}
}
ここでは、為替を通貨とレートのペアをアイテムとしてもつリストのListModelオブジェクトとして扱います。通貨のListModelは、データを表示するビューのソースとして使用されます。ここでは欧州中央銀行(ECB)からユーロ外国為替参照レートから実際のデータをフェッチします。このデータにはユーロ自体は定義されていないので、事前に、1.0のリファレンスレートとして登録しています。
この関数の記事は、QMLの非常に強力な機能の実例となります(ここ、テストにでます)。Javascriptの統合です。2つのJavaScript関数は、indexから通貨とレートを取り出すためのグルーコードとなります。これらは、通貨情報がロードされていない最初にコンポーネントのプロパティがバインディングされた時に必要となります。ただまぁ、これらの事については今はあまり気にしなくて良いです。今の所、あなたのQMLオブジェクトにJavaScriptのコードを透過的に統合できるという事を覚えておく事が重要なのです。
さて、XmlListModel - XMLデータをmodelへとロードするQtQuickオブジェクト - で実際のデータをフェッチしましょう。これを使うために、ファイルの先頭に以下のようなimportを追加します。
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
XmlListModel {
id: ratesFetcher
source: "http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml"
namespaceDeclarations: "declare namespace gesmes='http://www.gesmes.org/xml/2002-08-01';"
+"declare default element namespace 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref';"
query: "/gesmes:Envelope/Cube/Cube/Cube"
onStatusChanged: {
if (status === XmlListModel.Ready) {
for (var i = 0; i < count; i++)
currencies.append({"currency": get(i).currency, "rate": parseFloat(get(i).rate)})
}
}
XmlRole { name: "currency"; query: "@currency/string()" }
XmlRole { name: "rate"; query: "@rate/string()" }
}
関連したプロパティはsourceで、これは、データをどこからフェッチするのかというURLを含んでいます。queryは、以下のXmlRoleからモデルのアイテムを生成するための基本クエリとして使用するため、絶対XPathクエリを定義しています。また、namespaceDeclarationsは、XPathクエリに使う名前空間の宣言です。
onStatusChangedシグナルハンドラは、JavaScriptでのシグナル・ハンドラの仕組みもまた、もうひとつの自由度の機能である事を実証します。
少しだけ補足しておくと、Qtには独自のシグナル・スロットという仕組みがあります。これはUnix系OSが提供するシグナルとは異なるものですが、まぁ、あるイベントに対して、あるSlotとよばれるハンドラの呼び出しを登録するという仕組みである事は同じです。まぁ、Qt系のイベントのコールバック的な仕組みだと覚えておけば良いでしょう。
すべてのQMLプロパティは、<property>Changed というシグナルと、それに対応するon<property>Changedというシグナルハンドラを持っています。この場合、StatusChangedシグナルは、プロパティのステータスの任意の変更を通知するため発信され、rateFetcherがデータの読み込みを終え次第、すべての通貨・レートデータを通貨ListModelに登録するためのハンドラを定義しています。
要するにrateFetcher[XmlListModel]が通貨レート情報を読み込み、currencies[ListModel]に登録しています。
多くの場合、XmlListModel単体のデータソースとして使う事ができます。ただ、今回のケースでは中間コンテナとして使っています。今回はデータを修正し、ユーロのデータを追加しておく必要があったため、currencies[ListModel]にデータを入れています。
開発者であるあなたが、どのようにネットワークアクセスをしているのか考えなくて良い程、透過的にネットワークアクセスをしていることにお気づきでしょうか。
XmlListModelのブロックの後(60行目近辺)に、レートをフェッチしているアクティビティを示すため、Ubuntu ActivityIndicatorコンポーネントを追加しましょう。
ActivityIndicator {
anchors.right: parent.right
running: ratesFetcher.status === XmlListModel.Loading
}
それを、親(root)の右側に固定して、レートデータがフェッチされるまで、Activityを表示します(回線速度にもよるでしょうが、わりと一瞬なのですぐに消えちゃいます)。
最後に、ActivityIndicatorの後(65行目近辺)に、実際の通貨変換を実行するconvert - JavaScript関数を追加します。
function convert(from, fromRateIndex, toRateIndex) {
var fromRate = currencies.getRate(fromRateIndex);
if (from.length <= 0 || fromRate <= 0.0)
return "";
return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
}
var fromRate = currencies.getRate(fromRateIndex);
if (from.length <= 0 || fromRate <= 0.0)
return "";
return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
}
とまぁ、色々追加したのですが、ここまでだと画面を表示させてもほとんど変化はありません。裏でデータを取ってきて蓄える部分ですので。
通貨の選択
ここまででバックエンドはすべて加えたので、ここからはユーザー・インタラクションの追加に移ります。これから、オブジェクトの他のコンポーネントを組み合わせて再利用可能なブロック-新しいコンポーネント-の作成をします。
まずは、import分の追加です。
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1
Component {
id: currencySelector
Popover {
Column {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: pageLayout.height
Header {
id: header
text: i18n.tr("Select currency")
}
ListView {
clip: true
width: parent.width
height: parent.height - header.height
model: currencies
delegate: Standard {
text: currency
onClicked: {
caller.currencyIndex = index
caller.input.update()
hide()
}
}
}
}
}
}
ここでは、Ubuntu PopvarとスタンダードなQtQuickのListViewを組み合わせた通貨セレクタを作成しました。ListViewは、currencies ListModelからデータを出力します。どのように列オブジェクトがUbuntu Headerにラップされ、リストビューに縦に配置され、そしてどのようにLits Viewのそれぞれのアイテムが、スタンダードリストコンポーネントとなるのかに着目して下さい。
popvarはcurrenciesの選択肢を表示します。選択されると同時にpopvarはhideになり(onClickedハンドラを確認して下さい)、そしてcallerのデータが更新されています。
callerはcurrencyIndexとinputプロパティーを有しており、そして、inputはupdate()関数をもつitemを所持していると見なされます。
とここまでが、このページの説明なのですが、いやぁ、これ知らない人には確かにちんぷんかんぷんかもしれません。隠者はすっかり読み飛ばしていましたが・・・。この説明でわかった人は挙手を・・・・と勉強会なら聞く所です。
実はこのページのコードまでだと、画面上には何もでません。このComponentをpopup表示させるコードが必要です。ですので、動かして理解する事も困難かもしれません。とりあえず、次のページはほとんどコードと画像だけで、たいした説明が無いので、そちらで詳しく解説します。
今はとりあえず、以下の構成に成っているという事を理解しておいて下さい。()の中身はID-つまり名前です。
Rectangle(root) +-- Label(title)
+-- ListModel(currencies)
+-- XmlListModel(ratesFetcher)
+-- ActivityIndicator
+-- Component(currencySelector)
+-- Popver
+-- Column
+-- Header(header)
+-- ListView[model:currencies]
+-- delegate : Standard
とりあえず、本日ここまでのコードを記載しておきましょう。
というわけで、微妙にすっきりしない2ページ目の終わりですが、続きは次のエントリーへ持ち越します。
callerはcurrencyIndexとinputプロパティーを有しており、そして、inputはupdate()関数をもつitemを所持していると見なされます。
とここまでが、このページの説明なのですが、いやぁ、これ知らない人には確かにちんぷんかんぷんかもしれません。隠者はすっかり読み飛ばしていましたが・・・。この説明でわかった人は挙手を・・・・と勉強会なら聞く所です。
実はこのページのコードまでだと、画面上には何もでません。このComponentをpopup表示させるコードが必要です。ですので、動かして理解する事も困難かもしれません。とりあえず、次のページはほとんどコードと画像だけで、たいした説明が無いので、そちらで詳しく解説します。
今はとりあえず、以下の構成に成っているという事を理解しておいて下さい。()の中身はID-つまり名前です。
Rectangle(root) +-- Label(title)
+-- ListModel(currencies)
+-- XmlListModel(ratesFetcher)
+-- ActivityIndicator
+-- Component(currencySelector)
+-- Popver
+-- Column
+-- Header(header)
+-- ListView[model:currencies]
+-- delegate : Standard
とりあえず、本日ここまでのコードを記載しておきましょう。
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1
Rectangle {
id: root
width: units.gu(60)
height: units.gu(80)
color: "lightgray"
property real margins: units.gu(2)
property real buttonWidth: units.gu(9)
Label {
id: title
ItemStyle.class: "title"
text: i18n.tr("CurrencyConverter")
height: contentHeight + root.margins
anchors {
left: parent.left
right: parent.right
top: parent.top
}
}
ListModel {
id: currencies
ListElement {
currency: "EUR"
rate: 1.0
}
function getCurrency(idx) {
return (idx >= 0 && idx < count) ? get(idx).currency: ""
}
function getRate(idx) {
return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
}
}
XmlListModel {
id: ratesFetcher
source: "http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml"
namespaceDeclarations: "declare namespace gesmes='http://www.gesmes.org/xml/2002-08-01';"
+"declare default element namespace 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref';"
query: "/gesmes:Envelope/Cube/Cube/Cube"
onStatusChanged: {
if (status === XmlListModel.Ready) {
for (var i = 0; i < count; i++)
currencies.append({"currency": get(i).currency, "rate": parseFloat(get(i).rate)})
}
}
XmlRole { name: "currency"; query: "@currency/string()" }
XmlRole { name: "rate"; query: "@rate/string()" }
}
ActivityIndicator {
anchors.right: parent.right
running: ratesFetcher.status === XmlListModel.Loading
}
function convert(from, fromRateIndex, toRateIndex) {
var fromRate = currencies.getRate(fromRateIndex);
if (from.length <= 0 || fromRate <= 0.0)
return "";
return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
}
Component {
id: currencySelector
Popover {
Column {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: pageLayout.height
Header {
id: header
text: i18n.tr("Select currency")
}
ListView {
clip: true
width: parent.width
height: parent.height - header.height
model: currencies
delegate: Standard {
text: currency
onClicked: {
caller.currencyIndex = index
caller.input.update()
hide()
}
}
}
}
}
}
}
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1
Rectangle {
id: root
width: units.gu(60)
height: units.gu(80)
color: "lightgray"
property real margins: units.gu(2)
property real buttonWidth: units.gu(9)
Label {
id: title
ItemStyle.class: "title"
text: i18n.tr("CurrencyConverter")
height: contentHeight + root.margins
anchors {
left: parent.left
right: parent.right
top: parent.top
}
}
ListModel {
id: currencies
ListElement {
currency: "EUR"
rate: 1.0
}
function getCurrency(idx) {
return (idx >= 0 && idx < count) ? get(idx).currency: ""
}
function getRate(idx) {
return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
}
}
XmlListModel {
id: ratesFetcher
source: "http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml"
namespaceDeclarations: "declare namespace gesmes='http://www.gesmes.org/xml/2002-08-01';"
+"declare default element namespace 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref';"
query: "/gesmes:Envelope/Cube/Cube/Cube"
onStatusChanged: {
if (status === XmlListModel.Ready) {
for (var i = 0; i < count; i++)
currencies.append({"currency": get(i).currency, "rate": parseFloat(get(i).rate)})
}
}
XmlRole { name: "currency"; query: "@currency/string()" }
XmlRole { name: "rate"; query: "@rate/string()" }
}
ActivityIndicator {
anchors.right: parent.right
running: ratesFetcher.status === XmlListModel.Loading
}
function convert(from, fromRateIndex, toRateIndex) {
var fromRate = currencies.getRate(fromRateIndex);
if (from.length <= 0 || fromRate <= 0.0)
return "";
return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
}
Component {
id: currencySelector
Popover {
Column {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: pageLayout.height
Header {
id: header
text: i18n.tr("Select currency")
}
ListView {
clip: true
width: parent.width
height: parent.height - header.height
model: currencies
delegate: Standard {
text: currency
onClicked: {
caller.currencyIndex = index
caller.input.update()
hide()
}
}
}
}
}
}
}
というわけで、微妙にすっきりしない2ページ目の終わりですが、続きは次のエントリーへ持ち越します。
0 件のコメント:
コメントを投稿