Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to copy/paste table cells in a TableView

Problem

One of the very basic needs when you deal with tables is to copy/paste data of table cells. The JavaFX TableView doesn't support it out of the box.

Question

How do you access the table cells instead of the data objects in order to paste clipboard data into the selected cells?

Code

Here's what I got so far:

TableUtils.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;

public class TableUtils {

    /**
     * Install the keyboard handler:
     *   + CTRL + C = copy to clipboard
     *   + CTRL + V = paste to clipboard
     * @param table
     */
    public static void installCopyPasteHandler(TableView<?> table) {

        // install copy/paste keyboard handler
        table.setOnKeyPressed(new TableKeyEventHandler());

    }

    /**
     * Copy/Paste keyboard event handler.
     * The handler uses the keyEvent's source for the clipboard data. The source must be of type TableView.
     */
    public static class TableKeyEventHandler implements EventHandler<KeyEvent> {

        KeyCodeCombination copyKeyCodeCompination = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
        KeyCodeCombination pasteKeyCodeCompination = new KeyCodeCombination(KeyCode.V, KeyCombination.CONTROL_ANY);

        public void handle(final KeyEvent keyEvent) {

            if (copyKeyCodeCompination.match(keyEvent)) {

                if( keyEvent.getSource() instanceof TableView) {

                    // copy to clipboard
                    copySelectionToClipboard( (TableView<?>) keyEvent.getSource());

                    // event is handled, consume it
                    keyEvent.consume();

                }

            } 
            else if (pasteKeyCodeCompination.match(keyEvent)) {

                if( keyEvent.getSource() instanceof TableView) {

                    // copy to clipboard
                    pasteClipboard( (TableView<?>) keyEvent.getSource());

                    // event is handled, consume it
                    keyEvent.consume();

                }

            } 

        }

    }

    /**
     * Get table selection and copy it to the clipboard.
     * @param table
     */
    public static void copySelectionToClipboard(TableView<?> table) {

        StringBuilder clipboardString = new StringBuilder();

        ObservableList<TablePosition> positionList = table.getSelectionModel().getSelectedCells();

        int prevRow = -1;

        for (TablePosition position : positionList) {

            int row = position.getRow();
            int col = position.getColumn();

            Object cell = (Object) table.getColumns().get(col).getCellData(row);

            // null-check: provide empty string for nulls
            if (cell == null) {
                cell = "";
            }

            // determine whether we advance in a row (tab) or a column
            // (newline).
            if (prevRow == row) {

                clipboardString.append('\t');

            } else if (prevRow != -1) {

                clipboardString.append('\n');

            }

            // create string from cell
            String text = cell.toString();

            // add new item to clipboard
            clipboardString.append(text);

            // remember previous
            prevRow = row;
        }

        // create clipboard content
        final ClipboardContent clipboardContent = new ClipboardContent();
        clipboardContent.putString(clipboardString.toString());

        // set clipboard content
        Clipboard.getSystemClipboard().setContent(clipboardContent);

        System.out.println( "Clipboard string: " + clipboardContent);

    }

    public static void pasteClipboard(TableView<?> table) {

        TablePosition focusedCellPosition = table.getFocusModel().getFocusedCell();

        System.out.println("Pasting into cells starting at " + focusedCellPosition);

        String pasteString = Clipboard.getSystemClipboard().getString();

        System.out.println(pasteString);

        Pattern pattern = Pattern.compile("([^\t]*)\t([^\t]*)\t([^\n]*)(\n)?");
        Matcher matcher = pattern.matcher(pasteString);
        while (matcher.find()) {

            System.out.println(matcher.group(1) + "," + matcher.group(2) + "," + matcher.group(3));

            // TODO: what now? how to paste the data?  

        }

    }


}

TableCopyCellsDemo.java

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class TableCopyCellsDemo extends Application {

    private final ObservableList<Person> data = FXCollections.observableArrayList(new Person("Jacob", "Smith", 18), new Person("Isabella", "Johnson", 19), new Person("Ethan", "Williams", 20), new Person("Michael", "Brown", 21));

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {

        stage.setWidth(500);
        stage.setHeight(550);

        // create table columns
        TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));

        TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));

        TableColumn<Person, Integer> ageCol = new TableColumn<Person, Integer>("Age");
        ageCol.setMinWidth(60);
        ageCol.setCellValueFactory(new PropertyValueFactory<Person, Integer>("age"));


        TableView<Person> table = new TableView<>();
        table.setPlaceholder(new Text("No content in table"));
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, ageCol);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 10, 10, 10));

        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(table);

        vbox.getChildren().addAll(borderPane);

        vbox.getChildren().add( new Label( "Select cells and press CTRL+C. Paste the data into Excel or Notepad"));

        Scene scene = new Scene(vbox);

        stage.setScene(scene);
        stage.show();

        // enable multi-selection
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        // enable copy/paste
        TableUtils.installCopyPasteHandler(table);
    }


    public static class Person {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final IntegerProperty age;

        private Person(String fName, String lName, Integer age) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.age = new SimpleIntegerProperty(age);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final java.lang.String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final java.lang.String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final java.lang.String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final java.lang.String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final IntegerProperty ageProperty() {
            return this.age;
        }

        public final int getAge() {
            return this.ageProperty().get();
        }

        public final void setAge(final int age) {
            this.ageProperty().set(age);
        }
    }
}

The problem is the TODO in the pasteClipboard method. You can copy data of the selected cells to the clipboard via CTRL+C. With CTRL+V you get the data from the clipboard and analyze them. But I haven't found a means yet to write the data directly into the table.

Thank you very much!

like image 322
Roland Avatar asked Apr 19 '15 07:04

Roland


1 Answers

After some digging and reading about solutions with reflection I found out myself that it's easier than I thought. And most important: Without reflection.

I thought I'd share the code, hope it'll be of help to others.

Here's the basic method to modify a cell. This is just what I found out. In case someone knows a better method, then please share it.

        // get cell
        TableColumn tableColumn = table.getColumns().get(colIndex);
        ObservableValue observableValue = tableColumn.getCellObservableValue(rowIndex);

        if( observableValue instanceof StringProperty) { 

            ((StringProperty) observableValue).set(clipboardCellContent);

        }

So the paste method basically looks like this:

public static void pasteClipboard( TableView<?> table) {

    // abort if there's not cell selected to start with
    if( table.getSelectionModel().getSelectedCells().size() == 0) {
        return;
    }

    // get the cell position to start with
    TablePosition pasteCellPosition = table.getSelectionModel().getSelectedCells().get(0);

    System.out.println("Pasting into cell " + pasteCellPosition);

    String pasteString = Clipboard.getSystemClipboard().getString();

    System.out.println(pasteString);

    int rowClipboard = -1;

    StringTokenizer rowTokenizer = new StringTokenizer( pasteString, "\n");
    while( rowTokenizer.hasMoreTokens()) {

        rowClipboard++;

        String rowString = rowTokenizer.nextToken();

        StringTokenizer columnTokenizer = new StringTokenizer( rowString, "\t");

        int colClipboard = -1;

        while( columnTokenizer.hasMoreTokens()) {

            colClipboard++;

            // calculate the position in the table cell
            int rowTable = pasteCellPosition.getRow() + rowClipboard;
            int colTable = pasteCellPosition.getColumn() + colClipboard;

            // skip if we reached the end of the table
            if( rowTable >= table.getItems().size()) {
                continue;
            }
            if( colTable >= table.getColumns().size()) {
                continue;
            }

            String clipboardCellContent = columnTokenizer.nextToken();

            // System.out.println( rowClipboard + "/" + colClipboard + ": " + cell);

            // get cell
            TableColumn tableColumn = table.getColumns().get(colTable);
            ObservableValue observableValue = tableColumn.getCellObservableValue(rowTable);

            System.out.println( rowTable + "/" + colTable + ": " +observableValue);

            // TODO: handle double, etc
            if( observableValue instanceof StringProperty) { 

                ((StringProperty) observableValue).set(clipboardCellContent);

            }
            else if( observableValue instanceof IntegerProperty) { 

                int value;
                try {
                    value = NumberFormat.getInstance().parse(clipboardCellContent).intValue();
                    ((IntegerProperty) observableValue).set(value);
                } catch (ParseException e) {
                    e.printStackTrace();
                }

            }
        }

    }

}

You can get the full code on this gist.

like image 102
Roland Avatar answered Sep 30 '22 08:09

Roland