2007年12月22日土曜日

JavaFX SCADA UI:図式指示計を作る

続いて、棒図式指示計を作ってみます。こちらの方は、特に言語上問題になる点はありません。
ただの棒図式ではつまらないので、線形階調表示LinearGradientを指定して棒図式を描かせてみました。階調表示の指定が簡単になるよう、棒図式の矩形は固定の高さで描き、背景となる矩形の方の高さを指示計の値に合わせて変えています。
また、目盛線と目盛値は、foreach文を使って生成しています。
企画を構築して実行すると枠付き窓が表示され、数値指示計の横に右図のような棒図式指示計が表示されます。
下に載せたプログラムの例では、結果が早く見られるよう、画面の更新間隔を0.5秒にしてあります。


package client;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import client.ScadaClient;

class ScadaDataModel {
attribute presenting: Boolean;
attribute timingGenerator: Number;
attribute numericData: Number;
attribute sc: ScadaClient;
function formatNumber(value:Number): String;
operation textColor(value:Number): Color;
}

attribute ScadaDataModel.timingGenerator
= bind [1..120] dur 60000 linear continue if presenting;

function ScadaDataModel.formatNumber(value:Number): String {
return value format as <<##0.00>>;
}

operation ScadaDataModel.textColor(value:Number): Color {
var c:Color = green;
if (value > 90.0) {c = red;}
else if (value > 75.0) {c = Color {red: 1.0, green: 0.5, blue: 0.0};}
return c;
}

trigger on new ScadaDataModel {
sc = new ScadaClient();
}

trigger on ScadaDataModel.timingGenerator = value {
numericData = sc.receiveData();
System.out.println("受信生データ: {numericData}");
System.out.println("書式化データ: {formatNumber(numericData)}");
}

Frame {
var scadaData = ScadaDataModel {presenting: true}
title: "JavaFX SCADA UI"
centerOnScreen: true
width: 800
height: 600
background: Color {red: 0.9, green: 0.9, blue: 0.9}
onClose: operation() {System.exit(0);}
content: BorderPanel {
center: Canvas {
content:
[Group {
transform: translate(50, 50)
content:
[Rect {
height: 18
width: 50
arcHeight: 4
arcWidth: 4
stroke: blue
fill: white
},
Text {
content: bind scadaData.formatNumber(scadaData.numericData)
font: new Font("Monospaced", "PLAIN", 14)
x: 47
y: 4
halign: TRAILING
fill: bind scadaData.textColor(scadaData.numericData)
},
Text {
content: "m3/s"
x: 54
y: 6
}
]
},
Group {
transform: translate(200, 50)
content:
[
Rect {
height: 120
width: 55
stroke: blue
fill: white
},
Rect {
x: 10
y: 10
height: 100
width: 10
fill: LinearGradient {
x1: 0, y1: 0, x2: 0, y2: 1
stops:
[Stop {
offset: 0.1
color: red
},
Stop {
offset: 0.2
color: Color {red: 1.0, green: 0.5, blue: 0.0}
},
Stop {
offset: 0.4
color: Color {red: 0.5, green: 1.0, blue: 0.0}
}]
spreadMethod: PAD
}
},
Rect {
x: 10
y: 10
height: bind 100 - scadaData.numericData
width: 10
fill: Color {red: 0.5, green: 0.5, blue: 0.5}
},
Line {
x1: 23
y1: 10
x2: 23
y2: 110
stroke: black
},
Group {
transform: translate(23, 10)
content: foreach (i in [0,20..100])
Line {
transform: [translate(0, i)]
x1: 0
y1: 0
x2: 3
y2: 0
stroke: black
}
},
Group {
transform: translate(34, 10)
content: foreach (i in [0,20..100])
Text {
transform: [translate(0, 100 - i)]
content: "{i}"
font: new Font("Monospaced", "PLAIN", 8)
valign: MIDDLE
halign: LEADING
}
}
]
}
]
}
}
visible: true
}

2007年12月20日木曜日

JavaFX SCADA UI:数値指示計を作る

Javaプログラムから受け取った実数値を、JavaFXプログラムで書式設定して表示することができました。そこで、ごく簡単な数値表示器を仕立て、枠付き窓の適当な位置に配置してみたいと思います。
実数値を表示する文字列Text、その背景となる矩形Rect、および単位を表示する文字列Textを、Groupで組にし、アフィン変換
transformを指定して、画面上の任意の位置に配置します。
実数値に設定した書式は固定幅ではないので、Textのhalign属性にTRAILINGを指定し、文字列の末尾(文字列を囲む矩形の右上隅の点)を座標指定の基準点とします。
また、数値指示計に表示される文字は、等幅の書体の方が読みやすいので、
Textのfont属性にはMonospacedのFontを生成して設定しています。
ただ数値を表示するだけでは面白くないので、数値が上限値と上上限値を超えると、表示色が緑から橙、赤と変わるようにしてみました。「上上限値」という言い方は計測制御の分野独特のもので、
上限値を2段階に設け、その上の方の上限値をさします(同様に、下の方の下限値は「下下限値」と言います)。英語だとなんと言うんでしょうね。「Upper maximum」では、ちょっと意味が違うし…。
ScadaDataModelクラスにtextColor()操作を定義し、引数で渡された値によって、3種類の色オブジェクトColorを返すようにしました。この操作をTextオブジェクトのfill属性に結合し、数値にあわせてその表示色を変えています。
企画を構築して実行すると、枠付き窓が表示され、その左上の方に右図のような簡易数値指示計が表示されます。
等幅の書体を指定したので、小数点もそれなりの幅を持って表示されます。しかし、表示を繰り返していると、なぜか数値表示の部分だけが左右に微妙にぶれてしまいます。JavaFXも、細かい点での改良の余地は、まだまだありそうですね。
いつもどおり、最後に源コードを載せておきます。


package client;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import client.ScadaClient;

class ScadaDataModel {
attribute presenting: Boolean;
attribute timingGenerator: Number;
attribute numericData: Number;
attribute sc: ScadaClient;
function formatNumber(value:Number): String;
operation textColor(value:Number): Color;
}

attribute ScadaDataModel.timingGenerator
= bind [1..60] dur 60000 linear continue if presenting;

function ScadaDataModel.formatNumber(value:Number): String {
return value format as <<##0.00>>;
}

operation ScadaDataModel.textColor(value:Number): Color {
var c:Color = green;
if (value > 10.0) {c = red;}
else if (value > 5.0) {c = Color {red: 1.0, green: 0.5, blue: 0.0};}
return c;
}

trigger on new ScadaDataModel {
sc = new ScadaClient();
}

trigger on ScadaDataModel.timingGenerator = value {
numericData = sc.receiveData();
System.out.println("受信生データ: {numericData}");
System.out.println("書式化データ: {formatNumber(numericData)}");
}

Frame {
var scadaData = ScadaDataModel {presenting: true}
title: "JavaFX SCADA UI"
centerOnScreen: true
width: 800
height: 600
background: Color {red: 0.9, green: 0.9, blue: 0.9}
onClose: operation() {System.exit(0);}
content: BorderPanel {
center: Canvas {
content:
[Group {
transform: translate(50, 50)
content:
[Rect {
height: 18
width: 50
arcHeight: 4
arcWidth: 4
stroke: blue
fill: white
},
Text {
content: bind scadaData.formatNumber(scadaData.numericData)
font: new Font("Monospaced", "PLAIN", 14)
x: 47
y: 4
halign: TRAILING
fill: bind scadaData.textColor(scadaData.numericData)
},
Text {
content: "m3/s"
x: 54
y: 6
}]
}]
}
}
visible: true
}

2007年12月19日水曜日

JavaFX SCADA UI:UIプログラムで数値を表示する

Javaで記述した模擬依頼者プログラムから、UIプログラムへSCADA情報(実数値)を渡す準備ができました。そこで、JavaFXでUIプログラムを記述して、それを枠付き窓に数値表示させることにします。
数値は、
JavaFXの書式設定演算子formatを利用して、小数点以下第2位までの表示とします。書式として、「##0.00」形式を指定しました。丸め方法も指定できると良かったのですが、現状のJavaFXではそこまで記述できません
Javaでは数値の書式として、「%m.nf」形式で全体の桁数と小数点以下の桁数を指定することができます。JavaFXでもこの書式を使えないわけではありません。UIの窓に数値指示計として表示するには、こちらの方が都合はいいです。
しかし、現状では「%m.nf」形式の使用にいろいろと制限がついてしまいます。私のプログラムでは、置換引金の処理で、Javaプログラムから数値を読み込んでいます。「%m.nf」形式を有効にするためには、この区画の中でJavaクラスの実体を生成し、その参照変数を介して実体手続きを呼び出すようにするか、実体を生成したくない場合は、クラス手続きを呼び出すようにしなければなりません。そうしないと、「illegal format specifier %8.2f」のような誤りになってしまいます。
更に、そうして
「%m.nf」形式で書式を設定しても、それを割り当てる図形要素によって、書式が有効になるものとならないものがあります。Label要素では有効ですが、Text要素では無効で、頭の空白文字は無視されてしまいます。
今回の例では、書式指定が利用できることを示すために、Label要素を使用していますので、「%m.nf」形式を指定しても問題はありません。しかし、後で、文字列を図形や画像と組み合わせて表示できるよう、Text要素を使うつもりですので、「%m.nf」形式は使用しませんでした。
前説が長くなりましたが、プログラムの作成に話を進めます。12月18日の投稿で作成した「Javaアプリケーションプロジェクト」内に、UIプログラム用のJavaFXファイルを作成します。以前に紹介した目覚まし時計のプログラムでは、JavaとJavaFXの企画(Project)を分けました。今回は、
同じJava企画にJavaFXプログラムも記述します。その方が、構築作業が楽になります。
私は、「ScadaClientUI」という名前でJavaFXファイルを作成しました。ファイルを作成したら、企画名をマウスで右押しし、表示される状況目録から、「プロパティー」を選択します。表示された窓の「カテゴリ」から「実行」を選択し、右側に表示された「引数」文字列枠に、作成したJavaFXのファイル名を包み名付きで設定します。私は、包み名を「client」としましたので、「client.ScadaClientUI」と設定しました。
ページの最後にコードを載せておきます。なお、目覚まし時計と同様に、編集用具上では「import client.ScadaClient;」の「
ScadaClient」の部分に、誤りを示す赤い波線が表示されますが、無視します。
企画を構築して実行すると、次のように、出力窓と枠付き窓に書式化された数値が、1秒ごとに表示されます(ときどき、表示の更新が欠落することがありますが、そこはご愛嬌ということで)。




package client;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import client.ScadaClient;

class ScadaDataModel {
attribute presenting: Boolean;
attribute timingGenerator: Number;
attribute numericData: Number;
attribute sc: ScadaClient;
function formatNumber(value:Number): String;
}

attribute ScadaDataModel.timingGenerator
= bind [1..60] dur 60000 linear continue if presenting;

function ScadaDataModel.formatNumber(value:Number): String {
return value format as <<##0.00>>;
}

trigger on new ScadaDataModel {
sc = new ScadaClient();
}

trigger on ScadaDataModel.timingGenerator = value {
numericData = sc.receiveData();
System.out.println("受信生データ: {numericData}");
System.out.println("書式化データ: {formatNumber(numericData)}");
}

Frame {
var scadaData = ScadaDataModel {presenting: true}
title: "JavaFX SCADA UI"
centerOnScreen: true
width: 400
height: 200
onClose: operation() {System.exit(0);}
content: Label {
text: bind scadaData.formatNumber(scadaData.numericData)
}
visible: true
}

2007年12月18日火曜日

JavaFX SCADA UI:模擬依頼者を作る

これから、JavaFXでSCADAのUIプログラムを構築していきます。最初に、UIプログラムにSCADA情報を提供する依頼者の処理を模擬するプログラムを、Javaで作ります。
依頼者からUIプログラムへは倍精度浮動小数点数の模擬情報を渡し、UIプログラムで数値指示計や図式指示計などにその値を数値や図式として表示させます。値は何でも良いのですが、とりあえず正弦関数の絶対値を100倍して渡すことにしました。依頼者の手続きが呼び出されるたびに、正弦関数の角度を1度ずつ増やして計算します。
まず、NetBeansで「Javaアプリケーションプロジェクト」(名前は任意)を作成します。同様に任意の「パッケージ
」と「クラス名」を指定して、Javaクラスを作成します。私は、パッケージ名に「client」、クラス名に「ScadaClient」を指定しました。
Javaクラスには実体手続きを定義し、上記の値を計算して戻り値として返すようにしました。また、主手続きで、作成した実体手続きの動作確認を行っています。
私が作成したコードを、最後に載せておきます(例によって、字下げが消えてるため、読みづらくてすみません)。
Javaアプリケーションプロジェクトを構築・実行すると、0~4度までの5つの正弦関数値が、出力窓に表示されます。

package client;

public class ScadaClient {

// 模擬SCADAデータ生成用作業変数。
private int i = -1;

public double receiveData() {
// 模擬SCADAデータを生成して返す。
i += 1;
if (i >= 360) {
i = 0;
}
return (double)(Math.abs(Math.sin(i * Math.PI / 180.0) * 100));
}

public static void main(String[] args) {
ScadaClient sc = new ScadaClient();
for (int i = 0; i <>

2007年12月17日月曜日

JavaFX SCADA UI:はじめに

前に、目覚まし時計を作りましたので、次は、SCADAのUIをJavaFXで作ってみたいと思います。
そもそも、JavaFXを含め、RIAの調査を始めたきっかけが、
SCADAのUIをAppletよりも簡単かつ表現力豊かに構築する方法はないかということでしたので、ようやく本題に突入と言ったところでしょうか。
SCADAとは、「Supervisory Control And Data Acquisition」の頭文字をとったもので、日本では「遠隔監視制御システム」などと呼ばれています。その名のとおり、諸所に分散した施設の設備や機器の運転情報を収集して処理するとともに、運転制御をも行う機構です
(監視だけを行うものもあります)
関連する技術分野が多方面に及ぶ(分散処理、画像処理、データベース、AI、電子郵便、携帯端末、RAS、などなど、数え上げると切りがない)ため、プログラミングの題材としては、なかなか面白いものです。
機構全体としては、かなり膨大なものになりうるのですが、ここではその内のUI部分を(それも、かなり単純化して)構築することにします。したがって、SCADA役務の依頼者と提供者間の通信の実現方法については考慮しません。何らかの方法で依頼者は
SCADA役務の提供を受けることができる、という前提で作成します。
例によって、開発環境としてNetBeansを使います。「それでは、…」と言いたいところですが、今日は前置きで終わりです。

2007年12月16日日曜日

JavaFX安定版の公開時期

JavaFX共同体討論場に載せられていた投稿によると、JavaFXのある程度の安定版公開時期は、来年のJavaOneのころになるようです。
今は、その黎明期の姿を垣間見ることができる、貴重なときなのかもしれません。

2007年12月13日木曜日

SunもJavaFX用GUI構築用具を開発中

ベルギーで開催されたJavaPolis 2007(ヨーロッパ版のJavaOne)において、SunのJames Gosling氏が基調講演を行い、SunがJavaFX用のGUI構築用具の原型版を開発中であることを披露されました。
JavaFXにおけるGUIプログラミングは、Javaにおけるそれよりはるかに容易になったとはいえ、やはりGUIはWYSIWYGで設計したいものです。NetBeansのMatisseようなGUI構築用具を一度使うと、正直、WYSIWYGで編集できないのはつらいものがあります。
JavaFX用のGUI
構築用具としては、ご存知のように、JFXBuilderというお試し用具もあるようですが、Sunのものはどのような機能を提供してくれるのか、今から楽しみです。

2007年12月11日火曜日

JavaFXファイルでJavaクラスを輸入するときはご注意を

現状の逐次解釈(Interpreter)版のJavaFXプログラムで、Javaのクラスを輸入(import)して使うときは、ちょっと注意が必要です。
少なくとも現状の逐次解釈版のJavaFX(一括変換(Compiler)版では調べていません)プログラムでは、java.lang.Systemクラスなどを輸入して使う場合、包み(Package)全体の輸入宣言「import java.lang.*;」で済ますと、思わぬ誤動作をすることがありますので注意が必要です。
私が経験した事例では、
java.lang包みの輸入宣言を行っているプログラムで、クラスに数値(Number)型の属性を定義し、その属性に実数を格納したときに、その整数部分しか格納されないというものでした(同じプログラム内で、クラス属性ではなく、数値型としてvar宣言した変数には実数として格納されるので、ややこしい)。
いろいろ調べた結果、包み全体ではなく、
java.lang.Systemクラスを明示的に宣言することで、この不具合が解消されることが分かりました。
もともと、Javaのプログラムでも、包み全体ではなく、なるべく個々のクラスごとに輸入宣言を記述する方が良いとはされていますが、包み全体の輸入を宣言してもプログラムが
動作をすることはありません。ですから、包みの宣言でも正常に動作するのが、本来の姿だとは思いますが。

2007年12月8日土曜日

JavaFX言語の展開:置換引金の定義場所と構文の変更

これまで、クラス属性の置換引金はクラス本体の外で定義していましたが、これからは、クラス属性の宣言の一部として記述するようになります。そして、置換引金の構文も、鍵語が「trigger on」から「on replace」へと、置換処理の定義であることを明示したものに変わります。
(旧)
class Foo {
attribute bar: Boolean = true;
}
trigger on Foo.bar = value {
if (bar == true) {
beep();
}
}
(新)
class Foo {
attribute bar: Boolean = true on replace {
if (bar == true) {
beep();
}
};
}

2007年12月7日金曜日

JavaFX言語の展開:クラス属性の初期値の設定場所

これまで、クラスの属性(attribute)が初期値を持つ場合、クラス本体の外でその設定を行っていました。これからは、Javaのように、クラスの本体内で属性の宣言と初期値の設定を行うようになります。
(旧)
class Foo {
attribute bar: Boolean;
}

attribute Foo.bar = true;
(新)
class Foo {
attribute bar: Boolean = true;
}

2007年12月6日木曜日

JavaFX言語の展開:関数と操作の統合

11月27日の投稿でも少しふれましたが、JavaFX言語も、コンパイラのサポートとともに、より明快で使いやすいものへと発展しようとしています。
そこで、JavaFXはどのように変わるのか、ということについて検討中の話題も含めて整理しておきたいと思います。
まずは、「functionとoperationの統合」についてです。
現状のインタプリタ版では、手続きの記述方法として関数(
function)と操作(operation)の2つが用意されています。関数は、return文を用いて必ず値を返し、結合演算子(bind)を明示的に指定しなくても、その本体は差分評価(incremental evaluation)されます。それに対して、操作の方は、必ずしも値を返す必要がなく、関数では使えない制御文が使えます。その代わり、操作の本体は関数のように暗黙的に差分評価されることはありません。
しかし、手続きとして関数と操作の2種類があると紛らわしいので、次のように関数
に統合されることになります(「旧」はこれまでのインタプリタ版で用いられていた構文を、「新」はコンパイラ版で新しく適用される構文を意味します)。
(旧)
class Foo {
function times2(x) { return x * 2; }
operation print(s) { System.out.println(s); }
}
(新)
class Foo {
function times2(x) { return x * 2; }
function print(s) { System.out.println(s); }
}

2007年12月5日水曜日

JavaFXでアラーム時計を作る(完成で~す!)

タイマの残り時間が0になるとアラームが鳴るようになりましたので、タイマの時間をUIから設定できるようにします。また、タイマが作動中に電話などの割り込みが入ったときは、カウントダウンを一時中断できるようにもしてみます。
完成したタイマのウィンドウイメージは、右の図のようになります(昔よく見かけた柱時計みたいになってしまいました)。
タイマの時間をスピナで設定し、スタートボタンを押すとカウントダウンを開始し、ストップボタンを押すとカウントダウンを停止します。時間設定のスピナは、残り時間のディジタル表示器の役目もしてくれます。
コード自体は、いたってシンプルなので、
JavaFX Script プログラミング言語 リファレンス深く学ぼう JavaFX Script 言語を参考に読んでいただければ、何を行っているかすぐ分かると思います。
と言うわけで、ごちゃごちゃ書かずに、変更版の
アラーム時計JavaFXファイル「AlarmClock.fx」のソースコード全体を載せてしまいます。

package timer;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.util.Date;
import java.lang.Math;
import java.lang.System;
import timer.Sound;

public class Timer {
private attribute elapsed: Number;
public attribute seconds: Number;
public attribute minutes: Integer;
public attribute hours: Integer;
public attribute count: Number;
public attribute startTime: Number;
public attribute elapsedTime: Number;
public attribute remainingTime: Number;
public attribute running: Boolean;
public attribute alarmEnabled: Boolean;
}

attribute Timer.elapsed = bind if running
then [1..10] dur 1000 linear while running continue if running
else 0;

trigger on Timer.elapsed = value {
var now = new Date();
elapsedTime = now.getTime() / 1000 - startTime;
remainingTime = count - elapsedTime;
running = if (elapsedTime >= count) then false else running;
seconds = remainingTime % 60;
minutes = ((Integer)remainingTime / 60) % 60;
hours = ((Integer)remainingTime / 60) / 60;
hours = if (elapsedTime >= count) then 0 else hours;
minutes = if (elapsedTime >= count) then 0 else minutes;
seconds = if (elapsedTime >= count) then 0 else seconds;
}

trigger on Timer.running = value {
if ((alarmEnabled == true) and (remainingTime <= 0)) {
alarmEnabled = false;
Sound.alarm();
}
}


Frame {
var t = Timer {seconds:0, minutes:0, hours:0, count:0,
running:false, alarmEnabled:false}
title: "JavaFX アラーム"
height: 350
width: 215
onClose: operation() {System.exit(0);}
content: BorderPanel {
center: Canvas {
content:
Group {
var font = new Font("Dialog", "PLAIN", 15)
var secs = bind t.seconds
var mins = bind t.minutes + secs / 60
var hrs = bind t.hours + mins / 60
content:
[Rect {
height: 400
width: 225
fill: Color {red:0.9, green:0.9, blue:0.9}
},
Circle {cx: 100, cy: 100, radius: 80, fill: white, stroke: black, strokeWidth: 1},
Group {
transform: translate(100, 100)
content: foreach (i in [1..12])
Text {
var radians = Math.toRadians(30 * i - 90)
transform: [translate((70 * Math.cos(radians)), (70 * Math.sin(radians)))]
content: "{i}"
valign: MIDDLE
halign: CENTER
}
},
Group {
transform: translate(100, 100)
var hourHand =
Line {x1: 0, y1: 0, x2: 0, y2: -35,
strokeWidth: 4, stroke: black
transform: bind rotate((hrs * 30), 0, 0)
}
var minuteHand =
Line {x1: 0, y1: 0, x2: 0, y2: -55,
strokeWidth: 2, stroke: blue,
transform: bind rotate((mins * 6), 0, 0)
}
var secondHand =
Line {x1: 0, y1: 0, x2: 0, y2: -75,
strokeWidth: 1, stroke: red,
transform: bind rotate((t.seconds * 6), 0, 0)
}
content: [hourHand, minuteHand, secondHand]
},
Circle {cx: 100, cy: 100, radius: 3, fill: black, stroke: black }]
},
}
bottom: Box {
background: white
orientation: VERTICAL
content:
[FlowPanel {
content:
[Spinner {
enabled: bind not(t.running)
min: 0
max: 23
value: bind t.hours
},
SimpleLabel {
enabled: bind not(t.running)
text: "時"
}]
},
FlowPanel {
content:
[Spinner {
enabled: bind not(t.running)
min: 0
max: 59
value: bind t.minutes
},
SimpleLabel {
enabled: bind not(t.running)
text: "分"
}]
},
FlowPanel {
content:
[Spinner {
enabled: bind not(t.running)
min: 0
max: 59
value: bind t.seconds
},
SimpleLabel {
enabled: bind not(t.running)
text: "秒"
}]
},
FlowPanel {
content:
[Button {
enabled: bind t.running
text: "ストップ"
action: bind operation() {
t.running = false;
}
},
RigidArea {
width: 20
},
Button {
enabled: bind not(t.running)
text: "スタート"
action: operation() {
var date = new Date();
t.count = t.hours * 3600 + t.minutes * 60 + t.seconds;
t.startTime = date.getTime() / 1000;
t.remainingTime = t.count;
if (t.count <> 0) {
t.alarmEnabled = true;
t.running = true;
}
}
}]
}]
}
}
visible: true
}


2007年12月4日火曜日

JavaFXでアラーム時計を作る(3)

アラーム音再生やメッセージ表示の準備ができたので、JavaFXのプログラムからこれを呼び出せるようにしてみます(以降、いずれもNetBeansで作業を行う場合を例に記述しています)。
作成したアラーム出力Javaアプリケーションプロジェクトの、「〈プロジェクト名〉\build\classes\〈パッケージ名〉\」ディレクトリの下に構築されたクラスファイル(私の場合は、「Sound..class」)と、コピーしておいたサウンドファイル(同様に、「TestMusic.wav」)を、ともに、アラーム時計JavaFXアプリケーションプロジェクトの方の「〈プロジェクト名〉\build\classes\〈パッケージ名〉\」ディレクトリの下にコピーします。
そして、12月2日の投稿で紹介した上記のアラーム時計
JavaFXファイル(同じく「AlarmClock.fx」)に、以下の変更を加えます。
まず、アラーム出力クラスのインポート文を追加します(以下、いずれも私が指定したパッケージ名やファイル名で記述します)。

import timer.Sound;

このとき、「Sound」の下に赤い波線が表示され、「undefined type ...」のエラーアイコンが表示されますが、無視します(エラーメッセージが表示されますが、ちゃんとクラスファイルを認識しています)。
そして、Timer.elapsedの置換triggerアクションの定義に続けて、次のTimer.runningの置換triggerアクションの定義を追加します。

trigger on Timer.running = value {
if (remainingTime <= 0) {Sound.alarm();} }

これで、準備が整いました。最後に、アラーム時計
アプリケーションのプロジェクトを構築しなおして(このとき、「Clean and Build Project」を実行しないように注意します。せっかくコピーしたアラーム出力クラスファイルとサウンドファイルが消されてしまいます)、プロジェクトを実行します。
アラーム時計のウィンドウが表示され、タイマに設定した時間(現在は10秒固定)が経過すると、アラーム音が鳴り、メッセージウィンドウが表示されます。









ここまでくれば、アラームタイマはほとんど完成したも同然です。あとは、タイマの時間を設定できるようにするだけですが、今日はここまで。いつものように、ソースコード全体を載せておきます。

package timer;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.util.Date;
import java.lang.Math;
import java.lang.System;
import timer.Sound;

public class Timer {
private attribute elapsed: Number;
public attribute seconds: Number;
public attribute minutes: Integer;
public attribute hours: Integer;
public attribute count: Number;
public attribute startTime: Number;
public attribute elapsedTime: Number;
public attribute remainingTime: Number;
public attribute running: Boolean;
}

attribute Timer.elapsed = bind if running
then [1..10] dur 1000 linear while running continue if running
else 0;

trigger on Timer.elapsed = value {
var now = new Date();
elapsedTime = now.getTime() / 1000 - startTime;
remainingTime = count - elapsedTime;
running = if (elapsedTime >= count) then false else true;
seconds = remainingTime % 60;
minutes = ((Integer)remainingTime / 60) % 60;
hours = ((Integer)remainingTime / 60) / 60;
hours = if (elapsedTime >= count) then 0 else hours;
}

trigger on Timer.running = value {
if (remainingTime <= 0) {Sound.alarm();}
}

var date = new Date();

Frame {
var t = Timer {seconds:10, minutes:0, hours:0, count:10,
startTime:(date.getTime() / 1000), running:true}
title: "JavaFX アラーム"
height: 225
width: 215
onClose: operation() {System.exit(0);}
content:
Canvas {
content:
Group {
var font = new Font("Dialog", "PLAIN", 15)
var secs = bind t.seconds
var mins = bind t.minutes + secs / 60
var hrs = bind t.hours + mins / 60
content:
[Rect {
height: 225
width: 225
fill: white
},
Circle {cx: 100, cy: 100, radius: 80, fill: white, stroke: black, strokeWidth: 1},
Group {
transform: translate(100, 100)
content: foreach (i in [1..12])
Text {
var radians = Math.toRadians(30 * i - 90)
transform: [translate((70 * Math.cos(radians)), (70 * Math.sin(radians)))]
content: "{i}"
valign: MIDDLE
halign: CENTER
}
},
Group {
transform: translate(100, 100)
var hourHand =
Line {x1: 0, y1: 0, x2: 0, y2: -35,
strokeWidth: 4, stroke: black
transform: bind rotate((hrs * 30), 0, 0)
}
var minuteHand =
Line {x1: 0, y1: 0, x2: 0, y2: -55,
strokeWidth: 2, stroke: blue,
transform: bind rotate((mins * 6), 0, 0)
}
var secondHand =
Line {x1: 0, y1: 0, x2: 0, y2: -75,
strokeWidth: 1, stroke: red,
transform: bind rotate((t.seconds * 6), 0, 0)
}
content: [hourHand, minuteHand, secondHand]
},
Circle {cx: 100, cy: 100, radius: 3, fill: black, stroke: black }]
},
}
visible: true
}

2007年12月3日月曜日

JavaFXでアラーム時計を作る(2)

せっかく、時計がカウントダウン表示されるようになったので、時間になったらアラーム音が鳴って、メッセージウィンドウが表示されるようにしてみます。
残念ながら、現状のJavaFX自体には、サウンド再生サポートはありません(アニメーションと同期させてサウンドを再生するなどの応用を考えると、JavaFXでサウンドの再生やアニメーションとの同期などを簡潔に記述できるといいのですが…)。そこで、Javaのプログラムで音の再生とメッセージの表示を行うクラスを作成し、JavaFXのプログラムからそれを呼び出すことにしました。
NetBeansでJavaアプリケーションのプロジェクト(名前は任意)を作成し、パッケージ名を「timer」、ファイル名を「Sound.java」として、クラスファイルを作成します(パッケージ名は、作成済みのJavaFXファイルで指定したパッケージ名にあわせました。ファイル名は、その名前でJavaFXプログラムから参照しさえすれば、何でもかまいません)。
サウンドの再生には、Java Appletなどで使うAudioClipクラスを利用します。ただ、Appletを作るわけではないので、java.applet.AppletクラスのクラスメソッドnewAudioClip()でサウンドファイルを読み込むようにします。このメソッドの引数には、サウンドファイルのURLを指定しないといけないのですが、それでは不便なので、作成するクラスの基底クラスclassからgetResource()メソッドを呼びだして、サウンドファイルのURLを取得し、引数に指定します。
AudioClipを生成したら、あとは、そのplay()メソッドを呼び出してサウンドをワンタイム再生するか、loop()メソッドとstop()メソッドで繰り返し再生とその停止を行うようにします。私は、メッセージウィンドウの表示に先立ってサウンドの繰り返し再生を起動し、メッセージウィンドウが閉じられたら再生を停止するようにしました。
サウンドファイルのタイプとしては、「.au」ファイルと「.wav」ファイルのどちらも指定できます。自分のパソコンから好きな音のファイルを捜してきて(ネットワークからダウンロードしてもいいのですが、得体の知れないものは怖いので)、そのファイルを、作成するクラスの「.class」ファイルが出力されるディレクトリ(プロジェクトファイルの下の「build\classes\〈パッケージ名〉\」ディレクトリ)にコピーします(このとき、ファイル検索ウィンドウからファイルを持ってくるときは、ファイルの移動を行わないように注意してください。不用意にドラッグするとファイルが移動され、元の場所からなくなってしまいます)。私は、「TestMusic.wav」という、ちょっと軽快な音楽が演奏されるファイルを選びました。
メッセージウィンドウの表示には、おなじみのJOptionPane.showMessageDialog()を使用しました。
showMessageDialog(VBのMsgboxに相当)は、ちょっとしたデバッグなどにも使えて便利なのですが、ダイアログウィンドウが必ずしもウィンドウスタックの最前面に表示されるわけではない、という難点(?)があります。この点については、同じように不便だと思われた方がいらしたようで、改造版を作られています。興味のある方は、Googleなどで検索してみてください。
作成したプロジェクトを構築して実行すると、サウンドが再生されて、メッセージダイアログが表示されます。
話が長くなってしまいましたので、今日はここまでです。最後にソースを載せておきます(いつものように、インデントが消えてしまっています。HTMLで編集すればいいのでしょうけれど…)。

package timer;

import java.applet.Applet;
import java.applet.AudioClip;
import javax.swing.JOptionPane;

public class Sound {

public static void alarm() {
AudioClip ac
= Applet.newAudioClip(Sound.class.getResource("TestMusic.wav"));
// ac.play();
ac.loop();
JOptionPane.showMessageDialog(null, "時間ですよ!", "警告!警告!", JOptionPane.WARNING_MESSAGE);
ac.stop();
}

public static void main(String[] args) {
Sound.alarm();
}
}

2007年12月2日日曜日

JavaFXでアラーム時計を作る

11月28日の投稿でアナログ時計のアプリケーションを紹介しましたが、それにちょっと手を入れて、アラームタイマを作ってみます。まずは、10秒固定でカウントダウン表示するようにしてみます。
NetBeansで、適当な名前でJavaFXプロジェクトを作ります。今回は、package名を設定することにし、「timer」を指定しました。ファイル名も何でも良いのですが、「AlarmClock.fx」としました。
まず、11月28日に投稿したコードをそのままコピーしてファイルに貼り付けます。
次に、コードの先頭にpackage宣言

package timer;

を追加します。次に、Timerクラスのminutesとhoursの型をIntegerに変更します。

public attribute minutes: Integer;
public attribute hours: Integer;

更に、Timerクラスに次の4つのattributeを追加します。

public attribute count: Number;
public attribute startTime: Number;
public attribute elapsedTime: Number;
public attribute remainingTime: Number;

そして、Timer.elapsedの設定コードの「continue if true」の部分を「continue if running」と書き換えます。

attribute Timer.elapsed = bind if running
then [1..10] dur 1000 linear while running continue if running
else 0;

次に、Timer.elapsedの置換triggerアクションの定義を次のように変えます。

trigger on Timer.elapsed = value {
var now = new Date();
elapsedTime = now.getTime() / 1000 - startTime;
remainingTime = count - elapsedTime;
running = if (elapsedTime >= count) then false else true;
seconds = remainingTime % 60;
minutes = ((Integer)remainingTime / 60) % 60;
hours = ((Integer)remainingTime / 60) / 60;
hours = if (elapsedTime >= count) then 0 else hours;
}

そして、Frameインスタンスの定義の前に次の行を追加し、

var date = new Date();

Frameインスタンス内のTimerインスタンス生成コードを、次のように変更すれば完成です。

var t = Timer {seconds:10, minutes:0, hours:0, count:10,
startTime:(date.getTime() / 1000), running:true}

とりあえず、カウントダウンする10秒は、ハードコードしました。最後に全体のコードを載せておきます。

package timer;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.util.Date;
import java.lang.Math;
import java.lang.System;

public class Timer {
private attribute elapsed: Number;
public attribute seconds: Number;
public attribute minutes: Integer;
public attribute hours: Integer;
public attribute count: Number;
public attribute startTime: Number;
public attribute elapsedTime: Number;
public attribute remainingTime: Number;
public attribute running: Boolean;
}

attribute Timer.elapsed = bind if running
then [1..10] dur 1000 linear while running continue if running
else 0;

trigger on Timer.elapsed = value {
var now = new Date();
elapsedTime = now.getTime() / 1000 - startTime;
remainingTime = count - elapsedTime;
running = if (elapsedTime >= count) then false else true;
seconds = remainingTime % 60;
minutes = ((Integer)remainingTime / 60) % 60;
hours = ((Integer)remainingTime / 60) / 60;
hours = if (elapsedTime >= count) then 0 else hours;
}

var date = new Date();

Frame {
var t = Timer {seconds:10, minutes:0, hours:0, count:10,
startTime:(date.getTime() / 1000), running:true}
title: "JavaFX アラーム"
height: 225
width: 215
onClose: operation() {System.exit(0);}
content:
Canvas {
content:
Group {
var font = new Font("Dialog", "PLAIN", 15)
var secs = bind t.seconds
var mins = bind t.minutes + secs / 60
var hrs = bind t.hours + mins / 60
content:
[Rect {
height: 225
width: 225
fill: white
},
Circle {cx: 100, cy: 100, radius: 80, fill: white, stroke: black, strokeWidth: 1},
Group {
transform: translate(100, 100)
content: foreach (i in [1..12])
Text {
var radians = Math.toRadians(30 * i - 90)
transform: [translate((70 * Math.cos(radians)), (70 * Math.sin(radians)))]
content: "{i}"
valign: MIDDLE
halign: CENTER
}
},
Group {
transform: translate(100, 100)
var hourHand =
Line {x1: 0, y1: 0, x2: 0, y2: -35,
strokeWidth: 4, stroke: black
transform: bind rotate((hrs * 30), 0, 0)
}
var minuteHand =
Line {x1: 0, y1: 0, x2: 0, y2: -55,
strokeWidth: 2, stroke: blue,
transform: bind rotate((mins * 6), 0, 0)
}
var secondHand =
Line {x1: 0, y1: 0, x2: 0, y2: -75,
strokeWidth: 1, stroke: red,
transform: bind rotate((t.seconds * 6), 0, 0)
}
content: [hourHand, minuteHand, secondHand]
},
Circle {cx: 100, cy: 100, radius: 3, fill: black, stroke: black }]
},
}
visible: true
}

2007年12月1日土曜日

コマンドラインからのJavaFXインタプリタの起動

(以下の例はWindowsの場合です。ほかのOS環境では読み替えてください)
コマンドラインからJavaFXインタプリタを起動するには、まず、PATH環境変数にOpenJFXをインストールしたディレクトリの下にある「trunk\bin」ディレクトリを設定します。例えば、OpenJFXをCドライブの下に「OpenJFX」という名前でインストールした場合は、「C:\OpenJFX\trunk\bin;」をPATH環境変数に追加します。
そして、コマンドプロンプトウィンドウを開いてJavaFXアプリケーションを保存したディレクトリに移動し、次のようにコマンドを入力します(「JfxApp.fx」アプリケーションを起動する場合の例)。
>javafx JfxApp
もし、CLASSPATH環境変数を設定しているために「Exception in thread "main" java.lang.NoClassDefFoundError: ...」と、エラー表示されてアプリケーションを起動できないときは(私の場合がそうでした)、次のようにコマンドを入力して、CLASSPATHの値を空にしてから実行します。
>set classpath=
>javafx JfxApp
(「set classpath=」で、現在のコマンドプロンプトウィンドウのCLASSPATH の設定を解除します。ほかのコマンドプロンプトウィンドウのCLASSPATH設定には影響を与えません)。