Search code examples
javafxdrag-and-droptableview

JavaFX - drag a row from a tableview into a textarea


Consider a tableview inside a JavaFX application. The tableview contains data about people:

id    firstname    lastname

1     John         Doe

Each of the rows of the tableview can be used to instantiate a Person object:

new Person(Integer id, String firstname, String lastname).

Here is what I want to do:

I want to select one of the Persons inside the tableview and drag it with my mouse into a textarea. Inside the textarea I want to print the String:

1 John Doe.

I know that I have to implement three functions to achieve this: onDragDetected(), onDragOver() and onDragDropped().

I was thinking to start with:

public void onDragDetected(DragEvent dragevent) {
    Person person = tvPersonData.getSelectionModel().getSelectedItem();
}

This works. But I have to somehow get my Person into the DragBoard object (dragevent.getDragboard()). I think that I do. And then maybe my other functions can use whatever is inside my dragboard.

Can anyone help me out here?


Solution

  • In order to use a custom object in a DragAndDrop gesture, you need to define a custom DataFormat for the object:

    DataFormat personDataFormat = new DataFormat("com.package.Person");
    

    You obviously need to replace the com.package.Person will the full path to the class you are creating this for.

    Note also that in order for a class to be used as a DataFormat, that class must be Serializable. This is accomplished by implementing the Serializable interface in your Person object:

    class Person implements Serializable {}
    

    Another thing to remember is that JavaFX properties are not Serializable so if you use StringProperty in your Person object, this will not work; you'll need to create a wrapper class that holds the data you need.


    Once you have your DataFormat and Person implementing Serializable, you can copy your Person object to the Clipboard just like any other content; you just need to specify the DataFormat for it:

    ClipboardContent content = new ClipboardContent();
    content.put(personDataFormat, tableView.getSelectionModel().getSelectedItem());
    

    Dropping onto the TextArea is going to be similar. You'll define the DataFormat and then add the code to process as desired:

    Person droppedPerson = (Person) db.getContent(personDataFormat);
    textArea.appendText(droppedPerson.getName() + "\n");
    

    Full example application below:

    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.scene.input.*;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    import java.io.Serializable;
    import java.util.Arrays;
    
    public class DragObject extends Application {
    
        private final TableView<Person> tableView = new TableView<>();
        private final TextArea textArea = new TextArea();
    
        public static void main(String[] args) {
    
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            // **********************************************************************************************
            // Create a basic layout
            // **********************************************************************************************
            VBox root = new VBox(5);
            root.setAlignment(Pos.TOP_CENTER);
            root.setPadding(new Insets(10));
    
            // **********************************************************************************************
            // Configure a regular TableView
            // **********************************************************************************************
            configureTableView();
    
            tableView.setItems(sampleData());
            tableView.setPrefHeight(200);
            root.getChildren().add(tableView);
    
            root.getChildren().add(textArea);
    
            // **********************************************************************************************
            // This method will configure our drag-and-drop functionality
            // **********************************************************************************************
            initDragAndDrop();
    
            // **********************************************************************************************
            // Set the Scene for the stage
            // **********************************************************************************************
            primaryStage.setScene(new Scene(root));
    
            // **********************************************************************************************
            // Configure the Stage
            // **********************************************************************************************
            primaryStage.setTitle("Test Application");
            primaryStage.show();
        }
    
        private void configureTableView() {
    
            // **********************************************************************************************
            // Just a standard sample TableView
            // **********************************************************************************************
    
            TableColumn<Person, String> colName = new TableColumn<>("Name");
            TableColumn<Person, String> colEmail = new TableColumn<>("Email");
    
            colName.setCellValueFactory(new PropertyValueFactory<>("name"));
            colEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
    
            tableView.getColumns().addAll(colName, colEmail);
    
        }
    
        private void initDragAndDrop() {
    
            DataFormat personDataFormat = new DataFormat("Person");
    
            // **********************************************************************************************
            // Add drag and drop to the TableView
            // **********************************************************************************************
            tableView.setOnDragDetected(event -> {
                // **********************************************************************************************
                // Drag was detected, allow any type of transfer
                // **********************************************************************************************
                Dragboard db = tableView.startDragAndDrop(TransferMode.ANY);
    
                // **********************************************************************************************
                // Add the selected Person to the clipboard, using our custom DataFormat
                // **********************************************************************************************
                ClipboardContent content = new ClipboardContent();
                content.put(personDataFormat, tableView.getSelectionModel().getSelectedItem());
                db.setContent(content);
            });
    
            // **********************************************************************************************
            // Add drag and drop to the TextArea
            // **********************************************************************************************
            textArea.setOnDragOver(event -> {
    
                // **********************************************************************************************
                // Person was dragged over the TextArea; accept it only if dragged from a node other than itself.
                // In this case, this condition isn't necessary, but still good practice
                // **********************************************************************************************
                if (event.getGestureSource() != textArea
                    && event.getDragboard().hasContent(personDataFormat)) {
    
                    // **********************************************************************************************
                    // For this example, we'll allow ANY transfer type again
                    // **********************************************************************************************
                    event.acceptTransferModes(TransferMode.ANY);
    
                }
    
                // **********************************************************************************************
                // Consume the event. We'll handle placing content into the TextArea next
                // **********************************************************************************************
                event.consume();
            });
    
            // **********************************************************************************************
            // Add handler to the TextArea to accept a Person object that's been dropped and add the person's
            // name to the TextArea
            // **********************************************************************************************
            textArea.setOnDragDropped(event -> {
    
                // **********************************************************************************************
                // First, ensure the object being dropped was a Person object
                // **********************************************************************************************
                Dragboard db = event.getDragboard();
                boolean success = false;
                if (db.hasContent(personDataFormat)) {
    
                    // **********************************************************************************************
                    // Get a reference to the Person being dropped. Note that you need to 
                    // cast the object to a Person so Java knows what object type you're 
                    // working with.
                    // **********************************************************************************************
                    Person droppedPerson = (Person) db.getContent(personDataFormat);
                    textArea.appendText(droppedPerson.getName() + "\n");
    
                    success = true;
                }
    
                // **********************************************************************************************
                // Complete the event
                // **********************************************************************************************
                event.setDropCompleted(success);
    
                event.consume();
            });
    
        }
    
        private ObservableList<Person> sampleData() {
            // **********************************************************************************************
            // Just some sample Persons for our TableView
            // **********************************************************************************************
            return FXCollections.observableList(Arrays.asList(
                    new Person("John Williams", "jwilliams@et.com"),
                    new Person("Howard Shore", "hshore@shire.com"),
                    new Person("Danny Elfman", "delfman@scissorpalace.org")));
    
        }
    }
    

    RESULT:

    screenshot