Whether you’re importing files, copying links, or pasting content, many users expect to be able to do this using a drag-and-drop gesture on top of the usual Ctrl-O and click-me load buttons.

Luckily, JavaFX has comprehensive support for dragging and dropping both into and out of your applications.

That’s where this article comes in. This article has three apps that demonstrate the drag and drop functionality bundled with JavaFX. Each links to the source code at GitHub, so you can get off the ground quickly.

Drag in, drag out, or drag between windows
Save, Share, Grow

Part of my workflow for creating new apps is having a library of reusable code I can (no pun intended) drag and drop into a new project. I’m hoping by creating these articles that you can benefit from those snippets too.

So if it’s useful, save it, come back to it later, and if you like the content, feel free to share. It really helps the website grow 💕.

What’s in this article

  1. App 1: Drag files into a JavaFX app
  2. App 2: Drag files out of a JavaFX app
  3. App 3: Dragging and dropping objects around inside a JavaFX app
What sort of functionality you’ll achieve

By the end of the article, you’ll have reusable code you can deploy immediately. More importantly, you’ll know how the JavaFX drag and drop functionality works, so you can drag files into your apps, export data, and even transfer data between controllers.

How you’ll do it.

Each of the apps is going to be structured in a similar way with a view defined in FXML and the behaviour in Java inside a controller.

Simple JavaFX apps need three components - a Controller, a CSS stylesheet and an FXML file to construct the view

Each app is made up of a Pane, with either a draggable shape or a display area, and a title describing how the user should interact with the application.

Then in each case, we’ll go through how to implement the dragging behaviours above to get the desired functionality.

App 1: Dragging Files into JavaFX

Alright, this App’s going to be simple, but it’s going to let us drag text files into a JavaFX app and display them in a TextArea. We could do it with files, or text, or any of the other supported data types. But text will do.

Dragging things in, then…
…displaying the contents

It’s actually pretty easy to do. Objects like files and links can be dragged into a JavaFX application in two steps:

  1. Activate a Node to allow data to be transferred by setting an action using node.setOnDragOver().
  2. Setting an action using node.setOnDragDropped() that detects the files being dragged and displays them inside the app.

Both of these involve using information inside a DragEvent object, which I’ll show you how to use in each step below.

Before we start, here’s the basic shape of the application – the FXML. The only active part will be the TextArea, which has an fx:id attribute so we can refer to it from the controller.

<VBox xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.edencoding.controllers.DragInExample">
    <HBox alignment="CENTER" prefHeight="50.0" prefWidth="200.0" style="-fx-background-color: white;">
        <Label text="Drag Something from your Desktop" textAlignment="CENTER" wrapText="true">
            <font>
                <Font name="System Bold" size="12.0" />
            </font>
        </Label>
    </HBox>
    <StackPane BorderPane.alignment="CENTER">
        <TextArea fx:id="textArea" prefHeight="200.0" prefWidth="200.0" />
    </StackPane>
</VBox>

1. Activating a Node to allow data to be transferred

Allowing data to be dragged into a JavaFX application doesn’t just let you decide how you want it to be transferred. It also lets you give the user feedback about how you want that data to be transferred. Like when you hold the Ctrl key down and a “+” appears next to the mouse cursor.

It’s the same in JavaFX. The available transfer modes are:

TransferMode.ANY;
TransferMode.COPY;
TransferMode.MOVE;
TransferMode.COPY_OR_MOVE;
TransferMode.LINK;
TransferMode.NONE; //do not accept data

TransferMode.NONE is great for turning off drag and drop when you’re not looking for user input.

Here, we just want to use TransferMode.MOVE. To activate a node and let it accept drag events, we need to invoke setOnDragOver(), which will be fired when a drag event is detected inside the bounds of the node.

We’ll do this in the initialize() method of the controller.

public void initialize() {
    node.setOnDragOver(event -> {
        event.acceptTransferModes(TransferMode.MOVE);
    });
}

I’m not joking when I say that is literally it. The node will now see and accept move-based drag events. Now we want to be able to do something with the data it sees.

2. Accepting data into the JavaFX application

Now we’ve got a TextArea that’s listening to drag events, we need to tell it what to do when something gets dropped. To do that, we’ll invoke node.setOnDragDropped().

When a user drops something onto the node, we just need to check whether the dropped contents has any files. If it does, we’ll load a file. For the sake of this example, if they load multiple files, we’ll just load the first file in the list.

node.setOnDragDropped(event -> {
    Dragboard db = event.getDragboard();

    if(event.getDragboard().hasFiles()){
        //load the file
    }
});

I always prefer to load files asynchronously. If it’s a big file, it could take some time. Ideally we’d also give the user some feedback like a wait icon, but for now I’ll just take some code from when I created a text editor for a Task that will load in our file.

Now we just need to execute it from our drag listener, which we’ll also put in the controller’s initialize() method.

node.setOnDragDropped(event -> {
    Dragboard db = event.getDragboard();

    if(event.getDragboard().hasFiles()){
        File fileToLoad = db.getFiles().get(0); //get files from dragboard
        Task loadFiles = fileLoaderTask();      //create asynch task to load files
        loadFiles.run();                        //load file in
    }
});

It’s literally that easy. Just create a Main class that loads your FXML and you’re good to go!

All the code for this app is on my GitHub here. Feel free to steal with pride, and share it if you’ve found it useful.

.

App 2: Dragging files from a JavaFX app

Dragging files out of a JavaFX application is almost as simple, except we do everything in reverse.

Data can be dragged out of a JavaFX application in two steps:

  1. Activate a node to start drag event using node.startDragAndDrop(). This must be initiated inside the drag listener node.setOnDragDetected().
  2. Use the listeners for starting the drag action to push the data into the dragboard, ready for transfer. If it’s a file, save it to a temporary location like you would when dragging out of the window.

Here I’m going to create an app that lets you drag the contents of a text area onto the desktop to save as a file. You could do it with a bunch of other formats, even images, but this works for the example.

1 & 2. Activate a node to start a drag event and push data to the dragboard

The difference between the “dragging files in” app and this one is that once the mouse drags across a node in this app, we actually want to start the dragboard up ourselves. We can do that on any node by invoking node.startDragAndDrop(). We must do this inside the scope of node.setOnDragDetected().

Then, once we’ve started the drag and drop action, we’re going to save a temporary file to disk, and then instruct the dragboard to move that file when the drag gets dropped.

I’m going to drop all of the code in the initialize() method of the controller:

public void initialize() {
    textArea.setOnDragDetected(event -> {
        //1. Start up the drag and drop
        Dragboard db = textArea.startDragAndDrop(TransferMode.MOVE);

        //2. Save the file to disk
        File tempFile = new File("Output.txt");
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
            writer.write(textArea.getText());
        } catch (IOException e) {
            textArea.setText("Unable to transfer data to dragboard");
        }

        //3. Instruct the dragboard to move the file when the drag's dropped
        ClipboardContent content = new ClipboardContent();
        content.putFiles(Collections.singletonList(tempFile));
        db.setContent(content);
    });
}

And that’s it! The Output.txt file you created will be moved by the system, so you don’t need to worry about deleting it.

If the drag gets cancelled, you may want to use the node’s setOnDragDone() method to check whether your temp file is still there and remove it.

All the code for this app is on my GitHub here. Feel free to steal with pride, and share it if you’ve found it useful.

App 3: Dragging objects and data between windows

I spot apps all the time where you need to be able to drag files or data between windows. Every time you drag an attachment from an email into a new mail, that’s exactly what you’re doing.

Any custom data can be transferred between controllers using the drag board in four steps:

  1. Activate a node in the source window to start drag event using sourceNode.startDragAndDrop(). This is must be initiated inside the drag listener sourceNode.setOnDragDetected().
  2. Use the listeners for starting the drag action to push the custom data into the dragboard, ready for transfer. If it’s a file, save it to a temporary location like you would when dragging out of the window.
  3. Activate a node in the target window to allow data to be transferred by setting an action using targetNode.setOnDragOver().
  4. Pull the custom data from the dragboard and display it in the target window

1 & 2. Activate a source node and push data to the drag board

Activating a source node to initiate drag events is just as easy as it was when we were dragging objects out of the window above. Again, we’ll start the drag and drop event, and as we’re dragging the text out of the text area, we can push the text area’s contents into dragboard.

We’ll do this in the initialize() method of the controller.

public void initialize() {
    textArea.setOnDragDetected(event -> {
        //1. Start the drag and drop
        Dragboard db = textArea.startDragAndDrop(TransferMode.MOVE);

        //2. Push the text area's content to the dragboard
        ClipboardContent content = new ClipboardContent();
        content.putString(textArea.getText());
        db.setContent(content);

        //3. Clear the text area 
        textArea.setText("");
    });
}

Because I’ve used TransferMode.MOVE I thought it was sensible to clear the text area. But you could equally use TransferMode.COPY and keep it there too.

3. Activate a target node to allow drag events

Just like above, activating a target node is just as simple as when we were dragging files into the app before.

textArea.setOnDragOver(event -> {
    event.acceptTransferModes(TransferMode.MOVE);
});

Put that in the controller’s initialize() method, right next to the instructions for textArea.setOnDragDetected().

Now, all that’s left is that once something (our text) gets dropped, we want to accept the move and pull the data.

4. Pull the custom data from the dragboard and display it in the target window

Opening a node to drag events means it’s open to all drag events. So before we pull the data in from the dragboard, we need to check the right sort of data is there. We’ll do this using the dragboard’s hasString() method.

Once we’re confident the dragboard contains data we can use, it’s really easy to pull the data in from the dragboard using it’s getString() method.

textArea.setOnDragDropped(event -> {
    Dragboard db = event.getDragboard();

    if (db.hasString()) {
        textArea.setText(db.getString());
    }
});

Again, this should go in the controller’s initialize() method.

And that’s it!

Extra functionality

JavaFX’s drag and drop functionality is actually so much more comprehensive than just text and files. In fact, you can push any objects you want to the dragboard as long as they’re serializable.

A trick I sometimes use is to push a serializable lookup or index to the dragboard, and use a helper class to move non-serializable objects like nodes between controllers.

You can see an example of that in the GitHub here.

Conclusions

JavaFX has phenomenal functionality for supporting drag and drop applications. In fact, you can transfer both standard and completely custom data objects into, out of, or between windows.

JavaFX’s dragboard support works through its scene graph, so every example involves activating one or more nodes to drag events using the node’s setOnDragStarted() and setOnDragOver() methods.

JavaFX supports transferring files, text, URLs and serializable custom objects, meaning you can transfer significant chunks of data by dragging. When that’s not enough, you can always store non-serializable data objects in a helper class, and pass a serializable reference (like an integer, to look up the object later) into the dragboard.