Dragging and dropping list view items between different javafx windows - javafx-8

I've been wondering how you would be able to drag and drop list view items between 2 java fx windows.
The code that I used was
tilePane.setOnDragDropped((event) -> {
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasString()) {
TilePane pane = (TilePane) event.getGestureTarget();
if (pane.getChildren().size() >= 10) {
//error
} else {
ListView<Item> list = (ListView<Item>) event
.getGestureSource();
addShopItem(pane, list.getSelectionModel()
.getSelectedItem());
success = true;
}
}
event.setDropCompleted(success);
event.consume();
});
Both the list view and tile pane used to be in one window but I've decided to make seperate them into different javafx windows so it would allow for more flexibility. One window has the list view and the other has the tilepane.
I would like to drag the list view item to the tilepane(other window) but this code no longer works because getGestureTarget() is null for different applications.
Thanks

It does look like the gesture source and target both get set to null when the drag leaves the JavaFX application (e.g. moving it between two windows).
For the gesture source, you may need to manage that yourself by creating a property and setting its value in the onDragDetected handler.
The gesture target is surely just the tile pane to which you attached the onDragDropped listener. So I don't see that you need to access that from the event; though you could use the same technique.
Example:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class DnDListViews extends Application {
private int counter = 0 ;
private final ObjectProperty<ListCell<String>> dragSource = new SimpleObjectProperty<>();
#Override
public void start(Stage primaryStage) {
populateStage(primaryStage);
primaryStage.show();
Stage anotherStage = new Stage();
populateStage(anotherStage);
anotherStage.setX(primaryStage.getX() + 300);
anotherStage.show();
}
private void populateStage(Stage stage) {
ListView<String> listView = new ListView<>();
for (int i=0; i<5; i++ ) {
listView.getItems().add("Item "+(++counter));
}
listView.setCellFactory(lv -> {
ListCell<String> cell = new ListCell<String>(){
#Override
public void updateItem(String item , boolean empty) {
super.updateItem(item, empty);
setText(item);
}
};
cell.setOnDragDetected(event -> {
if (! cell.isEmpty()) {
Dragboard db = cell.startDragAndDrop(TransferMode.MOVE);
ClipboardContent cc = new ClipboardContent();
cc.putString(cell.getItem());
db.setContent(cc);
dragSource.set(cell);
}
});
cell.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasString()) {
event.acceptTransferModes(TransferMode.MOVE);
}
});
cell.setOnDragDone(event -> listView.getItems().remove(cell.getItem()));
cell.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasString() && dragSource.get() != null) {
// in this example you could just do
// listView.getItems().add(db.getString());
// but more generally:
ListCell<String> dragSourceCell = dragSource.get();
listView.getItems().add(dragSourceCell.getItem());
event.setDropCompleted(true);
dragSource.set(null);
} else {
event.setDropCompleted(false);
}
});
return cell ;
});
BorderPane root = new BorderPane(listView);
Scene scene = new Scene(root, 250, 450);
stage.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
If the dragboard supported attaching arbitrary object references for drag and drop within the same JVM (see JIRA request, and vote if so inclined) then this would be quite a bit easier...

Related

JavaFX Custom Table Cell - Strange behavior

I have this code:
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Example extends Application
{
#Override
public void start(Stage stage) throws Exception
{
TableView<Color> table = new TableView<>();
ObservableList<Color> colors = FXCollections.observableArrayList();
table.setItems(colors);
table.setEditable(true);
TableColumn<Color, Color> column = new TableColumn<>();
column.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue()));
column.setCellFactory(param ->
{
ObservableList<Color> menuColors = FXCollections.observableArrayList();
menuColors.addAll(Color.RED, Color.GREEN, Color.BLUE);
return new ComboBoxTableCell(menuColors);
});
Button button = new Button("Add row");
button.setOnAction(event -> colors.add(Color.BLACK));
VBox box = new VBox(table, button);
table.getColumns().add(column);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
stage.setScene(new Scene(box));
stage.show();
}
public class ComboBoxTableCell extends TableCell<Color, Color>
{
private ComboBox<Color> comboBox;
public ComboBoxTableCell(ObservableList<Color> colors)
{
comboBox = createFancyComboBox(colors);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(Color item, boolean empty)
{
if (item == null || empty)
setGraphic(null);
else
setGraphic(comboBox);
}
}
private ComboBox<Color> createFancyComboBox(ObservableList<Color> colors)
{
ComboBox<Color> comboBox = new ComboBox<>(colors);
Callback<ListView<Color>, ListCell<Color>> factory = list -> new ColorSquare();
comboBox.setCellFactory(factory);
comboBox.setButtonCell(factory.call(null));
comboBox.setValue(colors.get(0));
return comboBox;
}
public static class ColorSquare extends ListCell<Color>
{
#Override
public void updateItem(Color item, boolean empty)
{
super.updateItem(item, empty);
Rectangle rect = new Rectangle(18, 18);
if (item != null)
{
rect.setFill(item);
setGraphic(rect);
}
}
}
public static void main(String[] args)
{
launch(args);
}
}
If I try to run it, click on the button, change the color to for Green and click 8 times on the button, the green square will disapper.
How do I fix this and why is it happening? The real code isn't much different from this, this is the only problem I have. Thank you.
I have noticed that at times the rerendering of a table cell doesn't work right after a certain number of attempts. I had one where I was updating a remaining amount when the user entered a value in another cell.
To solve this I would toggle visibility of the column.
column.setVisible(false);
column.setVisible(true);
I would do this immediately after the action that changed the value in the cell.
It's a hack but seems to work.

JavaFX - move window with effect

I have undecorated non-fullscreen window, which I like to move outside screen boundaries when mouse leaves it's area, but do so smoothly. I found some JavaFX functionality to do so - Timeline, but KeyValue for that Timeline doesn't supports stage.xProperty - because this property is readonlyProperty. Is there way to move my window smoothly using JavaFX functions?
You can setup proxy properties that you manipulate via KeyValues in a Timeline. A listener on the proxy can modify the actual stage location.
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import javafx.stage.*;
import javafx.util.Duration;
public class StageSwiper extends Application {
private static final int W = 350;
private static final Duration DURATION = Duration.seconds(0.5);
#Override
public void start(Stage stage) throws Exception {
Label instructions = new Label(
"Window will slide off-screen when the mouse exits it.\n" +
"Click the window to close the application."
);
instructions.setTextAlignment(TextAlignment.CENTER);
final StackPane root = new StackPane(instructions);
root.setStyle("-fx-background-color: null;");
DoubleProperty stageX = new SimpleDoubleProperty();
stageX.addListener((observable, oldValue, newValue) -> {
if (newValue != null && newValue.doubleValue() != Double.NaN) {
stage.setX(newValue.doubleValue());
}
});
final Timeline slideLeft = new Timeline(
new KeyFrame(
DURATION,
new KeyValue(
stageX,
-W,
Interpolator.EASE_BOTH
)
),
new KeyFrame(
DURATION.multiply(2)
)
);
slideLeft.setOnFinished(event -> {
slideLeft.jumpTo(Duration.ZERO);
stage.centerOnScreen();
stageX.setValue(stage.getX());
});
root.setOnMouseClicked(event -> Platform.exit());
root.setOnMouseExited(event -> slideLeft.play());
stage.setScene(new Scene(root, W, 100, Color.BURLYWOOD));
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
stage.centerOnScreen();
stageX.set(stage.getX());
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX 8 TextArea loose focus on tab

Is it possible to change the default behaviour of a JavaFX TextArea, so that pressing Tab passes the focus to the next component?
While #ItachiUchiha solution works, as he states, it depends on the layout (box in his sample).
Based on this question, you can modify the default behavior of a TextArea, regardless of the layout.
But you will need to use for this private API, which may change at any time without notice.
In this sample Tab and Shitf+Tab will have the desired behavior, while Ctrl+Tab will insert "\t" on the text area.
#Override
public void start(Stage primaryStage) {
TextArea area = new TextArea();
area.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
if (event.getCode() == KeyCode.TAB) {
TextAreaSkin skin = (TextAreaSkin) area.getSkin();
if (skin.getBehavior() instanceof TextAreaBehavior) {
TextAreaBehavior behavior = (TextAreaBehavior) skin.getBehavior();
if (event.isControlDown()) {
behavior.callAction("InsertTab");
} else if (event.isShiftDown()) {
behavior.callAction("TraversePrevious");
} else {
behavior.callAction("TraverseNext");
}
event.consume();
}
}
});
VBox root = new VBox(20, new Button("Button 1"), area, new Button("Button 2"));
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
Well, you definitely can do this, but it depends on the Layout to which the TextArea is added to. I have created a simple example where a TextArea and a TextField are both added to a VBox. There is a keyEventHandler which monitors the keyPress event on the TextArea and sends the focus to the next child(if any)
import java.util.Iterator;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextAreaTabFocus extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox box = new VBox();
TextArea textArea = new TextArea();
TextField textField = new TextField();
box.getChildren().addAll(textArea, textField);
final EventHandler<KeyEvent> keyEventHandler =
keyEvent -> {
if (keyEvent.getCode() == KeyCode.TAB) {
Iterator<Node> itr = box.getChildren().iterator();
while(itr.hasNext()) {
if(itr.next() == keyEvent.getSource()) {
if(itr.hasNext()){
itr.next().requestFocus();
}
//If TextArea is the last child
else {
box.getChildren().get(0).requestFocus();
}
break;
}
}
keyEvent.consume();
}
};
textArea.setOnKeyPressed(keyEventHandler);
Scene scene = new Scene(box, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to delay the Drag-Start to combine move and Drag-And-Drop dynamically

I want to provide an application that:
allows to move images around (here a rectangle)
if that object is moved out of the working area, start Drag-and-Drop for transfere to other applications
So the javafx DragDetected() would come too soon during the object move on the canvas area, I suppress the onDragDetected() handling and in the onMouseDragged() handler I tried to convert the MouseDrag event into a Drag event using
event.setDragDetect(true);
But the onDragDetected() comes never again..... what can I do?
The full sample application is:
package fx.samples;
import java.io.File;
import java.util.LinkedList;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class DragRectangle extends Application {
Point2D lastXY = null;
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
Rectangle area = new Rectangle(0, 0, 500 , 500);
Rectangle rect = new Rectangle(0, 0, 30, 30);
rect.setFill(Color.RED);
mainPane.getChildren().add(rect);
rect.setOnMouseDragged(event -> {
System.out.println("Move");
Node on = (Node)event.getTarget();
if (lastXY == null) {
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
}
double dx = event.getSceneX() - lastXY.getX();
double dy = event.getSceneY() - lastXY.getY();
on.setTranslateX(on.getTranslateX()+dx);
on.setTranslateY(on.getTranslateY()+dy);
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) {
System.out.println("->Drag");
event.setDragDetect(true);
} else {
event.consume();
}
});
rect.setOnDragDetected(event -> {
System.out.println("Drag:"+event);
if (area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) { event.consume(); return; }
Node on = (Node)event.getTarget();
Dragboard db = on.startDragAndDrop(TransferMode.COPY);
db.setContent(makeClipboardContent(event, on, null));
event.consume();
});
rect.setOnMouseReleased(d -> lastXY = null);
}
public static ClipboardContent makeClipboardContent(MouseEvent event, Node child, String text) {
ClipboardContent cb = new ClipboardContent();
if (text != null) {
cb.put(DataFormat.PLAIN_TEXT, text);
}
if (!event.isShiftDown()) {
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
Bounds b = child.getBoundsInParent();
double f = 10;
params.setViewport(new Rectangle2D(b.getMinX()-f, b.getMinY()-f, b.getWidth()+f+f, b.getHeight()+f+f));
WritableImage image = child.snapshot(params, null);
cb.put(DataFormat.IMAGE, image);
try {
File tmpFile = File.createTempFile("snapshot", ".png");
LinkedList<File> list = new LinkedList<File>();
ImageIO.write(SwingFXUtils.fromFXImage(image, null),
"png", tmpFile);
list.add(tmpFile);
cb.put(DataFormat.FILES, list);
} catch (Exception e) {
}
}
return cb;
}
public static void main(String[] args) {
launch(args);
}
}
Ok, I spend some hours reading the JavaFX sources and playing arround with EventDispatcher etc... its finally easy:
In Short:
Suppress the system drag start proposal in the onMouseDragged() handler and set that flag on your behalf:
onMouseDragged(e -> {
e.setDragDetect(false); // clear the system proposal
if (...) e.setDragDetect(true); // trigger drag on your own decision
}
Long text:
The mechanism to start a DragDetected is consequently using the MouseEvent MOUSE_DRAGGED. The system Drag detection will apply some rules to determine if the current mouse-drag will be interpreted as a drag, here the original code:
if (dragDetected != DragDetectedState.NOT_YET) {
mouseEvent.setDragDetect(false);
return;
}
if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
pressedX = mouseEvent.getSceneX();
pressedY = mouseEvent.getSceneY();
mouseEvent.setDragDetect(false);
} else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) {
double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX);
double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY);
mouseEvent.setDragDetect(deltaX > hysteresisSizeX ||
deltaY > hysteresisSizeY);
}
}
and it set
mouseEvent.setDragDetect(true)
in the normal MOUSE_DRAG event. That event is passed down and is being processed by all 'down-chain' EventDispatchers... only if this events finally arrives for processing and if the isDragDetect flag is still true, a follow up DragDetected event will be generated.
So I am able to delay the DragDetected by clearing the isDragDetect flag on its way down using an EventDispatcher:
mainPane.setEventDispatcher((event, chain) -> {
switch (event.getEventType().getName()) {
case "MOUSE_DRAGGED":
MouseEvent drag = (MouseEvent)event;
drag.setDragDetect(false);
if (!area.intersects(drag.getSceneX(), drag.getSceneY(), 1, 1)) {
System.out.println("->Drag down");
drag.setDragDetect(true);
}
break;
}
return chain.dispatchEvent(event);
});
And if this code decides that a drag condition is reached, it simply sets the flag.
drag.setDragDetect(true);
Now I am able to move precisely my objects and start the Drag if they are moved outside the application area.
And after some minutes of thinking: the EventDispatcher is not necessary, all can be done in the onMouseDragged handler...
Full code:
package fx.samples;
import java.io.File;
import java.util.LinkedList;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class DragRectangle extends Application {
Point2D lastXY = null;
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
Rectangle area = new Rectangle(0, 0, 500 , 500);
Rectangle rect = new Rectangle(0, 0, 30, 30);
rect.setFill(Color.RED);
mainPane.getChildren().add(rect);
rect.setOnMouseDragged(event -> {
System.out.println("Move");
event.setDragDetect(false);
Node on = (Node)event.getTarget();
if (lastXY == null) {
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
}
double dx = event.getSceneX() - lastXY.getX();
double dy = event.getSceneY() - lastXY.getY();
on.setTranslateX(on.getTranslateX()+dx);
on.setTranslateY(on.getTranslateY()+dy);
lastXY = new Point2D(event.getSceneX(), event.getSceneY());
if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) event.setDragDetect(true);
event.consume();
});
rect.setOnDragDetected(event -> {
System.out.println("Drag:"+event);
Node on = (Node)event.getTarget();
Dragboard db = on.startDragAndDrop(TransferMode.COPY);
db.setContent(makeClipboardContent(event, on, "red rectangle"));
event.consume();
});
rect.setOnMouseReleased(d -> lastXY = null);
}
public static ClipboardContent makeClipboardContent(MouseEvent event, Node child, String text) {
ClipboardContent cb = new ClipboardContent();
if (text != null) {
cb.put(DataFormat.PLAIN_TEXT, text);
}
if (!event.isShiftDown()) {
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
Bounds b = child.getBoundsInParent();
double f = 10;
params.setViewport(new Rectangle2D(b.getMinX()-f, b.getMinY()-f, b.getWidth()+f+f, b.getHeight()+f+f));
WritableImage image = child.snapshot(params, null);
cb.put(DataFormat.IMAGE, image);
try {
File tmpFile = File.createTempFile("snapshot", ".png");
LinkedList<File> list = new LinkedList<File>();
ImageIO.write(SwingFXUtils.fromFXImage(image, null),
"png", tmpFile);
list.add(tmpFile);
cb.put(DataFormat.FILES, list);
} catch (Exception e) {
}
}
return cb;
}
public static void main(String[] args) {
launch(args);
}
}
Based on this:
The default drag detection mechanism uses mouse movements with a pressed button in combination with hysteresis. This behavior can be augmented by the application. Each MOUSE_PRESSED and MOUSE_DRAGGED event has a dragDetect flag that determines whether a drag gesture has been detected. The default value of this flag depends on the default detection mechanism and can be modified by calling setDragDetect() inside of an event handler. When processing of one of these events ends with the dragDetect flag set to true, a DRAG_DETECTED MouseEvent is sent to the potential gesture source (the object on which a mouse button has been pressed). This event notifies about the gesture detection.
your assumption that just enabling setDragDetect(true) at some point after the mousedragged has started will fire the drag event, does not work.
The reason for that is there is some private DragDetectedState flag, that is used to set the drag state at the beginning of the gesture. And once it's already selected, it can't be changed.
So if the event it's not triggered, what you could do is manually trigger it yourself:
if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) {
System.out.println("->Drag");
event.setDragDetect(true);
Event.fireEvent(rect, new MouseEvent(MouseEvent.DRAG_DETECTED, 0, 0, 0, 0, MouseButton.PRIMARY, 0, false, false, false, false, true, false, false, true, true, true, null));
}
This would effectively trigger a drag dectected event, and rect.setOnDragDetected() will be called.
But this is what you will get:
Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException:
Cannot start drag and drop outside of DRAG_DETECTED event handler
at javafx.scene.Scene.startDragAndDrop(Scene.java:5731)
at javafx.scene.Node.startDragAndDrop(Node.java:2187)
Basically, you can't combine DragDetected with MouseDragged, since the startDragAndDrop method has to be called within a Drag_Detected event:
Confirms a potential drag and drop gesture that is recognized over this Node. Can be called only from a DRAG_DETECTED event handler.
So instead of trying to combine two different events, my suggestion is you just use drag detected event, allowing your scene or parts of it to accept the drop, and translating your node there, while at the same time, if the drag&drop leaves the scene, you could drop the file on the new target.
Something like this:
#Override
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Rectangle rect = new Rectangle(0, 0, 30, 30);
rect.setFill(Color.RED);
mainPane.getChildren().add(rect);
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
rect.setOnDragDetected(event -> {
Node on = (Node)event.getSource();
Dragboard db = on.startDragAndDrop(TransferMode.ANY);
db.setContent(makeClipboardContent(event, on, null));
event.consume();
});
mainPane.setOnDragOver(e->{
e.acceptTransferModes(TransferMode.ANY);
});
mainPane.setOnDragExited(e->{
rect.setLayoutX(e.getSceneX()-rect.getWidth()/2d);
rect.setLayoutY(e.getSceneY()-rect.getHeight()/2d);
});
}

Drag and Drop vbox element with show snapshot in javafx

I want drag an element in vbox as a parent and show node moving during drag and drop of element, how can do this with The slightest change.
Just register mouse listeners with the elements of the VBox. You want to call startFullDrag() on the node on a dragDetected event, and rotate the child nodes of the VBox on a dragReleased event. You can use the dragEntered and dragExited events if you want to give visual hints to the user about the drag.
See the API docs for more.
Simple example (code is way cleaner in JavaFX 8, btw):
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
final VBox root = new VBox(5);
final ScrollPane scroller = new ScrollPane();
scroller.setContent(root);
final Scene scene = new Scene(scroller,400,200);
for (int i=1; i<=20; i++) {
final Label label = new Label("Item "+i);
addWithDragging(root, label);
}
// in case user drops node in blank space in root:
root.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
rotateNodes(root, indexOfDraggingNode, root.getChildren().size()-1);
}
});
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
private void addWithDragging(final VBox root, final Label label) {
label.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
label.startFullDrag();
}
});
// next two handlers just an idea how to show the drop target visually:
label.setOnMouseDragEntered(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("-fx-background-color: #ffffa0;");
}
});
label.setOnMouseDragExited(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("");
}
});
label.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("");
int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
int indexOfDropTarget = root.getChildren().indexOf(label);
rotateNodes(root, indexOfDraggingNode, indexOfDropTarget);
event.consume();
}
});
root.getChildren().add(label);
}
private void rotateNodes(final VBox root, final int indexOfDraggingNode,
final int indexOfDropTarget) {
if (indexOfDraggingNode >= 0 && indexOfDropTarget >= 0) {
final Node node = root.getChildren().remove(indexOfDraggingNode);
root.getChildren().add(indexOfDropTarget, node);
}
}
public static void main(String[] args) {
launch(args);
}
}
This is an addendum to #James_D's excellent answer
This shows how to add an image preview to the draggable node as #James_D suggests in his comment:
private void addPreview(final VBox root, final Label label) {
ImageView imageView = new ImageView(label.snapshot(null, null));
imageView.setManaged(false);
imageView.setMouseTransparent(true);
root.getChildren().add(imageView);
root.setUserData(imageView);
root.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
imageView.relocate(event.getX(), event.getY());
}
});
}
private void removePreview(final VBox root) {
root.setOnMouseDragged(null);
root.getChildren().remove(root.getUserData());
root.setUserData(null);
}
Call addPreview() in label.setOnDragDetected(). Call removePreview() in label.setOnMouseDragReleased() and root.setOnMouseDragReleased().
There is a much better solution that is far cleaner now.
// Root is the node you want to drag, not the scene root.
root.setOnDragDetected(mouseEvent -> {
final ImageView preview = new ImageView(root.snapshot(null, null));
final Dragboard db = root.startDragAndDrop(TransferMode.ANY);
db.setContent( // Set your content to something here.
);
db.setDragView(preview.getImage());
mouseEvent.consume();
});