Issue
I would like to drag and drop an item from a treeview into a textArea.
The goal is to write in the textArea the item dropped.
So if i choose to drop the item b, I would like to have b written in the textArea.
I have two problems.
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Only serializable objects or ByteBuffer can be used as data with data format []
2°) My setOn events don't run at all. At any time, an event is detected.
package application;
import java.io.Serializable;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class testDragAndDropTreeView extends Application implements Serializable {
private final TreeView<String> tree = new TreeView<String>();
private final TextArea text = new TextArea();
private static final DataFormat DATAFORMAT = new DataFormat();
@Override
public void start(Stage Stage) {
HBox hbox = new HBox();
hbox.setPadding(new Insets(20));
TreeItem<String> a = new TreeItem<String>("a");
TreeItem<String> b = new TreeItem<String>("b");
TreeItem<String> c = new TreeItem<String>("c");
TreeItem<String> root = new TreeItem<String>("Root");
root.getChildren().addAll(a, b, c);
root.setExpanded(true);
tree.setRoot(root);
tree.setOnDragDetected(event -> {
Dragboard db = tree.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.put(DATAFORMAT, tree.getSelectionModel().getSelectedItem());
db.setContent(content);
});
text.setOnDragOver(event -> {
if (event.getGestureSource() != text && event.getDragboard().hasContent(DATAFORMAT)) {
event.acceptTransferModes(TransferMode.ANY);
}
event.consume();
});
text.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(DATAFORMAT)) {
Person droppedPerson = (Person) db.getContent(DATAFORMAT);
text.appendText(droppedPerson.getName() + "\n");
success = true;
}
event.setDropCompleted(success);
event.consume();
});
tree.setCellFactory(param -> {
return new TreeCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setStyle(null);
setGraphic(null);
setText(null);
return;
}
setText(item);
}
};
});
hbox.getChildren().addAll(tree, text);
Stage.setScene(new Scene(hbox));
Stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Solution
A few of observations:
- You were trying to put TreeItems in the clipboard content, but they aren't serializable and are not of the expected type, so they don't fit. You actually wanted to put the value of the tree items in the clipboard (I think).
- You can just use a map to transfer data (clipboard content is also a map, so I guess you could still use that if you wanted).
- You just want strings of data, so you should use DataFormat.PLAIN_TEXT and put a string in the transfer object.
- You should cater for what to do if nothing is selected (selected item is null).
- Your code was referencing a non-existent class
Person
, I just changed it to String. - The action of initiating a drag from a tree item selects the tree item before the drag occurs (this is the default operation of the tree with the mouse events you are consuming, not additional code).
- The action of initiating a drag from a blank cell below the visible tree items, will drag and drop the selected item (if there is one or nothing at all), which is a bit strange but sounds like what you want to occur from comments.
- An alternative would be to define drag handlers on the tree cells themselves rather than the entire tree, but that differs a bit from the design you seem to have.
- Your code wasn't consistently following camel case naming conventions for Java, so I fixed some names. It is especially a bad idea to name a
Stage
variableStage
exactly the same case as the class name, as that is really confusing. - The root is shown and draggable. If you don't want the root shown, you can call
tree.setShowRoot(false)
.
Example
Test this modification of your code and verify if it does what you want. If it is not exactly what you want, perhaps you can use it as a new starting point and tweak it how you want.
import java.io.Serializable;
import java.util.Map;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class DragAndDropTreeViewApp extends Application implements Serializable {
private static final DataFormat DATA_FORMAT = DataFormat.PLAIN_TEXT;
private final TreeView<String> tree = new TreeView<>();
private final TextArea text = new TextArea();
@Override
public void start(Stage stage) {
HBox hbox = new HBox();
hbox.setPadding(new Insets(20));
TreeItem<String> a = new TreeItem<>("a");
TreeItem<String> b = new TreeItem<>("b");
TreeItem<String> c = new TreeItem<>("c");
TreeItem<String> root = new TreeItem<>("Root");
//noinspection unchecked
root.getChildren().addAll(a, b, c);
root.setExpanded(true);
tree.setRoot(root);
tree.setOnDragDetected(event -> {
if (tree.getSelectionModel().getSelectedItem() != null) {
Dragboard db = tree.startDragAndDrop(TransferMode.ANY);
db.setContent(
Map.of(
DATA_FORMAT,
tree.getSelectionModel().getSelectedItem().getValue()
)
);
}
});
text.setOnDragOver(event -> {
if (event.getGestureSource() != text && event.getDragboard().hasContent(DATA_FORMAT)) {
event.acceptTransferModes(TransferMode.ANY);
}
event.consume();
});
text.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(DATA_FORMAT)) {
String droppedPerson = (String) db.getContent(DATA_FORMAT);
text.appendText(droppedPerson + "\n");
success = true;
}
event.setDropCompleted(success);
event.consume();
});
tree.setCellFactory(param -> new TreeCell<>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setStyle(null);
setGraphic(null);
setText(null);
return;
}
setText(item);
}
});
hbox.getChildren().addAll(tree, text);
stage.setScene(new Scene(hbox));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Answered By - jewelsea
Answer Checked By - Candace Johnson (JavaFixing Volunteer)