JRubyでJavaFX+FXML


お手軽にマルチプラットフォームなGUIアプリを作れそうなので、ちょっと調べてみた。
元がJavaなので面倒なところは面倒だけど、それなりにお手軽。

全コードは下記においてある。
https://github.com/kinumi/jruby-javafx-samples


## sample1. ウインドウを表示してみる

ウインドウを表示するだけ。
特に役には立たないけど、お手軽。

```ruby:sample1/Gemfile
source 'https://rubygems.org/'

gem 'jrubyfx'
gem 'jrubyfx-fxmlloader'
```

```ruby:sample1/main.rb
#!jruby
#coding: utf-8

require 'bundler'
Bundler.require

class Main < JRubyFX::Application
  def start(stage)
    stage.show
  end
end

Main.launch
```

![スクリーンショット 2015-08-13 11.08.17.png](https://qiita-image-store.s3.amazonaws.com/0/83429/d5cad15d-fc80-52ef-63f0-6cde44771d2d.png "スクリーンショット 2015-08-13 11.08.17.png")

## sample2. FXMLで画面を作る

FXMLは[JavaFX Scene Builder](http://www.oracle.com/technetwork/java/javase/downloads/javafxscenebuilder-info-2157684.html)で作るとお手軽。そのへんは省略。

```sample2/ui/hello_window.fxml
省略
```

FXMLに対応したコントローラを作る。
内容はこんな感じ。

```ruby:sample2/ui/hello_window.rb
#coding: utf-8

class HelloWindow
  include JRubyFX::Controller

  # 対応するFXMLファイルを、main.rbで設定したfxml_rootからの相対パスで指定する
  fxml 'hello_window.fxml'

  # FXMLのイベントハンドラと同名のメソッドを作っておくと勝手に呼んでくれる
  def on_hello_action
    # FXMLでfx:idを付与したノードはインスタンス変数として参照できる
    @txt_hello.text = 'Hello JRuby + JavaFX!!'
  end
end
```

main.rbはこんな感じ。

```ruby:sample2/main.rb
#!jruby
#coding: utf-8

require 'bundler'
Bundler.require

# FXMLを「ui」ディレクトリ以下から探すよう設定する
fxml_root File.dirname(__FILE__) + '/ui'
# コントローラ
require_relative 'ui/hello_window'

class Main < JRubyFX::Application
  def start(stage)
    # stageに表示するfxmlを指定する
    stage.fxml HelloWindow
    stage.show
  end
end

Main.launch
```

ボタンを押すと、テキストフィールドに文字列が表示される。

![スクリーンショット 2015-08-13 12.01.09.png](https://qiita-image-store.s3.amazonaws.com/0/83429/dc804733-c07d-631a-c711-344d89f8f253.png "スクリーンショット 2015-08-13 12.01.09.png")

## sample3. コントローラの初期化時にパラメータを渡す

こんなかんじでパラメータを渡せるっぽい。

```ruby:sample3/ui/hello_window.rb
#coding: utf-8

class HelloWindow
  include JRubyFX::Controller

  fxml 'hello_window.fxml'

  # コンストラクタを定義しておく
  def initialize(initial_text)
    @txt_hello.text = initial_text
  end

  def on_hello_action
    @txt_hello.text = 'Hello JRuby + JavaFX!!'
  end
end
```

```ruby:sample3/main.rb
#!jruby
#coding: utf-8

require 'bundler'
Bundler.require

fxml_root File.dirname(__FILE__) + '/ui'
require_relative 'ui/hello_window'

class Main < JRubyFX::Application
  def start(stage)
    # :initializeというオプションに、コントローラのコンストラクタに渡したい引数を配列で指定する
    stage.fxml HelloWindow, :initialize => ['click button ->']
    stage.show
  end
end

Main.launch
```

こんな感じで、main.rbから渡した文字列が表示される。

![スクリーンショット 2015-08-13 13.54.40.png](https://qiita-image-store.s3.amazonaws.com/0/83429/6979f5ff-82ba-b888-508d-2ec3bc1254d3.png "スクリーンショット 2015-08-13 13.54.40.png")

## sample4. TableViewを使う

これを使えると結構便利。

```ruby:sample4/ui/table_view_window.rb
#coding: utf-8

class TableViewWindow
  include JRubyFX::Controller

  fxml 'table_view_window.fxml'

  # Javaのクラスを参照するためのモジュール
  # ほんとは共通モジュール的なところで定義するとよさげ
  # サンプルなので適当にここで定義する
  module J
    include_package 'javafx.beans.property'
  end

  # コンストラクタ
  def initialize
    # テーブルに表示する内容
    # JavaのObservableListを設定する
    @table.items = FXCollections.observable_list([])

    # Columnごとに、CellValueFactoryを設定する
    # JavaのサンプルではPropertyValueFactoryを使っていることが多いが、
    # JRubyだとうまくいかない。lambdaやProc.newなどで、自前で実装してやる必要があった
    # (もっとうまいやりかたがあるかもしれない。。)
    @col_name.cell_value_factory = lambda {|v| v.value.name }
    @col_address.cell_value_factory = lambda {|v| v.value.address }
    @col_tel.cell_value_factory = lambda {|v| v.value.tel }

    # とりあえずダミーデータを1000件分登録してみる
    Faker::Config.locale = :ja
    1000.times do
      name = Faker::Name.name
      address = Faker::Address.state + ' ' + Faker::Address.city + ' ' + Faker::Address.building_number
      tel = Faker::PhoneNumber.phone_number
      @table.items << TableRecord.new(name, address, tel)
    end
  end

  # テーブル1レコードぶんのデータクラス
  class TableRecord
    attr_reader :name
    attr_reader :address
    attr_reader :tel

    def initialize(name, address, tel)
      @name = J::SimpleStringProperty.new(name)
      @address = J::SimpleStringProperty.new(address)
      @tel = J::SimpleStringProperty.new(tel)
    end
  end
end
```

![スクリーンショット 2015-08-13 15.41.38.png](https://qiita-image-store.s3.amazonaws.com/0/83429/d773e236-22c9-d54a-a28e-0d277f908ef6.png "スクリーンショット 2015-08-13 15.41.38.png")

## FXMLで作った画面にFXMLで作った画面を埋め込む

なんて表現していいか難しいけど、使いどころとしてはTabPaneのTabを動的に増やしたいときなど、結構ある。

JavaFXでNode突っ込むようなところ（TabのsetContentとか、BorderPaneのsetBottomとか）には、コントローラのインスタンスを突っ込んでやればいいっぽい。

```
そのうち書く
```