第5章
第7章
図1「disable.png」
Redボタンの内容を記述している最初のButton{...}の中に、リスト1を追加してみましょう。
リスト1 Buttonに特性「disabledIcon」を追加する
disabledIcon:Image{ url:"file:///C:/Users/javafx/work/img/disable.png" }
実行してみます。
図2 無効度がより強まったかどうか?
Redボタンが有効になったとき、このバツアイコンが再び赤丸アイコンになることも確認してください。
そのチェックボックスが選択された・されないに関係なく、とにかくクリックされたときに何か特別な動作をさせたい
という場合に使えます。リスト2のような例はどうでしょうか。
リスト2 CheckBoxAction.fx
import javafx.ui.*; class FishModel{ attribute name:String; attribute selected:Boolean; } class FishGroupModel{ attribute group:FishModel*; attribute message:String; //表示させるメッセージ } var fishgroup=FishGroupModel{ var maguro=FishModel{ name:"まぐろ" selected:false} var katuo=FishModel{ name:"かつお" selected:false } var iwashi=FishModel{ name:"いわし" selected:false } group:[maguro,katuo,iwashi] message:"どれになさいますか" }; Frame{ width:250 height:100 content:BorderPanel{ top:GridPanel{ rows:1 columns:sizeof fishgroup.group cells:foreach (fish in fishgroup.group) CheckBox{ text:fish.name selected:bind fish.selected //アクションを記述 action:operation(){ if(fish.selected){ fishgroup.message="{fish.name}がお好みですか"; } else{ fishgroup.message="{fish.name}はお嫌いですか"; } } } } bottom:Label{text: bind fishgroup.message} } visible:true }
リスト2では、ひとつひとつの「FishModel」ではなく、全体の「FishGroupModel」に、特性「message」を与えています。アクションの結果を表示させるための文字列です。
リスト2では、簡単のため、「今どの要素が選択状態にあるか」を表示するようにはしていません。
そのかわり、その要素が選択状態にあるか、ないかで、異なる動作をさせています。
if( fish.selected)
というのがそれです。 チェックボックスがクリックされたときに、
クリックによって選択された場合は「お好みですか」、
クリックによって選択が解除された場合は「お嫌いですか」
と、表示するようにするのです。
実行してみましょう。他のチェックボックスがオンかオフかは、このアプリケーションでは関係しません。
図3 今クリックしたチェックボックスの動作に反応してメッセージが表示される
リスト3 TwoTabs.fx
import javafx.ui.*; //Sliderのためのモデルと変数 class SliderValueModel{ attribute value:Number; } var slidervalue=SliderValueModel{ value:0 }; //ProgressBarのためのモデルと変数 class ClickNumModel{ attribute num:Integer; } var clicknum=ClickNumModel{ num:0 }; Frame{ width:250 height:200 content:TabbedPane{ tabPlacement:TOP tabs:[ //具体的なTabの配列なので[...]で囲む Tab{ title:"SimpleSlider" content:GridPanel{ border:EmptyBorder{top:10 bottom:10 right:20 left:20} rows:2 columns:1 cells:[ Slider{ value:bindslidervalue.value min:0 max:100 minorTickSpacing:10 majorTickSpacing:20 paintTicks:true }, Label{ text:bind slidervalue.value.toString() } ] } }, Tab{ title:"ProgressBar" content:GridPanel{ border:EmptyBorder{top:10 bottom:10 right:20 left:20} rows:2 columns:1 cells:[ ProgressBar{ max:50 min:0 value:bind clicknum.num string:bind "{clicknum.num.toString()}回" stringPainted:true borderPainted:true }, Button{ text:"ここをクリック" action:operation(){ clicknum.num++; } } ] } } ] } visible:true }
実行してみましょう。どちらも、タブ上でちゃんと動作することを確認してください。
図4「SimpleSlide」と「ProgressBar」のタブを切り替える
・3次曲線
まずは3次曲線です。リスト4は、開始点・終点・制御点をいろいろに変えた3種類の3次曲線を表示したものです。「3次」などと偉そうに書きましたが、要するにS字(あるいはその裏返し)のような曲線です。
リスト4 MyCubicCurves.fx
import javafx.ui.*; import javafx.ui.canvas.*; Frame{ width:300 height:150 content:GridPanel{ rows:1 columns:3 cells:[ Canvas{ content:CubicCurve{ x1:10 y1:10 x2:90 y2:90 ctrlx1:40 ctrly1:20 ctrlx2:60 ctrly2:80 stroke: blue strokeWidth:2 } }, Canvas{ content:CubicCurve{ x1:10 y1:10 x2:90 y2:90 ctrlx1:50 ctrly1:10 ctrlx2:50 ctrly2:80 stroke: purple strokeWidth:2 } }, Canvas{ content:CubicCurve{ x1:10 y1:10 x2:70 y2:90 ctrlx1:90 ctrly1:30 ctrlx2:10 ctrly2:70 stroke: orange strokeWidth:2 } } ] } visible:true }
実行してみましょう。
図5 いろいろな3次曲線
CubicCurveのとる主な特性を表1に示します。
表1 CubicCurveの主な特性
x1, y1 | 曲線の開始点のX座標、Y座標 |
x2, y2 | 曲線の終点のX座標、Y座標 |
ctrlx1, ctrly1 | 最初の制御点のX座標、Y座標 |
ctrlx2, ctrly2 | 2番目の制御点のX座標、Y座標 |
「制御点」とは、実は筆者もよくわかりません。おそらく本書に示した「ベジェ曲線」の「制御点」と同じような考え方でしょう。
両制御点が曲線の外側に遠く離れていると、曲線のグンニャリ度が増す、ぐらいに考えておきましょう。
・QuadCurve
QuadCurveは山(逆さにすれば谷)が1つだけの曲線です。Javaの世界では「2次曲線」とされているようですが、「Quad」は「4」じゃないのかなと筆者は密かに思っています。でも2次曲線は4次曲線の仲間ですし、こだわってもしょうがありませんね。
QuadCurveでは「制御点」は一つです。
表2 QuadCurveの主な特性
x1, y1 | 曲線の開始点のX座標、Y座標 |
x2, y2 | 曲線の終点のX座標、Y座標 |
ctrlx, ctrly | 制御点のX座標、Y座標 |
これもリスト5のように、開始点・終点・制御点をいろいろに変えて、どのような結果が出るか見てみましょう。
リスト5 MyQuadCurves.fx
import javafx.ui.*; import javafx.ui.canvas.*; Frame{ width:300 height:150 content:GridPanel{ rows:1 columns:3 cells:[ Canvas{ content:QuadCurve{ x1:10 y1:100 ctrlx:50 ctrly:10 x2:90 y2:100 stroke:blue strokeWidth:2 } }, Canvas{ content:QuadCurve{ x1:10 y1:100 ctrlx:50 ctrly:50 x2:90 y2:100 stroke:brown strokeWidth:2 } }, Canvas{ content:QuadCurve{ x1:30 y1:10 ctrlx:10 ctrly:100 x2:90 y2:10 stroke:magenta strokeWidth:2 } } ] } visible:true }
図6 リスト5の実行結果
QuadCurveでは、制御点が開始点と終了点の中点にあると、対称的な曲線になります。また、制御点が端点から垂直方向に離れると、凹凸が深くなります。
本書ですでに紹介した「ベジェ曲線」や、ここで追加の紹介をした「何次曲線」というものを、制御点をひねくり出しながら描く必要はそう多くはないでしょうが、「こういう描き方もできたな」と覚えがあれば何らかの役には立つかも知れません。
本章ではグラデーションの塗り方を紹介していますが、図形イッパイではなく、途中から途中までを塗ることもできます。
この場合、今まで使わなかった特性x1,y1及びx2,y2を用います。
リストでは、「LinearGradient」の特性にこれらを加え、長方形の左から0.5-0.75の範囲をグラデーションにしています。
リスト6「LinearGradient」に特性を追加
fill:LinearGradient{ x1:0.5,y1:0, x2:0.75,y2:0 //X方向の両端を残す stops:[ Stop{offset:0.0 color:blue}, Stop{offset:1.0 color:lightblue} ] //リスト7あるいはリスト8をここに追加 }
本書掲載のプログラム「SimpleGrad.fx」の「fill:LinearGradient{...}」の部分を、リスト6のように書き換えたら、「PartialGrad.fx」という別名で保存し、実行してみましょう。
図7「SimpleGrad.fx」の一部をリスト6に修正後(グラデーションの範囲を狭めた)
図7では、グラデーションの領域の外側では、それぞれの基本色がベタ塗りされることになります(デフォルト)。しかし、「LinearGradient」の特性「spreadMethod」を指定することにより、別の処理のしかたもできます。
リスト7のように「REFLECT」を指定することで、グラデーションを指定しない部分には、グラデーションが「対称的」に繰り返されます。
リスト7 LinearGradient の特性「spreadMethod」を「REFLECT」に
spreadMethod:REFLECT
リスト6にさらにリスト7を追加したら、Reflect.fxという別名で保存し(サンプルフォルダにあります)、実行してみましょう。
図8 リスト7を追加後(対称配置)
リスト7に代えてリスト8のように「REPEAT」に指定すれば、同じ向きにグラデーションが繰り返されます。
リスト8 LinearGradientの特性「spreadMethod」を「REPEAT」に
spreadMethod:REPEAT
リスト7をリスト8に書き換えたら、Repeat.fxという別名で保存し、実行してみましょう。
図9 リスト7をやめてリスト8にする(繰り返し)
・放射状グラデーションの中心をずらす
放射状グラデーションでも直線状グラデーションと同じようにstop、spreadMethodの指定ができますが、それよりもう一組大事な特性があります。「」色の放射の中心を領域の中心から少しずらすことができます。
リスト9 focusX, focusYを設定
focusX:40 focusY:40
放射状グラデーションのサンプルプログラム「RadialGrad.fx」に、さらにリスト9を追加したら、「RadialShear.fx」という別の名前で保存し、実行してみましょう。
図10 リスト9を追加後(放射の中心を斜めに置く)
これはなかなかシブい効果を与える設定ですね。
リスト10 ShadowFilterの設定を変える例
ShadowFilter{ angle:30 distance:5 opacity:0.7 shadowColor:white }
リスト10では、shadowColorつまり影の色を白にしました。そこで、フレームの背景色は黒にしましょう。
Frameの特性「background」を指定します。
リスト11 フレームの背景色を黒にする
Frame{ width:200 height:200 background:black //背景色を指定 ..........
テキストそのものも白にします。
リスト12 Textのfillやstrokeを白にする
content:Text{ .......... fill:white stroke:white strokeWidth:1 } //Textの閉じカッコ
実行するとこんな感じです。
図11 ShadowFilterの特性を調整した結果
本書でボツになったわけが、なんとなくわかりますね。
平行移動を例にとりましょう。図形を、「X」のボタンでX方向、「Y」のボタンでY方向に移動させるプログラムです。実は本書のどこかにさりげなく、絵だけ表示されているんです。
図12 X方向、Y方向に分けて動かす
ひとつは、リスト13のように、図形の位置を表す特性を変更する方法です。
リスト13 XYPosition.fx
import javafx.ui.*; import javafx.ui.canvas.*; class PositionModel{ attribute x: Number; attribute y: Number; } var change=PositionModel{ x:0 y:0 }; Frame{ width:250 height:200 content:BorderPanel{ center:Canvas{ content:Rect{ x:bind 10*change.x y:bind 10*change.y width:50 height:30 fill:red } } bottom:GridPanel{ rows:1 columns:3 cells:[ Button{ text:"X" action:operation(){ change.x++; } }, Button{ text:"Y" action:operation(){ change.y++; } }, Button{ text:"クリア" action:operation(){ change.x=0; change.y=0; } }, ] } } visible:true }
もうひとつは、Transformのうちの、translateを用いる方法です。
リスト14 XYTranslate.fx
import javafx.ui.*; import javafx.ui.canvas.*; class TransformModel{ attribute transform:Transform*; } var dotransform=TransformModel{}; Frame{ width:250 height:200 content:BorderPanel{ center:Canvas{ content:Rect{ x:0 y:0 width:50 height:30 fill:red transform:bind dotransform.transform } } bottom:GridPanel{ rows:1 columns:3 cells:[ Button{ text:"X" action:operation(){ var movex:Transform=translate(10,0); insert movex into dotransform.transform; } }, Button{ text:"Y" action:operation(){ var movey:Transform=translate(0,10); insert movey into dotransform.transform; } }, Button{ text:"クリア" action:operation(){ delete dotransform.transform; } }, ] } } visible:true }
いずれも、本書を一通り学んでいれば、特に解説の必要もないでしょう。
このように、「動かす」というだけなら、どちらが良い悪いということもないと思います。
「移動量」あるいは「移動の過程」が大事なときはTransform、
「現在の位置や、他の図形との相対位置」が大事なときは、座標の値を変化させるのが妥当です。
また、JavaFXにもまだまだ不確定要素がありますから、いずれかを行って何か不具合が出たときに、もう一方の方法を試して見ると問題を回避できることがあるかも知れません。
リスト15 MyGroup.fx(本書に掲載したものを抜粋)
.............. class TransformModel{ attribute transform:Transform*; } var grouptransform=TransformModel{}; //リスト16を追記 Frame{ ................. content:BorderPanel{ center:Canvas{ content:Group{ transform:bind grouptransform.transform content:[ Rect{ //リスト17を追記 ............... }, Circle{ //リスト18を追記 ................. } ] }//Groupの閉じ括弧 }//Canvasの閉じ括弧 bottom: //リスト19のように書く ...................
まず、変数grouptransformの他に、リスト16のように同じTransformModel型の変数recttransformとcircletransformも作ります。特性は指定しません。
リスト16 recttransformとcircletransformを追加
var recttransform=TransformModel{}; var circletransform=TransformModel{};
Rectの{...}の中に、特性transformの指定を追記し、リスト16で追加したrecttransformの特性transformにバインドします。 リスト17 Rect{...} の中に追記
transform:bind recttransform.transform
Circle{...}の中にも特性transformの指定を追記し、こちらはcircletransformの特性transformにバインドします。 リスト18 Circle{...} の中に追記
transform:bind circletransform.transform
「MyGroup.fx」ではボタンが「回転」ひとつでしたので、BorderPanel(全体の配置を決めるパネル)のbottomに直接置いていましたが、今度はボタンが増えるので、bottomにはGridPanelを置いて、さらにその上にボタンを置きます。
リスト19 bottomの内容を書き換える
bottom:GridPanel{ rows:1 columns:4 cells:[ Button{ text:"四角" action:operation(){ var rotate:Transform=rotate(45,200,200); insert rotate into recttransform.transform; } }, Button{ text:"丸" action:operation(){ var rotate:Transform=rotate(60,80,80); insert rotate into circletransform.transform; } }, Button{ text:"御一緒に" action:operation(){ var rotate:Transform=rotate(30,150,150); insert rotate into grouptransform.transform; } }, Button{ text:"クリア" action:operation(){ delete recttransform.transform; delete circletransform.transform; delete grouptransform.transform; } } ] } //GridPanelの閉じカッコ } //BorderPanelの閉じカッコ
リスト19では、「四角」のボタンを押したときにはrecttransformに、「丸」のボタンを押したときにはcircletransformに、回転の行程が入るようになっています。これらの場合、それぞれの図形が個別に回転することになります。
「御一緒に」のボタンをクリックすると、円も四角もそれぞれの位置から、同じ中心と同じ回転角で回り始めます。
なお、「クリア」ボタンを押したときは、recttransformもcircletransformも要素を全部消して、全く元に戻します。
実行して、丸や四角を個別に回したり、全体を回したりしてみましょう。
図13 丸や四角を個別に回して位置を変えたあと、全体を回しているところ
リスト20 JpgCanvas.fx
import javafx.ui.*; import javafx.ui.canvas.*; class TransformModel{ attribute transform:Transform*; } var dotransform=TransformModel{}; Frame{ width:400 height:400 content:BorderPanel{ center:Canvas{ content:[ Circle{ cx:200 cy:200 radius:120 fill:RadialGradient{ cx:200 cy:200 stops:[ Stop{ offset:0.0, color:lightgreen}, Stop{ offset:1.0,color:green} ] } }, ImageView{ image:Image{ url: "file:///C:/Users/javafx/work/img/testimg.jpg" } transform:bind dotransform.transform //ImageViewだけにtransformを指定 } ] } right:GridPanel{ columns:1 rows:5 cells:[ Button{ text:"移動" action:operation(){ var move:Transform=translate(100,50); insert move into dotransform.transform; } }, Button{ text:"回転" action:operation(){ var rotate:Transform=rotate(30,0,0); insert rotate into dotransform.transform; } }, Button{ text:"拡大" action:operation(){ var scale:Transform=scale(2.0,3.0); insert scale into dotransform.transform; } }, Button{ text:"傾斜" action:operation(){ var skew:Transform=skew(5,5); insert skew into dotransform.transform; } }, Button{ text:"クリア" action:operation(){ delete dotransform.transform; } } ] } } visible:true }
図14 図形にかぶさって画像が移動しているところ
リスト21 AreaThree.fx
import javafx.ui.*; import javafx.ui.canvas.*; var mycircle=Circle{ cx:50 cy:100 radius:30 fill:lightblue stroke:blue }; var myrect=Rect{ x:50 y:40 width:100 height:60 fill:lightyellow stroke:yellow }; var mystar=Star{ cx:60 cy:80 rin:10 rout:20 points:6 fill:black }; Frame{ width:400 height:200 content:GridPanel{ rows:1 columns:2 cells:[ Canvas{ content:[myrect, mycircle,mystar] }, Canvas{ content:Subtract{ shape1:Add{ shape1:myrect shape2:mycircle //最終的に指定されるので、塗りの指定は不要 } shape2:mystar fill:red stroke:blue } } ] } visible:true }
リスト21では、shape1に「Add{...}」、shape2にmystarを指定しています。
shape1に指定された「Add{...}」では、Subtractで最終的に塗りが指定されているので、ここでの塗りの指定は不要です(指定してもエラーにはならないが、結局キャンセルされます)。
実行結果は、実は本書にひそかに掲載されています。探してみてください。
リスト22 OnPressScale.fx
import javafx.ui.canvas.*; import javafx.ui.*; class TransformModel{ attribute transform:Transform*; } var dotransform=TransformModel{}; Frame{ width:200 height:200 content: Canvas{ content: Circle{ transform:bind dotransform.transform cx:50 cy:50 radius:20 fill:red onMousePressed:operation(e:CanvasMouseEvent){ var large:Transform=scale(2.0,2.0); insert large into dotransform.transform; } onMouseReleased:operation(e:CanvasMouseEvent){ var small:Transform=scale(0.5,0.5); insert small into dotransform.transform; } } } visible:true }
リスト22では、マウスボタンを放すとscaleが「元に戻る」ようにしたいのですが、マウスボタンはその前に押さなければ放すという動作が成立しませんから、必ず事前に2倍に拡大されているはずです。そこで、放したときには逆に半分に縮小しています。
実行してみると、確かに大きさは変わりますが、赤丸が飛び出してくるように見えます。
図15 リスト22で、マウスボタンを押さないとき(左)と、押したとき (右)
なぜでしょうか。「scale」を指定すると、円の中心座標もそれぞれ拡大されるからだと考えられます。
これはこれで面白いプログラムですが、本書のプログラムのような「半径情報」を直接変化させるものと、場合により使い分けが必要ですね。