ScalaFXでJavaFX 2のFXMLLoaderを使う
ScalaFXでJavaFX 2のFXMLLoaderを使うの自体は簡単です。
後はコントローラーをどのように書くかですね2種類の方法があります。
まず方法1はJavaFXで書く
方法2はScalaFXで書くです
方法2についてはProScalaFXにサンプルがあります。
実行するとこんな感じです。
ProScalaFXのサンプルと同じようにコントローラーをScalaFXで書いてSystem.getPropertyで得られる値を一部GridPaneの表に表示する簡単なアプリを作ってみます。
今回もIntellij IDEA 12.1.6 ceを使います。
1. FileメニューよりNew Project...を選択。
プロジェクト名(GetProperty)とScala Homeを記入
2. Fileメニューから”Project Structure...”を選択
Project Settingsの中のLibrariesを選択3. 左上の"+" をクリック
scalafx_2.10-1.0.0-M6.jarを追加する。
最新のjarはここにあります。
http://search.maven.org/#search%7Cga%7C1%7Cscalafx
4. srcフォルダをcontrolキー+クリック又はマウスの右クリックでNew=>Scala Classを作成します。
作成するのは次の2つのクラスです。
GetProperty.scala
GetPropertyController.scala
GetProperty.scala
/** * Created with Intellij IDEA. * User: hshino * Date: 13/10/31 * Time: 18:30 * To change this template use File | Settings | File Templates. */ import java.io.IOException import javafx.{fxml => jfxf} import javafx.{scene => jfxs} import scalafx.Includes._ import scalafx.application.JFXApp import scalafx.application.JFXApp.PrimaryStage import scalafx.scene.Scene /** Example of using FXMLLoader from ScalaFX. */ object GetProperty extends JFXApp { val resource = getClass.getResource("GetProperty.fxml") if (resource == null) { throw new IOException("Cannot load resource: GetProperty.fxml") } val root: jfxs.Parent = jfxf.FXMLLoader.load(resource) stage = new PrimaryStage() { title = "FXML GetProperty Demo" scene = new Scene(root) } }
GetPropertyController.scala
/** * Created with Intellij IDEA. * User: hshino * Date: 13/10/31 * Time: 18:32 * To change this template use File | Settings | File Templates. */ import java.net.URL import java.util import javafx.scene.{control => jfxsc} import javafx.{event => jfxe} import javafx.{fxml => jfxf} import scalafx.scene.control.TextField class GetPropertyController extends jfxf.Initializable { @jfxf.FXML private var tfDelegate1: jfxsc.TextField = null @jfxf.FXML private var tfDelegate2: jfxsc.TextField = null @jfxf.FXML private var tfDelegate3: jfxsc.TextField = null @jfxf.FXML private var tfDelegate4: jfxsc.TextField = null @jfxf.FXML private var tfDelegate5: jfxsc.TextField = null @jfxf.FXML private var tfDelegate6: jfxsc.TextField = null @jfxf.FXML private var tfDelegate7: jfxsc.TextField = null @jfxf.FXML private var tfDelegate8: jfxsc.TextField = null @jfxf.FXML private var tfDelegate9: jfxsc.TextField = null @jfxf.FXML private var tfDelegate10: jfxsc.TextField = null @jfxf.FXML private var tfDelegate11: jfxsc.TextField = null @jfxf.FXML private var tfDelegate12: jfxsc.TextField = null private var tf1: TextField = _ private var tf2: TextField = _ private var tf3: TextField = _ private var tf4: TextField = _ private var tf5: TextField = _ private var tf6: TextField = _ private var tf7: TextField = _ private var tf8: TextField = _ private var tf9: TextField = _ private var tf10: TextField = _ private var tf11: TextField = _ private var tf12: TextField = _ @jfxf.FXML private def handleButtonAction(event: jfxe.ActionEvent) { System.out.println("You clicked me!") tf1.setText(System.getProperty("java.vendor")) tf2.setText(System.getProperty("java.vendor.url")) tf3.setText(System.getProperty("java.home") ) tf4.setText(System.getProperty("java.awt.graphicsenv")) tf5.setText(System.getProperty("java.class.version")) tf6.setText(System.getProperty("sun.boot.class.path")) tf7.setText(System.getProperty("java.version")) tf8.setText(System.getProperty("javafx.runtime.version")) tf9.setText(System.getProperty("java.runtime.version")) tf10.setText(System.getProperty( "os.name")) tf11.setText(System.getProperty( "os.arch" )) tf12.setText(System.getProperty("os.version" )) } def initialize(url: URL, rb: util.ResourceBundle) { tf1 = new TextField(tfDelegate1) tf2 = new TextField(tfDelegate2) tf3 = new TextField(tfDelegate3) tf4 = new TextField(tfDelegate4) tf5 = new TextField(tfDelegate5) tf6 = new TextField(tfDelegate6) tf7 = new TextField(tfDelegate7) tf8 = new TextField(tfDelegate8) tf9 = new TextField(tfDelegate9) tf10 = new TextField(tfDelegate10) tf11 = new TextField(tfDelegate11) tf12 = new TextField(tfDelegate12) } }
5. fxmlファイルはSceneBuilder 1.1で作りIDEAのプロジェクトのフォルダに保存します。
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.geometry.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.scene.text.*?> <AnchorPane id="anchorPane" prefHeight="500.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="GetPropertyController"> <children> <VBox padding="$x2" prefHeight="500.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <children> <Button mnemonicParsing="false" onAction="#handleButtonAction" text="Click me!" textFill="RED"> <font> <Font size="16.0" fx:id="x1" /> </font> </Button> <GridPane prefHeight="382.0" prefWidth="565.0"> <children> <Label alignment="CENTER" contentDisplay="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.vendor" textAlignment="LEFT" textFill="#0029ff" textOverrun="ELLIPSIS" underline="false" wrapText="false" GridPane.columnIndex="0" GridPane.rowIndex="0" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.vendor.url" textFill="#33ffe7" GridPane.columnIndex="0" GridPane.rowIndex="1" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.home" textFill="#ff9533" GridPane.columnIndex="0" GridPane.rowIndex="2" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.awt.graphicsenv" textFill="#ff66af" GridPane.columnIndex="0" GridPane.rowIndex="3" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.class.version" textFill="#00cc83" GridPane.columnIndex="0" GridPane.rowIndex="4" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="sun.boot.class.path" textFill="#0c9900" GridPane.columnIndex="0" GridPane.rowIndex="5" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.version" textFill="#1400ff" GridPane.columnIndex="0" GridPane.rowIndex="6" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="javafx.version" textFill="#66edff" GridPane.columnIndex="0" GridPane.rowIndex="7" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="java.runtime.version" textFill="#ffb800" GridPane.columnIndex="0" GridPane.rowIndex="8" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="os.name" textFill="#ff00b8" GridPane.columnIndex="0" GridPane.rowIndex="9" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="os.arch" textFill="#00e0ff" GridPane.columnIndex="0" GridPane.rowIndex="10" /> <Label alignment="CENTER" font="$x1" prefHeight="28.0" prefWidth="280.0" text="os.version" textFill="#00cc21" GridPane.columnIndex="0" GridPane.rowIndex="11" /> <TextField fx:id="tfDelegate1" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="0" /> <TextField fx:id="tfDelegate2" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="1" /> <TextField fx:id="tfDelegate3" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="2" /> <TextField fx:id="tfDelegate4" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="3" /> <TextField fx:id="tfDelegate5" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="4" /> <TextField fx:id="tfDelegate6" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="5" /> <TextField fx:id="tfDelegate7" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="6" /> <TextField fx:id="tfDelegate8" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="7" /> <TextField fx:id="tfDelegate9" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="8" /> <TextField fx:id="tfDelegate10" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="9" /> <TextField fx:id="tfDelegate11" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="10" /> <TextField fx:id="tfDelegate12" prefHeight="32.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowIndex="11" /> </children> <columnConstraints> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> </columnConstraints> <rowConstraints> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> </rowConstraints> <VBox.margin> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" fx:id="x2" /> </VBox.margin> </GridPane> </children> </VBox> </children> </AnchorPane>
6. 後はRunメニューからRunさせてください。
Click me!ボタンをクリックするとPropertyを表示します。
Java Mission Controlを使ってみる
1. まず適当なJavaアプリをFlightRecorderのオプションを指定してFlightRecordingします。
時間は60秒間で保存するファイル名はmyrecording.jfrです。
拡張子を.jfrとする事で後でJava Mission Controlに読み込ませ表示させます。
この場合のメインクラス名はFileChooserAndTabPaneです。
ターミナルで以下のようにコマンドを打ちます。
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr FileChooserAndTabPane
2. ファイルが作成されます。
3. Java Mission Controlを起動します。
ターミナルで以下のようにコマンドを打ちます。
/Library/Java/JavaVirtualMachines/jdk1.7.0_40.jdk/Contents/Home/bin/jmc
4. 起動したらFileメニューから”ファイルを開く”を選択して手順2で作成されたmyrecording.jfrを読み込み。
5. Fileメニューから”接続”を選択すると現在起動中のJavaアプリに接続できます。
6. 接続するJVMを選択してNext ボタンをクリックする。
7. ”JMXコンソールを開始します”を選択してFinishボタンをクリックする。
8. 表示される
Java Mission Control解説動画
ヘルプメニューでJava Mission Controlヘルプを選択するとSafariで表示されます。
Marcus Hirtさんのブログ
その他詳しい情報はこのページで
Java Platform, Standard Edition Java Flight Recorder Runtime Guide
ScalaFXでTreeView
ScalaFXはJavaFX 2.2.xのラッパーでScala言語で書かれたUI DSLです。
JavaFX 2.2.xのラッパーとしては開発が進んでいる方です。
最近ScalaFX 1.0 M5とScalaFX 8.0 M1が出ました。
今回はIDEにIntelliJ IDEA 12 Community Editionを使いScalaFX 1.0 M5でTreeViewを作成します。
Scalaはすでにホームディレクトリにインストールしてあるscala-2.10.1を使います。
Pluginのインストールはこのサイトが親切丁寧です。
【図解】Scala 2.10 + IntelliJ IDEA 12 で「Hello World」する
すでにscala-2.10.1がインストールしてある場合など若干の変更をすれば良いです。
準備ができていると仮定します。
1. FileメニューよりNew Project...を選択。
プロジェクト名、Scala Homeを記入
2. Fileメニューから”Project Structure...”を選択
Project Settingsの中のLibrariesを選択
3. "+" をクリック
4. scalafx_2.10-1.0.0-M5.jarを追加する。
5. srcフォルダをcontrolキー+クリック又はマウスの右クリックでNew=>Scala Classを作成します。
TreeViewSampleの元のコードはオラクルのJavaFXチュートリアルにあるものを使いこれをScalaFXで書き直しました。
13 Tree View
ScalaFXのTreeViewは開発途中ということもあってラップが中途半端な感じです。
このためコンパイル実行が可能なようにコードを少し工夫してあります。
TreeViewSample.scala
/** * Created with Intellij IDEA. * User: hshino * Date: 13/09/14 * Time: 20:32 * To change this template use File | Settings | File Templates. */ import scalafx.Includes._ import scalafx.application.JFXApp import scalafx.application.JFXApp.PrimaryStage import scalafx.scene.Node import scalafx.scene.Scene import scalafx.scene.control.TextField import java.util import scalafx.scene.control.TreeItem import scalafx.scene.control.TreeView import scalafx.scene.image.Image import scalafx.scene.image.ImageView import scalafx.scene.input.KeyEvent import javafx.scene.{control => jfxsc} import scalafx.scene.paint.Color import javafx.util.Callback import javafx.scene.{input => jfxsi} import scalafx.beans.property.StringProperty import scalafx.scene.layout.VBox import scalafx.scene.layout.Priority class Employee (name: String , department: String ){ val name2:StringProperty = new StringProperty(name) val department2:StringProperty = new StringProperty(department) def getName:String = { name2.get() } def setName(fName:String ):Unit = { name2.set(fName) } def getDepartment:String = { department2.get() } def setDepartment(fName:String ):Unit = { department2.set(fName) } } class TextFieldTreeCellImpl extends jfxsc.TreeCell[String] { private val tfDelegate: jfxsc.TextField = new jfxsc.TextField private var tf: TextField = _ override def startEdit(){ super.startEdit() if (tf == null) { createTextField() } setText(null) setGraphic(tf) tf.selectAll() } override def cancelEdit(){ super.cancelEdit() setText(getItem) setGraphic(getTreeItem.getGraphic) } override def updateItem(item:String , empty: Boolean ) { super.updateItem(item, empty) if (empty) { setText(null) setGraphic(null) } else { if (isEditing) { if (tf != null) { tf.setText(getString) } setText(null) setGraphic(tf) } else { setText(getString) setGraphic(getTreeItem.getGraphic) } } } def createTextField():Unit = { tf = new TextField(tfDelegate) println("tf:TextField = " + tf) tf.setText (getString) tf.setEditable(true) tf.onKeyReleased = (event: KeyEvent) => { if (event.getCode == jfxsi.KeyCode.ENTER) { println("KeyCode.ENTER") commitEdit(tf.getText) } else if (event.getCode == jfxsi.KeyCode.ESCAPE) { cancelEdit() } } } def getString: String ={ if(getItem == null ){ "" }else{ getItem.toString } } } object TreeViewSample extends JFXApp { private val tvDelegate: jfxsc.TreeView[String] = new jfxsc.TreeView[String] private var tv: TreeView[String] = _ val rootIcon: Node = { new ImageView(new Image(this.getClass.getClassLoader.getResourceAsStream("root.png"))) } private val rootNodeDelegate: jfxsc.TreeItem[String] = new jfxsc.TreeItem[String] private var rootNode: TreeItem[String] = _ rootNode = new TreeItem[String](rootNodeDelegate) val depIcon = { new Image(this.getClass.getClassLoader.getResourceAsStream("department.png")) } val employees = util.Arrays.asList( new Employee("Ethan Williams", "Sales Department"), new Employee("Emma Jones", "Sales Department"), new Employee("Michael Brown", "Sales Department"), new Employee("Anna Black", "Sales Department"), new Employee("Rodger York", "Sales Department"), new Employee("Susan Collins", "Sales Department"), new Employee("Mike Graham", "IT Support"), new Employee("Judy Mayer", "IT Support"), new Employee("Gregory Smith", "IT Support"), new Employee("Jacob Smith", "Accounts Department"), new Employee("Isabella Johnson", "Accounts Department")) for (i <- 0 to employees.size -1) { val empLeaf:TreeItem[String] = new TreeItem[String](employees.get(i).getName) var found:Boolean = false for ( depNode <- rootNode.getChildren) { if (depNode.getValue.contentEquals(employees.get(i).getDepartment)){ depNode.getChildren.add(empLeaf) found = true } } if (!found) { val depNode:TreeItem[String] = new TreeItem[String]( employees.get(i).getDepartment, new ImageView(depIcon) ) rootNode.getChildren.add(depNode) depNode.getChildren.add(empLeaf) } } stage = new PrimaryStage { title = "Tree View Sample" width = 500 height = 500 println("rootNode = " + rootNode) rootNode.value = "MyCompany Human Resources" rootNode.graphic = rootIcon rootNode.setExpanded(true) tv = new TreeView(tvDelegate) tv.setRoot(rootNode) tv.setShowRoot(true) tv.setEditable(true) tv.setPrefSize(500, 500) tv.vgrow = Priority.ALWAYS tv.hgrow = Priority.ALWAYS tv.setCellFactory(new Callback[jfxsc.TreeView[String],jfxsc.TreeCell[String]](){ override def call( p: jfxsc.TreeView[String]) :jfxsc.TreeCell[String] = { new TextFieldTreeCellImpl() } }) scene = new Scene { fill = Color.LIGHTGRAY root = new VBox { vgrow = Priority.ALWAYS hgrow = Priority.ALWAYS content = tv } } } }
6. 後はdepartment.pngとroot.pngをsrcフォルダにコピーしてRunメニューからRunさせてください。
ProScalaFXには参考になるコードが沢山あります。
ScalaFX 1.0 Milestone 5 が出ました
前回のM4からはだいぶ間があきましたがScalaFX 8と同時開発のようですからやむを得ないという感じでしょうか。
ScalaFX Downloads
M5の変更点はこちらが詳しいです。
What is New in ScalaFX 1.0 Milestone 5
PieChartが加わりましたね。
PieChartDemo.scala
import scalafx.application.JFXApp import scalafx.collections.ObservableBuffer import scalafx.scene.Scene import scalafx.scene.chart.PieChart object PieChartDemo extends JFXApp { val fruitsData = Seq(("みかん", 27), ("パイナップル", 11), ("りんご", 28), ("ぶどう", 30), ("梨", 15)) stage = new JFXApp.PrimaryStage { title = "PieChart Demo by ScalaFX M5 " scene = new Scene { root = new PieChart() { title = "Pie Chart" clockwise = false data = ObservableBuffer(fruitsData.map {case (x, y) => PieChart.Data(x, y)}) } } } }
KotlinでJavaFX 2.2のContextMenuを利用する
JavaFX 2.2のContextMenuとはPopupMenuの事です。
今回はシンプルなボタンをクリックすると”Hello Kotlin World!”とラベルにテキストをセットするものにExitというメニューでJavaFXプログラムを終了させるContextMenuをマウスの右クリックで表示させます。
プログラムにはFXMLを使いコントローラークラスもKotlinで書きます。
コントローラークラスにはKotlinらしく関数リテラルも使ってみます。
コードは以下のようになります。
JavaFX_FXML_TEST.kt
package javafx_fxml_test import javafx.application.Application import javafx.fxml.FXMLLoader import javafx.scene.Parent import javafx.scene.Scene import javafx.stage.Stage import javafx.application.Platform fun main(args: Array<String>) = Application.launch(JavaFX_FXML_TEST().javaClass, args.makeString("")) public class JavaFX_FXML_TEST : Application() { override public fun init():Unit{ println("javafx.version = " + System.getProperty("javafx.version")) println("init()はPlatform.isFxApplicationThread() が " + Platform.isFxApplicationThread()) } override public fun start(p0: Stage?) : Unit { val root : Parent? = FXMLLoader.load(getClass().getResource("/Sample.fxml")) val scene: Scene = Scene(root) p0?.setScene(scene) p0?.show() } }
SampleController.kt
package javafx_fxml_test import java.net.URL import java.util.ResourceBundle import javafx.event.ActionEvent import javafx.fxml.FXML import javafx.fxml.Initializable import javafx.scene.control.Label import javafx.scene.layout.AnchorPane import javafx.scene.input.MouseEvent import javafx.scene.input.MouseButton import javafx.scene.control.ContextMenu import javafx.scene.control.MenuItem public class SampleController() : Initializable { FXML private var label : Label? = null FXML private var anchorPane : AnchorPane? = null FXML private fun handleButtonAction(event : ActionEvent?) : Unit { println("You clicked me!" +"\n" + event) label?.setText("Hello Kotlin World!") } override public fun initialize(p0: URL?, p1 : ResourceBundle?) : Unit { val cm = ContextMenu() val cmItem1 = MenuItem("Exit") cmItem1.setOnAction{ System.exit(0) } cm.getItems()?.add(cmItem1) val handler = {(p0:MouseEvent?) -> if (p0?.getButton() == MouseButton.SECONDARY) { cm.show(anchorPane, p0?.getScreenX() as Double, p0?.getScreenY() as Double) println("MOUSE_CLICKED x = " + p0?.getScreenX() + " y = " + p0?.getScreenY()) } } anchorPane?.addEventHandler(MouseEvent.MOUSE_CLICKED, handler) } }
Sample.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" fx:id="anchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="javafx_fxml_test.SampleController"> <children> <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" /> <Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" /> </children> </AnchorPane>