問題となったコード

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.input.DragEvent
import scalafx.scene.control.Label
import scalafx.scene.layout.BorderPane

object DropTarget extends JFXApp {
  stage = new PrimaryStage {
    title = "DropTarget"
    scene = new Scene {
      root = new BorderPane {
        padding = Insets(25)
        val message = new Label("Drop files here!")
        center = message
        onDragEntered = (ev: DragEvent) => {
          println("entered")
          ev.consume()
        }
        onDragExited = (ev: DragEvent) => {
          println("exited")
          ev.consume()
        }
        onDragOver = (ev: DragEvent) => {
          println("over")
          ev.consume()
        }
        onDragDropped = (ev: DragEvent) => {
          println("dropped")
          message.text = "Dropped!"
          ev.consume()
        }
      }
    }
  }
}

inkar-us-i.hatenablog.com
こちらで作ったHello Worldに、ドロップ関連のハンドラを設定しただけのシンプルなものです。
onHogehogeに設定する値はEventHandlerなので、ラムダ式の(Event => Unit)型から暗黙的に変換してやる必要があります。そのためにscalafx.Includes._のimportが必要になっています。このあたりはラムダ式を任意の関数型インターフェースのインスタンスとして認識してくれるJavaのほうが賢い作りになっているとも言えますが、そもそもJavaのライブラリのラッパでなければEventHandlerクラスなど定義せずに素直に(Event => Unit)を受け取るような仕様になっていたはずなので、何とも言えない所。

ドロップ関連のイベントハンドラは以下の4つで、それぞれ次の状態で呼ばれます。

名前 説明
onDragEntered ドラッグしてきた対象がこのノードの上に入った時
onDragOver ドラッグしてきた対象がこのノードの上にいる間、繰り返し呼ばれる
onDragExited ドラッグしてきた対象がこのノードの上から消えた時
onDragDropped ドラッグしてきた対象がこのノードの上でドロップされた時

ということなので、先ほどのソースコードを実行して出てきたウィンドウに適当なものをドラッグしてくると、以下のように出力されるはずです。

entered
over
over
over
...
over
(ここでマウスをはなす)
dropped
exited

実際に実行してみた結果はこちら。以下のようなウィンドウが表示されます。

f:id:cloudear8:20160223230123p:plain

これに外からファイルか何かをドラッグしてくると…

entered
over
over
...
over
(ここでマウスをはなす)
exited

onDragDroppedが呼ばれていません。

解決策

DragEvent (JavaFX 2.2)

このドキュメントを参考にすると、実際に要素がドロップされる前の段階で、acceptTransferModesを設定しなければならいようです。
使用できるTransferModeは以下の通り

TransferMode (JavaFX 8)

これに従って、コードを次のように書き換えます。

onDragOver = (ev: DragEvent) => {
  ev.acceptTransferModes(TransferMode.ANY: _*)
  println("over")
  ev.consume()
}

TransferModeはscalafx.scene.input以下で定義されているので、そちらのimportも忘れずに。

これを実行すると期待通りの出力が得られる上、ドロップ後にはウィンドウの表示もちゃんと変更されます。
f:id:cloudear8:20160223230914p:plain
やっぱり困ったらJavaFXのドキュメントに当たるのが正しいようです。


Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

JavaFX & Java8プログラミング―Javaによる新しいGUIプログラミング入門

JavaFX & Java8プログラミング―Javaによる新しいGUIプログラミング入門