このエントリは、JavaFX Advent Calendar 2015 の記事です。
昨日分は、aoeさんの、「javapackagerの紹介」でした。

JavaFXでは、ListViewやTreeViewの表示をカスタマイズできます。
ここでは例として、ListViewの場合を考えてみましょう。

表示方法を決めるために、ListCellのサブクラスを作成します。
以下、タイトルと著者を持つ、Bookクラスを表示するListViewのためのListCell<Book>です。

package samplej;

public class BookListCell extends ListCell<Book>{

    private final Label titleLabel = new Label();
    private final Label authorLabel = new Label();
    private final HBox container = new HBox(8.0, titleLabel, authorLabel);

    @Override
    protected void updateItem(Book book, boolean empty){
        super.updateItem(book, empty);
        if(!empty){
            titleLabel.setText(book.titlePropertey().get());
            authorLabel.setText(book.authorProperty().get());
            setGraphic(container);
        }
    }
}
※インポートなどは省略しています。以下同様。

そして、上記クラスをインスタンス化する、CellFactoryをListViewに設定します。

ListView<Book> list = new ListView<>();
list.setCellFactory(listView -> new BookListCell());

Callbackクラスなので、ラムダで書けますね。
こうすることで、ListViewを描画するときに、BookListCellクラスのupdateItemメソッドが実行されるわけです。

さて本題です。
これ、そのままScalaFXでやるとどうなるでしょうか?

package samples

class BookListCell extends ListCell[Book]{

  val titleLabel = new Label()
  val authorLabel = new Label()
  val container = new HBox(8.0, titleLabel, authorLabel)

  override def updateItem(book : Book, empty : Boolean) : Unit = {
  //この時点でコンパイルエラー
  }
}

はい、updateItemメソッドをoverrideで書くとコンパイルエラーになります。
おかしいなー、と思ってソースを追ってみると、Cell.scalaにこんなコメントがあります。(一番最後の方)
// TODO: implement updateItem(T item, boolean empty)
// might be difficult since updateItem is a protected method which needs to be defined in the delegate's class

まあ、確かにそうなんだよ...
ScalaFXの基本方針は、JavaFXの各クラスのラッパー的なクラスを用意して、各メソッドの実行を内部で持つJavaFXのインスタンスに委譲する感じになっています。
でも、CellクラスのupdateItemメソッドはprotectedなのでそういう方針では実装できませんよね。

とはいえ、そもそもupdateItemメソッドは外部から実行されるわけじゃないし(protectedなんだから)、やろうと思えば、Javaのソースと合わせて以下のようにできます。
ScalaFX側ではBookListCellを作成せず、

val list = new ListView[Book]()
list.cellFactory = (listView : ListView[Book]) => new samplej.BookListCell()

とします。
ListViewはScalaFXのクラスですが、samplej.BookListCellは先程作成したJavaのクラスの方です。
ScalaFXのListCellでオーバーライドはできませんが、ListCellの委譲先のインスタンスに、必要な処理をオーバーライドしたJavaFXのインスタンスを持たせる感じですね。
なお上記のソースは、ScalaFXで用意された良いように計らってくれるimplicit conversionを使った記述なので、ちゃんと書くと次のような感じになります。

list.cellFactory = (listView : ListView[Book]) => new ListCell[Book](new samplej.BookListCell())

JavaFXのサンプルで作った方のBookListCellで必要な処理をオーバーライドしているので、ScalaFXの方では委譲先を指定するコンストラクタを使用して、ListCellを作成しています。
※ちなみに、通常(引数のないコンストラクタ)は、内部でJavaFXのインスタンスが生成されています

JavaFXとScalaFX、両方のクラスが同じソースに出てくるといろいろ面倒というかややこしくなるのであまり好ましくないと思うのですが、やっぱり細かいところで必要になってしまいますね...


明日は、opengl-8080さんの、「Spring BootとJavaFXを組み合わせてみる」です。