この記事は、JavaFX Advent Calendar 2016の21日目です。
昨日はHASUNUMA KenjiさんのJavaFX の TextField に入力補完を付けてみるでした 

去年のAdvent Calendarで、ListViewの扱いについて書きました。
ScalaFXの実装方針との兼ね合いで、ListCell#updateItemをオーバーライドするというCell描画の方式がうまくいかない、という内容です。

今年の記事を書くにあたって、このあたりについてもう一度調べてみたら、ScalaFXのGitHubに、issueがあがっていたので、今年は一年越しの続編として、これについて書いてみます。

まず、 JavaFXで、CellFactoryを設定するメソッドは以下となります。
public final void setCellFactory(Callback<TreeView<T>,TreeCell<T>> value)
ScalaFXでは以下。
def cellFactory_=(v: (TreeView[T]) ⇒ TreeCell[T]): Unit

どちらも、TreeViewを引数として受けて、描画処理を実装したTreeCellのインスタンスを返すコールバックを設定します。
しかし描画処理自体は、TreeCell#updateItem をオーバーライドして実装するので、ScalaFXのクラスだけでやろうとするとうまくいきません。上記のissueにも例がありますが、ちゃんとやろうとすると結局、ScalaFXのcellFactoryでも、JavaFXのTreeCellを使うことになってしまいます。
このへんは去年の記事で書いたとおりです。(私の記事はListCellを使って、継承したクラスを別に定義。issueの例ではTreeCellをメソッド内で直接newしてますが、JavaFXのクラスを使ってupdateItemメソッドをオーバーライドしなきゃならない、という意味で言いたいことは一緒です)

で、issueでは、cellFactoryメソッドのシグネチャを変えるのがいいんじゃね?という内容になってます。
def cellFactory_=(u: ((TreeCell[T], T, Boolean) => Unit))

というわけで、試しに実装してみました。
※FactoryなのにUnitなのがどうかなーと思ったので、「cellRenderer」にしてます
※issueではTreeCellですが、去年のコードを一部再利用している関係でListCellです

●ListViewの代わりに使うクラス
package com.bugworm.advent2016

import javafx.util.Callback
import javafx.scene.{control => jfxsc}

import scalafx.Includes._
import scalafx.scene.control.{ListCell, ListView}

class NewListView[T](override val delegate: jfxsc.ListView[T] = new jfxsc.ListView[T])
  extends ListView[T]{

  private var renderer_ : (ListCell[T], T, Boolean) => Unit = _
  def cellRenderer = renderer_
  def cellRenderer_=(r: (ListCell[T], T, Boolean) => Unit): Unit ={
    renderer_ = r;
    cellFactory() = new Callback[jfxsc.ListView[T], jfxsc.ListCell[T]] {
      override def call(param: jfxsc.ListView[T]): jfxsc.ListCell[T] = {
        new jfxsc.ListCell[T]{
          override def updateItem(item: T, empty: Boolean): Unit ={
            super.updateItem(item, empty)
            (item, empty) match {
              case (null, _) | (_, true) => {
                setText(null)
                setGraphic(null)
              }
              case (item, empty) => renderer_.apply(this, item, empty)
            }
          }
        }
      }
    }
  }
}

●使用例
package com.bugworm.advent2016

import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.collections.ObservableBuffer
import scalafx.scene.Scene
import scalafx.scene.control.Label
import scalafx.scene.layout.VBox

object CellSample extends JFXApp{

  stage = new PrimaryStage{
    scene = new Scene(new VBox(
      Label("Cell Factory Test"),
      new NewListView[Book](){
        prefHeight = 100.0
        items = ObservableBuffer(
          new Book("ゴルゴ13", "さいとうたかを"),
          new Book("数学ガール", "結城浩"),
          new Book("三国志", "吉川英治"),
          new Book("ガラスの仮面", "美内すずえ"),
          new Book("哭きの竜", "能條純一"),
          new Book("傷だらけの天使たち", "喜国雅彦")
        )
        cellRenderer = (cell, item, empty) => {
          cell.text = item.title + " [" + item.author + "]"
        }
      }
    ))
  }
  stage.show()
}

ListViewの代わりのクラスはJavaFXとScalaFXが入り混じって結構面倒な実装になっていますが、使用例の方はJavaFXのクラスを使う必要が無いので、割といい感じなんじゃないかと思います。