Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX DragAndDrop with custom DataFormat

The Overview

I am setting up drag & drop in a Java FX application. When I try to grab custom data from the Dragboard I am getting a java.nio.HeapByteBuffer instead of an Object as the JavaDoc stuggest. This byte buffer cannot be cast to my original data type.

I'm working in Scala so the syntax it a tiny bit different but you get the idea. However, perhaps it's because I'm in Scala that I'm getting this HeapByteBuffer instead of a regular Object?

The Details

Ok, here I'm going to create JavaFX control that can be dragged and dropped. I'm going to attach a String and a MyObject; the String can be retrieved while the MyObject cannot.
Note, I'm aware there are simpler ways to attach simple strings to the dragboard, this is just an example to show that MyObject should be retrievable from the dragboard also.

Here I create a custom component which can be dragged. When it is dragged, two objects are attached to the dragboard: a MyObject and a String.

class ToolboxItem
    extends Label {

    setOnDragDetected(new EventHandler[MouseEvent] {
        def handle(event: MouseEvent) {
        val dragboard = startDragAndDrop(TransferMode.COPY)
        val content = new ClipboardContent()
        content.put(DnDTarget.DndString, "sean is cool")
        content.put(DnDTarget.DndObject, new MyObject)
        dragboard.setContent(content)
        event.consume()
    }

}

MyObject is super simple for the sake of getting going:

class MyObject
    extends Serilaizable

Now, when I receive a drag event, I want to grab this data from the event.
(also, here I define my DataFormat)

object DnDTarget {

    val DndString = new DataFormat("my.custom.dnd.string")
    val DndObject = new DataFormat("my.custom.dnd.object")

}

trait DnDTarget
    extends Node {

    setOnDragOver(new EventHandler[DragEvent]() {
        def handle(event: DragEvent) {
            if (valid(event)) {
                val dragboard = event.getDragboard
                val myString = dragboard.getContent(DnDTarget.DndString)
                val myObject = dragboard.getContent(DnDTarget.DndObject)
                myString.asInstanceOf[String]   // no problem
                myObject.asInstanceOf[MyObject] // throws exception
                event.acceptTransferModes(TransferMode.COPY)
            }
            event.consume()
        }
    })
}

Ok, so, calling myString.asInstanceOf[String] works fine, I got my String back. However, myObject.asInstanceOf[MyObject] throws a ClassCastException saying:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.nio.HeapByteBuffer cannot be cast to com.example.MyObject

Note: I am not using ScalaFX here, vanilla JavaFX in Scala

like image 978
Sean Connolly Avatar asked Oct 22 '22 09:10

Sean Connolly


1 Answers

I had a similar problem, while trying to implement drag-and-drop in JavaFX 8. When implementing a custom type, I got the same exception during deserialization. And I eventually figured out what the problem was in my case.

It was a bug on my part, where my "MyObject" class actually wasn't deserializable (In my case MyObject extended a supertype that didn't implement Serializable and didn't have a default no-argument constructor, which it must have in such a case). Anyway, after testing that "MyObject" can be serialized / deserialized in a unit test, my JavaFX drag-and-drop started working. As far as I can tell, the problem is this:

JavaFX error handling seems to hide any exceptions during deserialization, and returns the serialized bytebuffer instead of your deserialized object.

It is debatable wether or not this is good error handling.

In your case, you can test this by trying to deserialize the bytebuffer you get from JavaFX yourself using the following java-code:

public static MyObject deserialize(ByteBuffer buffer) {
    try {
        ByteArrayInputStream is = new ByteArrayInputStream(buffer.array());
        ObjectInputStream ois = new ObjectInputStream(is);
        MyObject obj = (MyObject) ois.readObject();
        return obj;
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

If this method throws an exception, you have your bug. Fix it, and drag and drop should start working again.

like image 80
Yngvefaen Avatar answered Oct 27 '22 16:10

Yngvefaen