JRubyFXでGUIを作るときに悩んだ点と解法

JRubyFXだけではなくJavaFXでの書き方が知りたかったという例も含めてます。
JRubyFXのWikiとサンプルをもっと見るべきだった。

###モーダルウィンドウの作り方

```.rb
child_stage = Java::JavafxStage::Stage.new
child_stage.initModality Modality::WINDOW_MODAL
child_stage.initOwner @stage
child_stage.show
```
`@stage` は親ウィンドウのstage。
`child_stage`は`JRubyFX::Application`を継承したクラスに渡せば、普通に扱える。

###.set~をなくす
```.rb
# 2行
l = label 'text'
l.setStyle "-fx-background-color: red;"
# 1行
l = label 'text', style: "-fx-background-color: red;"

# 2行
b = button 'push'
b.setOnAction{ p "pushed!" }
# 1行
b  = button 'push', onAction: Proc.new{ p "pushed!" }
```

`set~ 〜` は生成時に `~: 〜` で(多分)代用できる。

###Insets

```.rb
l = label('?', padding: insets(20)) # 四方同じ値
l.setPadding insets(1,2,3,40)  # 上右下左
```

サンプルにあるのに気づかず、JRubyFXでの書き方も分からず、苦労してたどり着いた。

###アラートウィンドウの作り方

```.rb
alert = Java::JavafxSceneControl::Alert.new(
  Java::JavafxSceneControl::Alert::AlertType::ERROR)
with(alert, title: 'タイトル', headerText: 'ヘッダー', contentText: 'コンテント')
alert.show
```

`[:INFORMATION, :CONFIRMATION, :ERROR, :NONE, :WARNING]`はどれも似た感じ（だと思う）。
[jrubyfx-dialogs](https://github.com/byteit101/jrubyfx-dialogs)というgemを使っても良さそう。
Wikiに書いてあるが、 `with` を使えば `set~` を一度に出来る。

###styleを使わずにラベルの文字色を変える

```.rb
l = label('aa', textFill: Color.rgb(0,0,255))
l.setTextFill Color.web('FF0000')
```

[JavaFXのColor](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/paint/Color.html)は作り方に種類があるので、少し悩む。
定義済みの色もかなりあるので、さらに悩む。

###styleを使わずに背景色を変える

```.rb
l = label '背景色変更' 
l.setBackground(
  Java::JavafxSceneLayout::Background.new(
    Java::JavafxSceneLayout::BackgroundFill.new(
      Color::PURPLE,  # ここのColorインスタンスを変えれば別の色に
      Java::JavafxSceneLayout::CornerRadii::EMPTY,
      Insets::EMPTY
    )
  )
)
```

Colorのインスタンスを受け取って、Backgroundのインスタンスを生成するメソッドを定義しないとだるい。

###ファイル・チューザで拡張子を指定

```.rb
fc = file_chooser do
  add_extension_filter('テキストファイル (*.txt)')
end
```

なぜか`add_extension_filter`のキャメルケースがない。



###子ウィンドウから親ウィンドウの要素をいじる

```.rb
require 'jrubyfx'

class ChildWindow < JRubyFX::Application
  attr_accessor :parent, :stage
  def initialize(parent, stage = Java::JavafxStage::Stage.new)
    super()
    @parent = parent
    @stage  = stage
  end

  def start(stage=@stage)
    parent = @parent
    with(stage, width: 400, height: 400, title: 'Child') do
      layout_scene do
        vbox do
          ta = text_area('',prefWidth: 400, prefHeight: 300,
            text: 'parent["#vb"].add label("I am Child")')
          eb = button('eval', onAction: Proc.new{ eval ta.text })
        end
      end
      show
    end
  end
end

class ParentWindow < JRubyFX::Application
  def start(stage)
    with(stage, width: 200, height: 600, title: 'Parent') do
      ChildWindow.new(stage).start
      layout_scene do
        vbox(id: 'vb')
      end
      show
    end
  end
end

ParentWindow.launch
```

JRubyFX::Applicationを継承したクラスのinitializeメソッドは `super()` の必要がある。
参考：[Javaクラスを継承したRubyクラスのinitializeでエラー - 骨を盗んで肉を盗まず](http://d.hatena.ne.jp/icalo35/20130428) 

子ウィンドウのボタンを押すとテキストエリアの中身が実行され、親ウィンドウのVBoxにLabelが追加される。
子ウィンドウから親ウィンドウの要素を直接アクセスしてしまえば良い。
リンクは適当に作れるので、双方向に命令を飛ばし合うことも可能。

コードは用意しないけど、JRubyFX::Controllerを使う場合、コントローラー`initialize`内の`self`を別ウィンドウに渡し、受け取った側は`(受け取ったself).instans_eval{ ~ }`すると楽。

###スコープが外れる
```scope.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>


<VBox fx:id="vb" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" />
```

```main.rb
require 'jrubyfx'

fxml_root File.dirname(__FILE__)

class MainCon
  include JRubyFX::Controller
  fxml 'scope.fxml'

  def initialize(stage)
    b1 = button('b1')
    b1.setOnAction{ |e| ss(e) }
    b2 = button('b2') do
      setOnAction{ |e| ss(e) }
    end
    @vb.children.addAll(b1,b2)
  end

  def ss(e)
    p e.source.text
  end
end

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

Main.launch
```

`fx:id="vb"`に対応する`@vb`に定義したボタンを2つ追加。
b1ボタンを押すと`b1`が表示されるが、b2ボタンでは以下のように`NoMethodError`が起こる。

```
Exception in thread "JavaFX Application Thread" org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `ss' for #<Java::JavafxSceneControl::Button:0x5a3a9e15 @supers={:ss=>1}>
	at org.jruby.RubyBasicObject.method_missing(org/jruby/RubyBasicObject.java:1657)
	at RUBY.method_missing(/パス/lib/ruby/gems/jrubyfx-1.2.0-java/lib/jrubyfx/dsl.rb:103)
	at RUBY.method_missing(/パス/lib/ruby/gems/jrubyfx-1.2.0-java/lib/jrubyfx/core_ext/precompiled.rb:1809)
	at RUBY.block in initialize(/パス/src/main.rb:13)
```

コードの一部を以下のように変えてselfをチェック。

```main.rb
# 前略
    b1 = button('b1')
    b1.setOnAction{ p self; p self.class.ancestors }
    b2 = button('b2') do
      setOnAction{ p self; p self.class.ancestors }
    end
# 後略
```

```.rb
# b1
#<MainCon:0x6fb3dc36 @nodes_by_id={}, @vb=#<Java::JavafxSceneLayout::VBox:0x3d96ed13>, @scene=#<Java::JavafxScene::Scene:0x1b76a607>, @stage=#<Java::JavafxStage::Stage:0x44e6e64f>>
[MainCon, JRubyFX::Controller, JRubyFX::DSL, JRubyFX, JRubyFX::Utils::CommonUtils, JRubyFX::FXImports, Object, Kernel, BasicObject]
# b2
#<Java::JavafxSceneControl::Button:0x5b99e118>
[Java::JavafxSceneControl::Button, Java::JavafxSceneControl::ButtonBase, Java::JavafxSceneControl::Labeled, Java::JavafxSceneControl::Control, Java::JavafxSceneControl::Skinnable, Java::JavafxSceneLayout::Region, Java::JavafxScene::Parent, Java::JavafxScene::Node, JRubyFX::DSL, JRubyFX, JRubyFX::Utils::CommonUtils, JRubyFX::FXImports, Java::JavafxEvent::EventTarget, Java::JavafxCss::Styleable, Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy, JavaProxyMethods, Object, Kernel, BasicObject]
```

b2の方には`MainCon`がない。
共通する先祖でメソッドを定義するか、b1の書き方にするか、生成時にProcを渡すかはお好きに。