私は、まだ、NetBeans 6.5を使っています(6.5.1ではありません)。
過日、JavaFX 1.2 SDKとJDK 6 Update 13を導入した際に、NetBeans用のJavaFX追加接続プログラム(plug-in)もJavaFX 1.2相当のものを導入し直しました。ところが、JavaFXのプログラムを実行しようとすると、誤りが発生して実行できませんでした(見本の一部には実行できるものもありましたが・・・)。
追加接続プログラムに問題があり、その版が更新されれば実行できるようになるだろうと放っておきました。
ところが、本日たまたま古いJDKを整理し、それまでJDK 6 Update 11で動いていたNetBeans 6.5がJDK 6 Update 13で動くようになったところ、JavaFX 1.2のプログラムも正常に実行できるようになりました。
JDKを整理しなくても、NetBeansの設定変更でうまく対処できるのかもしれません。私のように、「追加接続プログラムの問題だ」とあきらめてしまう前に、一度調べてみると良いかもしれません。
なお、先ごろ公開されたNetBeans 6.7へは、JavaFX 1.2追加接続プログラムをうまく導入できないようです(NetBeans 6.7の導入ページにも、JavaFXは、NetBeans 6.5.1でのみ利用できまるとなってはいるのですが・・・)。John O'Conner氏が何とかしようと奮闘(?)されているようなのですが。興味のある方は、彼のBlogをご覧ください。
2009年7月2日木曜日
2009年2月12日木曜日
Servletから周期的にデータを取得するプログラムを作りました
Servletから周期的にデータを取得して表示を更新する、JavaFXの役務依頼器(client)プログラムを作ってみました。

Servletへ1秒ごとにHTTPのGET要求を発行して数値データを文字列として取得し、棒図式と数字で実時間表示します(遠隔監視(Remote Monitor)を模擬した表示にしてみました。Servletは、監視データの代わりに現在の秒値を返します)。
コードの例は、こちらに載せています。
Servletへ1秒ごとにHTTPのGET要求を発行して数値データを文字列として取得し、棒図式と数字で実時間表示します(遠隔監視(Remote Monitor)を模擬した表示にしてみました。Servletは、監視データの代わりに現在の秒値を返します)。
コードの例は、こちらに載せています。
2009年2月10日火曜日
Servletと通信するプログラムを作りました
Servletと通信するJavaFXの役務依頼器(client)プログラムを作ってみました。

「OK」ボタンが押されると、SwingTextFieldに入力された文字列を、HTTPのGET要求としてServletへ送ります。Servletでは、HttpServletRequest#getParameter()で送られてきた文字列を読み込み、前後に文字列を付加して依頼器へ送り返します。依頼器は、Servletから受信した文字列をTextとして表示します。
コードの例は、こちらに載せています。
「OK」ボタンが押されると、SwingTextFieldに入力された文字列を、HTTPのGET要求としてServletへ送ります。Servletでは、HttpServletRequest#getParameter()で送られてきた文字列を読み込み、前後に文字列を付加して依頼器へ送り返します。依頼器は、Servletから受信した文字列をTextとして表示します。
コードの例は、こちらに載せています。
2009年2月8日日曜日
Servlet接続プログラム作成の参考文書
役務供給器(server)と通信する依頼器(client)側のJavaFXプログラムを開発する場合、試験用の供給器側プログラムをServletとして作成する、というのもひとつの手だと思います。NetBeansを利用すると、Servletプログラムの作成から配備までを容易に行うことができます。
今日は、Servletと接続するプログラムを作成する際に参考になる文書を紹介します。
Connecting to Servlets
今日は、Servletと接続するプログラムを作成する際に参考になる文書を紹介します。
Connecting to Servlets
2009年2月5日木曜日
段階式書式指定(CSS)に関する参考文書
段階式書式指定(CSS(Cascading Style Sheets))を利用したJavaFX応用プログラムを開発する上で、参考になるWeb日誌と投稿を紹介します。
次のWeb日誌は、CSSとSkinクラスやControlクラスの関係について、簡潔な例を用いていて分かりやすく説明しています(表題には「Applets」とありますが、卓上型(desktop)プログラムとして実行できます)。
Styling your Applets with CSS
上のWeb日誌の例を試すときは、CSSファイル「style.css」を台本ファイルと同じディレクトリに置き、台本ファイル「Main.fx」の行
stylesheets: ["http://piliq.com/javafx/tutorials/css/style.css"];
を、次のように書き替えてから一括変換してください。
stylesheets: ["{__DIR__}style.css"];
CSSファイルの設定を書き替えると、台本ファイルを一括変換しなおさなくても、(台本を再実行しただけで)変更が反映されるのが分かります。
次の投稿には、段階式書式指定について詳しく説明されています。
CSS support control and skinning
上の投稿と次のWeb日誌をあわせて読むと、より分かりやすいかもしれません。
JavaFX in Style
次のWeb日誌は、CSSとSkinクラスやControlクラスの関係について、簡潔な例を用いていて分かりやすく説明しています(表題には「Applets」とありますが、卓上型(desktop)プログラムとして実行できます)。
Styling your Applets with CSS
上のWeb日誌の例を試すときは、CSSファイル「style.css」を台本ファイルと同じディレクトリに置き、台本ファイル「Main.fx」の行
stylesheets: ["http://piliq.com/javafx/tutorials/css/style.css"];
を、次のように書き替えてから一括変換してください。
stylesheets: ["{__DIR__}style.css"];
CSSファイルの設定を書き替えると、台本ファイルを一括変換しなおさなくても、(台本を再実行しただけで)変更が反映されるのが分かります。
次の投稿には、段階式書式指定について詳しく説明されています。
CSS support control and skinning
上の投稿と次のWeb日誌をあわせて読むと、より分かりやすいかもしれません。
JavaFX in Style
2009年1月29日木曜日
作成時に列幅が指定可能な表部品を作ってみました
「Planet JFX Wiki」に、javax.swing.JTableを使ったTable部品の見本が載っています。それにちょっと手を加えて、表を作成する際に列幅を指定できるようにしてみました。
コードの例は、こちらに載せています。
2009年1月27日火曜日
HTTP、XMLおよびJSONに関する参考文書
JavaFXといえばRIAなので、今日は、HTTP、XMLおよびJSONの処理に関して、参考になるWeb日誌と応用見本のWeb面を紹介したいと思います。
HTTPとXMLに関する簡単な応用例として、次の2つのWeb日誌が参考になります。
Parsing a XML Sandwich with JavaFX
Reading Twitter with JavaFX
HTTPとXMLの応用見本としては、次のものが簡潔で分かりやすいと思います。
Interesting Photos : JavaFX Example Application
また、次のものは、HTTP、XMLおよびJSONの3つとも取り上げています。
Location-Based Coffee Shop Search
HTTPとXMLに関する簡単な応用例として、次の2つのWeb日誌が参考になります。
Parsing a XML Sandwich with JavaFX
Reading Twitter with JavaFX
HTTPとXMLの応用見本としては、次のものが簡潔で分かりやすいと思います。
Interesting Photos : JavaFX Example Application
また、次のものは、HTTP、XMLおよびJSONの3つとも取り上げています。
Location-Based Coffee Shop Search
2009年1月22日木曜日
「JavaFXプログラミング言語の基礎補遺」を公開しました
JavaFXに関するWeb面で、「JavaFXプログラミング言語の基礎補遺」を公開しました。「JavaFXプログラミング言語の基礎」では取り上げなかった細かなことや、やや発展的な事項について記述しました。
2009年1月11日日曜日
2008年12月24日水曜日
JavaFX追加接続の下見機能でWYSIWYG開発が可能に
ちょっと古めかしい言葉を出してしまいましたが、MSのVisual StudioやNetBeansのMatisseのようなWYSIWYG環境でのソフトウェ開発が、JavaFXでも可能になりました。
それは、NetBeansなどのIDE用に提供されている、JavaFX追加接続(plug-in)プログラムの下見(preview)機能を利用した開発のことです。Visual StudioやMatisseのような、直接操作方式ではありませんが、編集作業と並行して実行時の姿が実時間で見られるので、かなり開発効率が上がります。
事前評価(preview)版のJavaFX追加接続にもその原型はあったのですが、原始コードの編集領域を分割して表示するため編集領域が狭くなる、コードによってはうまく表示ができないなど、正直言って「今一」の感が否めませんでした。しかし、JavaFX 1.0対応版では、これらの点かなり改善さているようです(まだ、それほど試した訳ではないので、問題点の有無を把握し切れてはいませんが)。
少なくとも、下見画面がIDEの窓とは独立した専用の窓に表示されるため、編集領域が狭くなることはありません。直接操作方式ではありませんが、元々JavaFX言語は宣言型の言語であり、VBやJavaに比べれば、画面表示とコードの対応が取りやすいこともあって、それほど苦にはなりません(もちろん、図形部品をマウスで引っ張りまわして好きな位置に配置したり、寸法を自由に変えられることに越したことはないのですが)。
それは、NetBeansなどのIDE用に提供されている、JavaFX追加接続(plug-in)プログラムの下見(preview)機能を利用した開発のことです。Visual StudioやMatisseのような、直接操作方式ではありませんが、編集作業と並行して実行時の姿が実時間で見られるので、かなり開発効率が上がります。
事前評価(preview)版のJavaFX追加接続にもその原型はあったのですが、原始コードの編集領域を分割して表示するため編集領域が狭くなる、コードによってはうまく表示ができないなど、正直言って「今一」の感が否めませんでした。しかし、JavaFX 1.0対応版では、これらの点かなり改善さているようです(まだ、それほど試した訳ではないので、問題点の有無を把握し切れてはいませんが)。
少なくとも、下見画面がIDEの窓とは独立した専用の窓に表示されるため、編集領域が狭くなることはありません。直接操作方式ではありませんが、元々JavaFX言語は宣言型の言語であり、VBやJavaに比べれば、画面表示とコードの対応が取りやすいこともあって、それほど苦にはなりません(もちろん、図形部品をマウスで引っ張りまわして好きな位置に配置したり、寸法を自由に変えられることに越したことはないのですが)。
2008年12月23日火曜日
JavaFX 1.0 SDK Update 1公開
JavaFX 1.0 SDK Update 1が、12月19日に公開されました。公開直後は、導入用プログラムが更新前のものしか公開されていなかったのですが、今日改めて見てみたら、JavaFX 1.0 SDK Update 1用の導入プログラムに変更されていましたので、早速導入してしまいました。
2008年12月16日火曜日
狼おじさんになってしまうところでした
昨日、私の不注意から、JavaFXのcontinue式に不具合があるとの投稿をしてしまいました。
再度確認したところ、continue式には、私が指摘したような不具合はないことが分かりました。
ここに、訂正して、お詫びいたします。
なお、誤った情報をこれ以上広めたくありませんので、まことに勝手ながら昨日の投稿は削除いたしました。
貴重なご指摘をいただいた、雪羽 さんどうもありがとうございました。ここに、お礼を述べさせていただきます。
再度確認したところ、continue式には、私が指摘したような不具合はないことが分かりました。
ここに、訂正して、お詫びいたします。
なお、誤った情報をこれ以上広めたくありませんので、まことに勝手ながら昨日の投稿は削除いたしました。
貴重なご指摘をいただいた、雪羽 さんどうもありがとうございました。ここに、お礼を述べさせていただきます。
2008年12月13日土曜日
NetBeans用JavaFX追加接続プログラム日本語表示不具合
JavaFX言語の入門書「JavaFXプログラミング言語の基礎」を書き始め、まだ第1章だけですがWebページで公開しました。
そこに載せている見本コードの動作確認をしていて分かったのですが、NetBeans用JavaFX追加接続プログラム(JavaFX 1.0相当版:JavaFX 1.0バージョン1.4.3、JavaFX SDK for Windowsバージョン1.3.3)の日本語表示には、まだ未修正の不具合があります。
これは、以前の投稿で記したものですが、標準出力に日本語文字列を表示するようなプログラムを一括変換すると誤りが発生するというものです。同じプログラムを、JavaFX 1.0 SDKのjavafxcでコマンド行から一括変換すると正常に変換でき、日本語が表示できます。したがって、Windows上で動作するNetBeans用JavaFX追加接続プログラムの標準出力への日本語表示に、問題が残っているようです。ちなみに、枠付き窓に日本語が表示できない不具合は、修正されています。
話は変わりますが、以前から、JavaFX言語は、表現力が高いばかりでなく、初心者がプログラミングを習得するのにも適した言語であると思っています。Java言語ですと、何はともあれ、「class」を定義しなければなりません。そこで、クラスとは何なのかの話をいきなり始めるか、「とりあえず、おまじない(?)と思って記述してください」と、クラスに関する説明を後回しにするかの選択をしなければなりません。
いずれにしても、説明する方も、また説明される方もあまりすっきりしません。
その点、JavaFXなら、println()関数の説明をするぐらいで「Hello world!」プログラムが書けますので、話が簡単です。また、JavaFX 1.0では、データ型の推定機能が復活しましたので、その点でもプログラミングが楽になります。しかも、動画や多重媒体応用プログラムが簡潔に書けるまでになります。
「JavaFX言語でプログラミングを学ぶ」といった講座が一般的になる、そんな日が早く来るといいのですが。
そこに載せている見本コードの動作確認をしていて分かったのですが、NetBeans用JavaFX追加接続プログラム(JavaFX 1.0相当版:JavaFX 1.0バージョン1.4.3、JavaFX SDK for Windowsバージョン1.3.3)の日本語表示には、まだ未修正の不具合があります。
これは、以前の投稿で記したものですが、標準出力に日本語文字列を表示するようなプログラムを一括変換すると誤りが発生するというものです。同じプログラムを、JavaFX 1.0 SDKのjavafxcでコマンド行から一括変換すると正常に変換でき、日本語が表示できます。したがって、Windows上で動作するNetBeans用JavaFX追加接続プログラムの標準出力への日本語表示に、問題が残っているようです。ちなみに、枠付き窓に日本語が表示できない不具合は、修正されています。
話は変わりますが、以前から、JavaFX言語は、表現力が高いばかりでなく、初心者がプログラミングを習得するのにも適した言語であると思っています。Java言語ですと、何はともあれ、「class」を定義しなければなりません。そこで、クラスとは何なのかの話をいきなり始めるか、「とりあえず、おまじない(?)と思って記述してください」と、クラスに関する説明を後回しにするかの選択をしなければなりません。
いずれにしても、説明する方も、また説明される方もあまりすっきりしません。
その点、JavaFXなら、println()関数の説明をするぐらいで「Hello world!」プログラムが書けますので、話が簡単です。また、JavaFX 1.0では、データ型の推定機能が復活しましたので、その点でもプログラミングが楽になります。しかも、動画や多重媒体応用プログラムが簡潔に書けるまでになります。
「JavaFX言語でプログラミングを学ぶ」といった講座が一般的になる、そんな日が早く来るといいのですが。
2008年12月5日金曜日
JavaFX 1.0 SDKが公開されました
JavaFX 1.0 SDKが公開されましたので、早速導入してみました。
JavaFX 1.0 SDKは、JavaFX.comのStartページから導入できます。なお、同ページには、「Learning the JavaFX Script Programming Language」という指導書への連結が載っていますが、その指導書の中に記載されているJavaFX 1.0 SDK導入ページの連結をたどると、本日(12/5)現在では、事前評価(Preview)版の導入ページに案内されてしまいます。上記のStartページから導入すると、正式版が導入できます。
また、上記の指導書には、JDK 5またはJDK 6が必要と記されていますが、JavaFX 1.0 SDKを導入しようとすると、最新のJDK 6u11を導入するように求められます。
JavaFX 1.0 SDKは、JavaFX.comのStartページから導入できます。なお、同ページには、「Learning the JavaFX Script Programming Language」という指導書への連結が載っていますが、その指導書の中に記載されているJavaFX 1.0 SDK導入ページの連結をたどると、本日(12/5)現在では、事前評価(Preview)版の導入ページに案内されてしまいます。上記のStartページから導入すると、正式版が導入できます。
また、上記の指導書には、JDK 5またはJDK 6が必要と記されていますが、JavaFX 1.0 SDKを導入しようとすると、最新のJDK 6u11を導入するように求められます。
2008年10月8日水曜日
JavaFX Air Hockey program
I made a Air Hockey game in JavaFX.
As for the program, named AirHockey.fx, plese refer to the code (modified to conform to JavaFX 1.0).
When you run the program, an air hockey game table with two aqua color mallets are displayed on your screen. Computer's goal (the upper one) and your goal (the lower one) are displayed in burlywood color.
Push the "SET" button to start the game. Then, a white puck appears on your side.
You can manipulate your mallet by the mouse as long as it resides in your area.
A velocity of your mallet is computed from its displacement by applying the scaling transformation. The scaling is modulated by the velocityScaling factor. You can make your mallet's velocity more real by moving the factor closer to 1.0.
You can also regulate the bounce back speed by adjusting the elastic modulus elastic.
In this program, a computer simply move its mallet back and forth. It has no ability to take the offensive. It is your business to make your opponent cool.
Enjoy yourself!
2008年10月7日火曜日
JavaFXで空中ホッケー遊技(Air hockey game)を作る
頭の中が沸騰しそうになったとき、しばらく空っぽにするのにいいですよ。
プログラムを実行すると、右の図のような空中ホッケーの卓が表示されます。薄茶色で表示されているところが得点圏(goal)で、青緑色で表示されているのが槌(mallet)です。
「SET」ボタンを押すと、図の位置に白い円盤(puck)が表示されます(プログラムを実行した直後や、得点した後は、円盤が表示されません)。
人間様用の槌(下側が「人間様」の領域で、上側が「計算機様」の領域です)をマウスで動かして(人間様の領域内にマウスを持っていくと、その後を槌が追います)、円盤を打ってみてください。
槌を速く動かしすぎると、槌で円盤を引きずるような動作をします。これは、マウスの位置の変化量から槌の速度を計算する際に、スケーリング変換を行っているためです。velocityScalingの値で、変換係数を調節してください。
また、円盤が卓の壁や槌に衝突してはね返されるとき、弾性係数を考慮して動きを計算しています。elasticの値で、弾性率を変えてみてください。
今回のものでは、計算機様は、ただ槌を往復させるだけで、何の芸も持ち合わせていません。計算機様にどんな芸を持たせるかが、これからの楽しみです。何の制限も持たせなければ、計算機様をいくらでも強くできますが、それでは勝負になりません。強過ぎても弱過ぎてもだめですし、(乱数に因らない)偶然の要素もないと面白くない、その辺りが工夫のしどころです。
最後に、原始コードを載せておきます。なお、前回の投稿で、仕立て節に定義した関数がうまく機能しないと記しましたが、(部品化を考慮して)仕立て節を別の台本ファイルで定義したことがまずかっただったようです。このプログラムのように、同じ台本ファイル内に仕立て節を定義する限り、特に問題はありませんでした。
/*
* AirHockey.fx
* The air hockey game.
*
* Created and modified:
* V 1.0.0 2008/09/25
*/
package game;
import javafx.animation.*;
import javafx.application.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import java.lang.Math;
import java.lang.System;
/**
* @author terra
*/
var goalCorners: Disc[];
var mouseEvent: MouseEvent;
var tableWidth: Number = 300;
var tableHeight: Number = 400;
var goalWidth: Number = 80;
var goalCornerRadius: Number = 5;
var puckRadius: Number = 10;
var malletRadius: Number = 15;
var hittingAreaDepth: Number = tableHeight / 2 - malletRadius;
var elastic: Number = 0.95;
var velocityScaling = 0.35;
var malletCMaxV = 2; // Maximum velocity of the computer's mallet.
var timingGenerator: Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time: 20ms
action: function(): Void {
malletC.move();
puck.move();
malletH.calcuVelocity();
puck.collide(malletH);
puck.collide(malletC);
for (corner in goalCorners) {
puck.collide(corner);
}
score.judgeGoal();
}
}
};
var puck: Puck = Puck {
// x: goalCornerRadius + tableWidth / 2
// y: goalCornerRadius + 2 * tableHeight/ 3
x: -(puckRadius + 1)
y: goalCornerRadius + tableHeight / 2
radius: puckRadius
vX: 0, vY: 0
}
var malletH: MalletH = MalletH { // Human's mallet.
x: bind mouseEvent.getStageX()
y: bind mouseEvent.getStageY()
radius: malletRadius
color: Color.AQUA
}
var malletC: MalletC = MalletC { // Computer's mallet.
x: goalCornerRadius + tableWidth / 2
y: goalCornerRadius + malletRadius + puckRadius
radius: malletRadius
color: Color.AQUA
vX: malletCMaxV, vY: 0
}
for (i in [0..1]) {
for (j in [0..1]) {
insert Disc {
x: (tableWidth - goalWidth) / 2 + j * goalWidth
y: i * (2 * goalCornerRadius + tableHeight)
radius: goalCornerRadius
color: Color.LIGHTGREY
} into goalCorners;
}
}
var score: Score = Score{}
Frame {
title: "Air hockey"
width: 2 * goalCornerRadius + tableWidth + 56 as Integer
height: 2 * goalCornerRadius + tableHeight + 28 as Integer
visible: true
closeAction: function(): Void {System.exit(0);}
stage: Stage {
width: 2 * goalCornerRadius + tableWidth as Integer
height: 2 * goalCornerRadius + tableHeight as Integer
fill: Color.LIGHTGRAY
content: [
Rectangle { // Jointed goal area.
x: (tableWidth - goalWidth) / 2
y: 0
width: goalWidth
height: 2 * goalCornerRadius + tableHeight +2
fill: Color.BURLYWOOD
},Rectangle { // Table.
x: goalCornerRadius, y: goalCornerRadius
width: tableWidth, height: tableHeight
fill: Color.OLIVE
},
Rectangle { // Human's hittig area.
x: goalCornerRadius + malletRadius
y: goalCornerRadius + tableHeight - hittingAreaDepth
width: tableWidth - 2 * malletRadius
height: hittingAreaDepth - malletRadius
fill: Color.OLIVE
onMouseMoved: function(ev: MouseEvent): Void {
mouseEvent = ev;
}
},
Line { // Center line.
startX: goalCornerRadius
startY: goalCornerRadius + tableHeight / 2
endX: goalCornerRadius + tableWidth -1
endY: goalCornerRadius + tableHeight / 2
stroke: Color.BLUE
},
puck,
malletH,
malletC,
goalCorners,
ScoreIndicator {
x: 2 * goalCornerRadius + tableWidth + 5 as Integer
y: goalCornerRadius + tableHeight / 2 - 24 as Integer
score: bind score.scoreC
},
ScoreIndicator {
x: 2 * goalCornerRadius + tableWidth + 5 as Integer
y: goalCornerRadius + tableHeight / 2 + 5 as Integer
score: bind score.scoreH
},
Rectangle { // "SET" button.
x: 2 * goalCornerRadius + tableWidth + 5 as Integer
y: goalCornerRadius + tableHeight - 30 as Integer
width: 35, height: 30
arcWidth: 8, arcHeight: 8
fill: Color.DARKGREY
effect: Lighting {
light: DistantLight {
azimuth: 225, elevation: 50
}
}
onMousePressed: function(ev: MouseEvent): Void {
puck.x = goalCornerRadius + tableWidth / 2;
puck.y = goalCornerRadius + 2 * tableHeight/ 3;
puck.vX = 0;
puck.vY = 0;
timingGenerator.start();
}
},
Text {
x: 2 * goalCornerRadius + tableWidth + 10 as Integer
y: goalCornerRadius + tableHeight - 20 as Integer
textOrigin: TextOrigin.TOP
fill: Color.YELLOW
font: Font {
size: 14
name: "Monospaced", style: FontStyle.BOLD
}
effect: DropShadow {
offsetX: 3, offsetY: 3, radius: 2
color: Color.BLACK
}
content: "SET"
}
]
}
}
class Disc extends CustomNode {
attribute x: Number;
attribute y: Number;
attribute radius: Number;
attribute color: Color = Color.WHITE;
attribute vX: Number;
attribute vY: Number;
attribute lastX: Number = x;
attribute lastY: Number = y;
function create(): Node {
return Circle {
centerX: bind x, centerY: bind y
radius: bind radius
fill: bind color
};
}
}
class Puck extends Disc {
function move(): Void {
x += vX;
y += vY;
if (x + radius > goalCornerRadius + tableWidth) {
x = goalCornerRadius + tableWidth - radius;
vX *= -1.0 * elastic;
} else if (x - radius < goalCornerRadius) {
x = goalCornerRadius + radius;
vX *= -1.0 * elastic;
}
if (x >= 0) {
if (y > 2 * goalCornerRadius + tableHeight) {
// It crossed the Human's goal line.
x = -(radius + 1);
y = 2 * goalCornerRadius + tableHeight + radius;
vX = 0;
vY = 0;
} else if (y + radius > tableHeight + goalCornerRadius) {
if ((x < (tableWidth - goalWidth) / 2)
or (x > (tableWidth + goalWidth) / 2)) {
y = tableHeight + goalCornerRadius - radius;
vY *= -1.0 * elastic;
}
} else if (y < 0) {
// It crossed the computer's goal line.
x = -(radius + 1);
y = -radius;
vX = 0;
vY = 0;
} else if (y - radius < goalCornerRadius) {
if ((x < (tableWidth - goalWidth) / 2)
or (x > (tableWidth + goalWidth) / 2)) {
y = goalCornerRadius + radius;
vY *= -1.0 * elastic;
}
}
}
}
function collide(disc: Disc): Void {
var distX: Number = disc.x - x;
var distY: Number = disc.y - y;
var minDist: Number = disc.radius + radius;
var dist2: Number = distX * distX + distY * distY;
var minDist2: Number = minDist * minDist;
if (dist2 < minDist2) {
var colAngle: Number = Math.atan2(distY, distX);
var sinColAngle: Number = Math.sin(colAngle);
var cosColAngle: Number = Math.cos(colAngle);
var expres1: Number = disc.vX * cosColAngle + disc.vY * sinColAngle;
var expres2: Number = vX * sinColAngle - vY * cosColAngle;
var expres3: Number = vX * cosColAngle + vY * sinColAngle;
x = disc.x - minDist * cosColAngle;
y = disc.y - minDist * sinColAngle;
vX = (1+ elastic) * expres1 * cosColAngle + expres2 * sinColAngle
- elastic * expres3 * cosColAngle;
vY = (1+ elastic) * expres1 * sinColAngle - expres2 * cosColAngle
- elastic * expres3 * sinColAngle;
}
}
}
class MalletH extends Disc {
function calcuVelocity(): Void {
vX = velocityScaling * (x - lastX);
vY = velocityScaling * (y - lastY);
lastX = x;
lastY = y;
}
}
class MalletC extends Disc {
function move(): Void {
x += vX;
if (x + radius > 2 * goalCornerRadius + tableWidth / 2
+ goalWidth / 2) {
x = 2 * goalCornerRadius + tableWidth / 2 + goalWidth / 2
- radius;
vX *= -1.0;
} else if (x - radius < tableWidth / 2 - goalWidth / 2) {
x = tableWidth / 2 - goalWidth / 2 + radius;
vX *= -1.0;
}
}
}
class Score {
attribute scoreC: Integer = 0; // Computer's score.
attribute scoreH: Integer = 0; // Human's score.
function judgeGoal(): Void {
if (puck.x < 0) {
if (puck.y > 2 * goalCornerRadius + tableHeight) {
scoreC += 1;
} else if (puck.y < 0) {
scoreH += 1;
}
puck.y = goalCornerRadius + tableHeight / 2;
timingGenerator.stop();
}
}
}
class ScoreIndicator extends CustomNode {
attribute score: Integer = 0;
attribute x: Integer = 0;
attribute y: Integer = 0;
attribute width: Integer = 21;
attribute height: Integer = 18;
attribute fillColor: Color = Color.WHITE;
attribute borderColor: Color = Color.YELLOWGREEN;
attribute scoreTextColor: Color = Color.BLACK;
function create(): Node {
Group {
content: [
Rectangle {
x: x, y: y
width: width, height: height
arcWidth: 6, arcHeight: 6
stroke: borderColor, fill: fillColor
},
Text {
x: x + 4 * height / 18 as Integer
y: y + 5 * height / 18 as Integer
textOrigin: TextOrigin.TOP
fill: scoreTextColor
font: Font {
size: 14 * height / 18 as Integer
name: "Monospaced", style: FontStyle.BOLD
}
content: bind "{%2d score}"
}
]
}
}
}
2008年9月9日火曜日
仕立て節がうまく機能しない
前回の投稿で、SCADA UIプログラム例をPAC類型に基づいて構成しなおしました。
そこで、SCADA UIの構成部品(数値指示計や棒図式指示計など)をjavafx.scene.CustomNodeクラスの派生クラスとしてPACエージェント化しようとしたのですが、どうもうまくいきません。
CustomNodeの派生クラスで、新たに関数を定義しているのですが、その関数が思ったように機能してくれません。関数の機能を限定し、「回避策」的なプログラミングを行えば、少なくともこれまでご紹介したSCADA UIプログラム例と同程度の機能を持った仕立て節を作ることは可能です。ですが、せっかく「仕立てる」のですから、あらが目立つようではどうも…。というわけで、もう少し一括変換器の成熟を待つことにしました。
JavaFX言語V1.0における変更点についても、これまでにご紹介したもの以外の変更があるのですが、すでにご紹介した内容についても更なる変更が生じていますので、こちらについても落ち着くのを待ってご紹介したほうが良さそうです。まだ「2008年の秋」は始まったばかりですから…。
そこで、SCADA UIの構成部品(数値指示計や棒図式指示計など)をjavafx.scene.CustomNodeクラスの派生クラスとしてPACエージェント化しようとしたのですが、どうもうまくいきません。
CustomNodeの派生クラスで、新たに関数を定義しているのですが、その関数が思ったように機能してくれません。関数の機能を限定し、「回避策」的なプログラミングを行えば、少なくともこれまでご紹介したSCADA UIプログラム例と同程度の機能を持った仕立て節を作ることは可能です。ですが、せっかく「仕立てる」のですから、あらが目立つようではどうも…。というわけで、もう少し一括変換器の成熟を待つことにしました。
JavaFX言語V1.0における変更点についても、これまでにご紹介したもの以外の変更があるのですが、すでにご紹介した内容についても更なる変更が生じていますので、こちらについても落ち着くのを待ってご紹介したほうが良さそうです。まだ「2008年の秋」は始まったばかりですから…。
2008年8月18日月曜日
JavaFX言語V1.0における変更:利用修飾子
JavaFX言語のV1.0における変更点の内、利用修飾子について、その内容をご案内したいと思います。
var x;
var x : String;
var x = z + 22;
var x = bind f(q);
変数を宣言します。変数は、オブジェクト直定数で初期化したり、派生クラスで再定義したり、その値を読んだり、値を割り当てたり、もしくは既定値の式またはオブジェクト直定数の初期化で結合することができます。既定で利用可能なのは台本内からで、利用修飾子がない場合は、同じ台本内からのみ初期化、再定義、読むこと、割り当て、または結合が可能です。
def x = 100;
def x = a + b;
def x = bind a + b;
変数を定義します。定義内容は変更できません。この種の変数は、オブジェクト直定数で初期化したり、派生クラスで再定義したり、値を割り当てたりすることができません。既定で利用可能なのは台本内からで、利用修飾子がない場合は、同じ台本内からのみ読むこと、または結合が可能です。
public var x;
公開変数を宣言します。この変数は、どこからでも、初期化、再定義、読むこと、割り当て、もしくは結合が可能です。
public def x = "Hi";
公開変数を定義します。この定義は、どこからでも読むこと、および結合が可能ですが、値を割り当てたり、オブジェクト直定数で初期化したり、または派生クラスで再定義することはできません。
protected var x;
同じパッケージまたは派生クラスから利用可能な変数を宣言します。
protected def x = bind z;
同じパッケージまたは派生クラスから読むこと、または結合が可能な変数を定義します。
package var x;
同じパッケージから利用可能な変数を宣言します。
package def x = 42;
同じパッケージから読むこと、または結合が可能な変数を定義します。
public-readable var x;
どこからでも読むことはできますが、既定で利用可能な台本内からのみ、初期化または書くこと(割り当てまたは結合)が可能な変数を宣言します。
public-readable package var x;
どこからでも読むことはできますが、同じパッケージ内からのみ初期化または書くこと(割り当てまたは結合)が可能な変数を宣言します。
non-writable public var x = 1.0;
non-writable public var x;
公に利用できますが、書くこと(割り当てまたは結合)ができない変数を宣言します。どこからでも、初期化、再定義、または読むことは可能ですが、その値を変更することはできません。この場合、初期化は書くことにはあたりません。
non-writable var x = 1.0;
同じ台本内から初期化または読むことはできますが、値の割り当てまたは結合はできない変数を宣言します。
var x;
var x : String;
var x = z + 22;
var x = bind f(q);
変数を宣言します。変数は、オブジェクト直定数で初期化したり、派生クラスで再定義したり、その値を読んだり、値を割り当てたり、もしくは既定値の式またはオブジェクト直定数の初期化で結合することができます。既定で利用可能なのは台本内からで、利用修飾子がない場合は、同じ台本内からのみ初期化、再定義、読むこと、割り当て、または結合が可能です。
def x = 100;
def x = a + b;
def x = bind a + b;
変数を定義します。定義内容は変更できません。この種の変数は、オブジェクト直定数で初期化したり、派生クラスで再定義したり、値を割り当てたりすることができません。既定で利用可能なのは台本内からで、利用修飾子がない場合は、同じ台本内からのみ読むこと、または結合が可能です。
public var x;
公開変数を宣言します。この変数は、どこからでも、初期化、再定義、読むこと、割り当て、もしくは結合が可能です。
public def x = "Hi";
公開変数を定義します。この定義は、どこからでも読むこと、および結合が可能ですが、値を割り当てたり、オブジェクト直定数で初期化したり、または派生クラスで再定義することはできません。
protected var x;
同じパッケージまたは派生クラスから利用可能な変数を宣言します。
protected def x = bind z;
同じパッケージまたは派生クラスから読むこと、または結合が可能な変数を定義します。
package var x;
同じパッケージから利用可能な変数を宣言します。
package def x = 42;
同じパッケージから読むこと、または結合が可能な変数を定義します。
public-readable var x;
どこからでも読むことはできますが、既定で利用可能な台本内からのみ、初期化または書くこと(割り当てまたは結合)が可能な変数を宣言します。
public-readable package var x;
どこからでも読むことはできますが、同じパッケージ内からのみ初期化または書くこと(割り当てまたは結合)が可能な変数を宣言します。
non-writable public var x = 1.0;
non-writable public var x;
公に利用できますが、書くこと(割り当てまたは結合)ができない変数を宣言します。どこからでも、初期化、再定義、または読むことは可能ですが、その値を変更することはできません。この場合、初期化は書くことにはあたりません。
non-writable var x = 1.0;
同じ台本内から初期化または読むことはできますが、値の割り当てまたは結合はできない変数を宣言します。
2008年8月17日日曜日
JavaFX言語V1.0における変更
JavaFX言語のV1.0(初版)における変更点について、ご案内したいと思います。
まだ実装されていないものもあります。また、実装済みのものについては、古い構文も(警告はされるが)ある期間は引き続き機能するようです。ただし、最終的には、警告ではなく誤りになり、その切り替わり時点は決まっていないということのようです。
不等価演算子
不等価演算子「<>」は「!=」に置き換えられました。この件と次の剰余演算子の変更については、以前の投稿でも触れました。
剰余演算子
剰余演算子「%」は「mod」に置き換えられました。これに関連して、剰余代入演算子「%=」は、(「mod=」に置き換えられるのではなく)剰余代入演算子そのものが支援されなくなるため、「x = x mod y」のように記述することになります。
ちなみに、JavaFXの剰余演算はJavaのそれ(%)と同じで、剰余の符号は被除数の符号に合わせられます(被除数および/または除数が負数の場合の、剰余の符号に関係します)。
台本の取込み
import文は、Java言語の「import static」(J2SE 5.0で支援された、クラスの静的要素の取り込み)機能を含むように、拡張されました。
「+」による文字列連結の廃止
「+」演算子による文字列の連結は廃止されます。String型の変数aとbに格納した文字列の連結は、(「a + b」ではなく)「"{a}{b}"」と表します。
関数再定義
クラスの要素関数を再定義するときは、「override」鍵語が必要になります。
変数宣言
変数を宣言するときは、それがどこであれ、鍵語「var」と「def」が使用され、鍵語「attribute」(クラス属性を宣言)と「static」(台本水準の変数を定義)はなくなります。「def」を用いて定義した変数に(初期化以外で)値を割り当てると誤りになります。
利用修飾子
利用修飾子は、「public」、「package」、「protected」、「public-readable」および「non-writable」の5つとなり、「private」はなくなります。既定の利用可能性は、「台本内から利用可」です。
利用修飾子については、別途投稿します。
オブジェクト直定数構文
「オブジェクト直定数の振る舞いがいくぶんか限定され、構文も厳密になる」ようです。
再帰取込み
「import パッケージ名.*」構文は、「import パッケージ名.**」を支援するように拡張されます。これは、「指定されたパッケージとその下位パッケージに含まれるすべてを取り込む」ことを意味します。
仮引数の不変化
関数の仮引数は不変で、それらに値を割り当てると誤りになります。
まだ実装されていないものもあります。また、実装済みのものについては、古い構文も(警告はされるが)ある期間は引き続き機能するようです。ただし、最終的には、警告ではなく誤りになり、その切り替わり時点は決まっていないということのようです。
不等価演算子
不等価演算子「<>」は「!=」に置き換えられました。この件と次の剰余演算子の変更については、以前の投稿でも触れました。
剰余演算子
剰余演算子「%」は「mod」に置き換えられました。これに関連して、剰余代入演算子「%=」は、(「mod=」に置き換えられるのではなく)剰余代入演算子そのものが支援されなくなるため、「x = x mod y」のように記述することになります。
ちなみに、JavaFXの剰余演算はJavaのそれ(%)と同じで、剰余の符号は被除数の符号に合わせられます(被除数および/または除数が負数の場合の、剰余の符号に関係します)。
台本の取込み
import文は、Java言語の「import static」(J2SE 5.0で支援された、クラスの静的要素の取り込み)機能を含むように、拡張されました。
「+」による文字列連結の廃止
「+」演算子による文字列の連結は廃止されます。String型の変数aとbに格納した文字列の連結は、(「a + b」ではなく)「"{a}{b}"」と表します。
関数再定義
クラスの要素関数を再定義するときは、「override」鍵語が必要になります。
変数宣言
変数を宣言するときは、それがどこであれ、鍵語「var」と「def」が使用され、鍵語「attribute」(クラス属性を宣言)と「static」(台本水準の変数を定義)はなくなります。「def」を用いて定義した変数に(初期化以外で)値を割り当てると誤りになります。
利用修飾子
利用修飾子は、「public」、「package」、「protected」、「public-readable」および「non-writable」の5つとなり、「private」はなくなります。既定の利用可能性は、「台本内から利用可」です。
利用修飾子については、別途投稿します。
オブジェクト直定数構文
「オブジェクト直定数の振る舞いがいくぶんか限定され、構文も厳密になる」ようです。
再帰取込み
「import パッケージ名.*」構文は、「import パッケージ名.**」を支援するように拡張されます。これは、「指定されたパッケージとその下位パッケージに含まれるすべてを取り込む」ことを意味します。
仮引数の不変化
関数の仮引数は不変で、それらに値を割り当てると誤りになります。
2008年8月14日木曜日
SCADA UIプログラム例のJavaFX SDK対応書き換え:PAC類型適用
先日の投稿で、SCADA UIプログラム例のJavaFX SDK対応の書き換えは、一応済みました。
今回は、プログラム構造を基本設計におけるPAC(Presentation-Abstraction-Control)類型を参考にして構成し直してみました。ただし、Presentation要素とAbstraction要素が、結合機能により、(Control要素を介さない)直接的なつながりを持っているので、純粋にはPAC類型と呼べないかも知れません。
しかし、機能的なまとまりをPACエージェントとして構成し、プログラム全体をそれらPACエージェントの階層化された木構造で構成しているという点においては、PAC類型に準じたものです。PAC類型については、F. ブッシュマン、ほか著の『ソフトウェア アーキテクチャ ソフトウェア開発のためのパターン体系』(株式会社トッパン,1999)に説明されています。
PACエージェントとして構成するため、Javaのプログラムを2つに分けました。
まずは、依頼機側の接続エージェントとなるJavaのプログラムを示します。
次に、警報鳴動エージェントとなるJavaのプログラムを示します。
最後に、利用者インタフェース(UI)最上位エージェントである、UI枠付き窓エージェントのJavaFXプログラムを示します。
今回は、プログラム構造を基本設計におけるPAC(Presentation-Abstraction-Control)類型を参考にして構成し直してみました。ただし、Presentation要素とAbstraction要素が、結合機能により、(Control要素を介さない)直接的なつながりを持っているので、純粋にはPAC類型と呼べないかも知れません。
しかし、機能的なまとまりをPACエージェントとして構成し、プログラム全体をそれらPACエージェントの階層化された木構造で構成しているという点においては、PAC類型に準じたものです。PAC類型については、F. ブッシュマン、ほか著の『ソフトウェア アーキテクチャ ソフトウェア開発のためのパターン体系』(株式会社トッパン,1999)に説明されています。
PACエージェントとして構成するため、Javaのプログラムを2つに分けました。
まずは、依頼機側の接続エージェントとなるJavaのプログラムを示します。
/*
* ClientSideConnection.java
* The Client-Side Connection agent for a SCADA application.
*
* Created and modified:
* V 1.0.0 2008/07/31
*/
package client;
/**
* @author terra
*/
class ClientSideConnection {
private int i = -1;
/**
* Create and return pseudo SCADA data as if received from a server.
*/
double receiveScadaData() {
i += 1;
if (i >= 360) {
i = 0;
}
return (double) Math.sin(i * Math.PI / 180) * 100;
}
/**
* Main method for testing this class.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
ClientSideConnection scc = new ClientSideConnection();
for (int i = 0; i < 5; i++) {
System.out.println(scc.receiveScadaData());
}
}
}
次に、警報鳴動エージェントとなるJavaのプログラムを示します。
/*
* AlarmSounder.java
* The Temporary Alarm Sounder agent for a SCADA application.
* At some point, this agent will be rewritten in the JavaFX language.
*
* Created and modified:
* V 1.0.0 2008/08/11
*/
package client;
import java.applet.Applet;
import java.applet.AudioClip;
import javax.swing.JOptionPane;
/**
* @author terra
*/
class AlarmSounder {
private AudioClip ac = Applet.newAudioClip(
AlarmSounder.class.getResource("ALERT.WAV"));
/**
* Start sounding the alarm.
*/
public void startAlarm() {
ac.loop();
}
/**
* Stop sounding the alarm.
*/
public void stopAlarm() {
ac.stop();
}
/**
* Main method for testing this class.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
AlarmSounder as = new AlarmSounder();
as.startAlarm();
JOptionPane.showMessageDialog(null, "Alarm is sounding!",
"Alarm test", JOptionPane.WARNING_MESSAGE);
as.stopAlarm();
}
}
最後に、利用者インタフェース(UI)最上位エージェントである、UI枠付き窓エージェントのJavaFXプログラムを示します。
/*
* ScadaClientUI.fx
* The User Interface Frame (UIF) agent for a SCADA application.
*
* Created and modified:
* V 1.0.0 2008/08/01
*/
package client;
import javafx.animation.*;
import javafx.application.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import java.lang.System;
/**
* @author terra
*/
var csc = new ClientSideConnection(); // Client-Side Connection agent.
var alarmSounder = new AlarmSounder(); // Alarm Sounder agent.
/*
* The Abstraction compenent of the UIF agent.
*/
class UIFAbstraction {
attribute numericDatum: Number;
attribute numericData: Number[] = for (i in [0..180]) 0.0;
attribute alarting: Boolean;
function getDisplayColor(value: Number): Color {
var c: Color = Color.GREEN;
if (value > 90.0) {
c = Color.RED;
} else if (value > 75.0) {
c = Color.color(1.0, 0.6, 0.0);
}
return c;
}
function setNumericDatum(value: Number): Void {
numericDatum = value;
delete numericData[0];
insert value into numericData;
if (numericDatum > 90.0 and not alarting) {
alarting = true;
alarmSounder.startAlarm();
alarmDialog.toFront();
alarmDialog.visible = true;
}
}
function setAlarting(alarting: Boolean): Void {
this.alarting = alarting;
}
}
var uifa = UIFAbstraction {};
/*
* The Control compenent of the UIF agent.
*/
class UIFControl {
attribute schedular: Timeline = Timeline{
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time: 200ms
action: function(): Void {
uifa.setNumericDatum(csc.receiveScadaData());
}
}
}
}
var uifc = UIFControl {};
uifc.schedular.start();
/*
* The Presentation compenent of the UIF agent.
*/
Frame {
title: "SCADA UI Example"
width: 800, height: 632
visible: true
closeAction: function(): Void {System.exit(0);}
stage: Stage {
width: 800, height: 600
fill: Color.color(0.9, 0.9, 0.9)
content: [
Group {
translateX: 50, translateY: 50
content: [
Rectangle {
width: 55, height: 18
arcWidth: 4, arcHeight: 4
stroke: Color.BLUE, fill: Color.WHITE
},
Text {
x: 4, y: 5
textOrigin: TextOrigin.TOP
fill: bind uifa.getDisplayColor(uifa.numericDatum)
font: Font {
name: "Monospaced", size: 14, style: FontStyle.PLAIN
}
content: bind "{%7.2f uifa.numericDatum}"
},
Text {
x: 60, y: 6
textOrigin: TextOrigin.TOP
font: Font {
name: "Monospaced", size: 14, style: FontStyle.PLAIN
}
content: "m3/s"
}
]
},
Group {
translateX: 200, translateY: 50
content: [
Rectangle {
width: 60, height: 120
stroke: Color.BLUE, fill: Color.WHITE
},
Rectangle {
x: 10, y: 10
width: 10, height: 100
fill: LinearGradient {
startX: 0.0, startY: 0.0
endX: 0.0, endY: 1.0
stops: [
Stop {offset: 0.05, color: Color.RED},
Stop {offset: 0.0875, color: Color.color(1.0, 0.6, 0.0)},
Stop {offset: 0.125, color: Color.LIMEGREEN},
]
}
},
Rectangle {
x: 10, y: 10
width: 10, height: bind 50 - uifa.numericDatum / 2
fill: Color.BLACK
},
Line {
startX: 23, startY: 10
endX: 23, endY: 110
stroke: Color.BLACK
},
Group {
translateX: 23, translateY: 10
content: for (i in [-100..100 step 50]) Line {
translateY: i / 2 + 50
startX: 0, startY: 0
endX: 3, endY: 0
stroke: Color.BLACK
}
},
Group {
translateX: 32, translateY: 10
content: for (i in [-100..100 step 50]) Text {
translateY: 53 - i / 2
content: "{i}"
font: Font {
name: "Monospaced", size: 10, style: FontStyle.PLAIN
}
horizontalAlignment: HorizontalAlignment.LEADING
verticalAlignment: VerticalAlignment.TOP
}
}
]
},
Group {
translateX: 50, translateY: 200
content: [
Rectangle {
width: 230, height: 120
stroke: Color.BLUE, fill: Color.WHITE
},
Rectangle {
x: 10, y: 22
width: 181, height: 88
fill: Color.BLACK
},
Rectangle {
x: 10, y: 15
width: 181, height: 7
fill: Color.GRAY
},
Rectangle {
x: 10, y: 10
width: 181, height: 5
fill: Color.DARKGRAY
},
Line {
startX: 194, startY: 10
endX: 194, endY: 110
stroke: Color.BLACK
},
Group {
translateX: 194, translateY: 10
content: for (i in [-100..100 step 50]) Line {
translateY: i / 2 + 50
startX: 0, startY: 0
endX: 3, endY: 0
stroke: Color.BLACK
}
},
Group {
translateX: 203, translateY: 11
content: for (i in [-100..100 step 50]) Text {
translateY: 53 - i / 2
content: "{i}"
font: Font {
name: "Monospaced", size: 10, style: FontStyle.PLAIN
}
horizontalAlignment: HorizontalAlignment.LEADING
verticalAlignment: VerticalAlignment.TOP
}
},
Group {
translateX: 10, translateY: 10
content: for (i in [0..179]) Line {
translateX: i
startX: 0, startY: bind 50 - uifa.numericData[i] / 2
endX: 1, endY: bind 50 - uifa.numericData[i + 1] / 2
strokeWidth: 2
stroke: bind uifa.getDisplayColor(uifa.numericData[i + 1])
}
}
]
}
]
}
}
/*
* The Alarm Dialog agent.
*/
var alarmDialog = Dialog {
title: "Alarm"
width: 300, height: 60
visible: false
closeAction: function(): Void {
alarmSounder.stopAlarm();
uifa.alarting = false;
}
stage: Stage {
content: Text {
x: 10, y: 10
textOrigin: TextOrigin.TOP
fill: Color.RED
font: Font {size: 14}
content: "Flow rate exceeded the second upper limit!"
}
}
}
登録:
投稿 (Atom)