JavaFX table- how to add components?

Issues with your Implementation

You can embed JavaFX components in Swing applications (by placing the JavaFX component in a JFXPanel). But you can’t embed a Swing component in JavaFX (unless you are using JavaFX 8+).

JavaFX has it’s own button implementation anyway, so there is no reason to embed a javax.swing.JButton on a JavaFX scene, even if you were using Java8 and it would work.

But that won’t fix all of your issues. You are providing a cell value factory for your button column to supply buttons, but not supplying a custom cell rendering factory. The default table cell rendering factory renders the toString output on the respective cell value, which is why you just see the to string representation of the button in your table implementation.

You are putting buttons in your Person object. Don’t do that – they don’t belong there. Instead, dynamically generate a button in the cell rendering factory. This allows you to take advantage of a table’s virtual flow technology whereby it only creates visual nodes for what you can see on the screen, not for every element in the table’s backing data store. For example, if there are 10 rows visible on the screen and 10000 elements in the table, only 10 buttons will be created rather than 10000.

How to fix it

  1. Use JavaFX Button instead instead of javax.swing.JButton.
  2. Provide a cell rendering factory for your button.
  3. Generate a button in the table cell rather than in the Person.
  4. To set the button (or any arbitrary JavaFX node) in the table cell, use the cell’s setGraphic method.

Correct Sample Code

This code incorporates the suggested fixes and a couple of improvements.

gift registry

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

public class GestureEvents extends Application {
    private TableView<Person> table = new TableView<Person>();
    private final ObservableList<Person> data =
        FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "[email protected]","Coffee"),
            new Person("Isabella", "Johnson", "[email protected]","Fruit"),
            new Person("Ethan", "Williams", "[email protected]","Fruit"),
            new Person("Emma", "Jones", "[email protected]","Coffee"),
            new Person("Michael", "Brown", "[email protected]","Fruit")

        );

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

    @Override
    public void start(Stage stage) {
        stage.setTitle("Table View Sample");

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        final Label actionTaken = new Label();

        table.setEditable(true);

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

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

        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));

        TableColumn<Person, Person> btnCol = new TableColumn<>("Gifts");
        btnCol.setMinWidth(150);
        btnCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Person>, ObservableValue<Person>>() {
          @Override public ObservableValue<Person> call(CellDataFeatures<Person, Person> features) {
              return new ReadOnlyObjectWrapper(features.getValue());
          }
        });
        btnCol.setComparator(new Comparator<Person>() {
          @Override public int compare(Person p1, Person p2) {
            return p1.getLikes().compareTo(p2.getLikes());
          }
        });
        btnCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() {
          @Override public TableCell<Person, Person> call(TableColumn<Person, Person> btnCol) {
            return new TableCell<Person, Person>() {
              final ImageView buttonGraphic = new ImageView();
              final Button button = new Button(); {
                button.setGraphic(buttonGraphic);
                button.setMinWidth(130);
              }
              @Override public void updateItem(final Person person, boolean empty) {
                super.updateItem(person, empty);
                if (person != null) {
                  switch (person.getLikes().toLowerCase()) {
                    case "fruit": 
                      button.setText("Buy fruit");
                      buttonGraphic.setImage(fruitImage);
                      break;

                    default:
                      button.setText("Buy coffee");
                      buttonGraphic.setImage(coffeeImage);
                      break;
                  }

                  setGraphic(button);
                  button.setOnAction(new EventHandler<ActionEvent>() {
                    @Override public void handle(ActionEvent event) {
                      actionTaken.setText("Bought " + person.getLikes().toLowerCase() + " for: " + person.getFirstName() + " " + person.getLastName());
                    }
                  });
                } else {
                  setGraphic(null);
                }
              }
            };
          }
        });

        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, btnCol);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 10, 10, 10));
        vbox.getChildren().addAll(label, table, actionTaken);
        VBox.setVgrow(table, Priority.ALWAYS);

        stage.setScene(new Scene(vbox));
        stage.show();
    }

    public static class Person {

        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
        private final SimpleStringProperty likes;

        private Person(String fName, String lName, String email, String likes) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
            this.likes = new SimpleStringProperty(likes);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }

        public String getLikes() {
            return likes.get();
        }

        public void setLikes(String likes) {
            this.likes.set(likes);
        }
    }

    // icons for non-commercial use with attribution from: http://www.iconarchive.com/show/veggies-icons-by-iconicon/bananas-icon.html and http://www.iconarchive.com/show/collection-icons-by-archigraphs.html
    private final Image coffeeImage = new Image(
      "http://icons.iconarchive.com/icons/archigraphs/collection/48/Coffee-icon.png"
    );
    private final Image fruitImage = new Image(
      "http://icons.iconarchive.com/icons/iconicon/veggies/48/bananas-icon.png"
    );
}

On Using Swing Components in JavaFX

After investigating further I’ve found examples that override cell graphics using predefined cell types like text and checks. It is not clear if any generic jfx component can be placed in a cell like a JFXPanel.

A JFXPanel is for embedding a JavaFX component in Swing not a Swing component in JavaFX, hence it would make absolutely no sense to try to place a JFXPanel in a JavaFX TableView. This is why you find no examples of anybody attempting such a thing.

This is unlike JTable, since using a JTable I can place anything that inherits from JComponent as long as I setup the render class correctly.

A JavaFX TableView is similar to a Swing JTable in this regard. Instead of a JComponent, a Node is the basic building block for JavaFX – they are analogous, though different. You can render any Node in a JavaFX TableView as long as you supply the appropriate cell factory for it.

In Java 8, there is a SwingNode that can contain a Swing JComponent. A SwingNode allows you to render any Swing component inside a JavaFX TableView.

The code to use a SwingNode is very simple:

import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import javax.swing.*;

public class SwingFx extends Application {
  @Override public void start(Stage stage) {
    SwingNode swingNode = new SwingNode();
    SwingUtilities.invokeLater(() -> swingNode.setContent(new JButton("Click me!")));

    stage.setScene(new Scene(new StackPane(swingNode), 100, 50));
    stage.show();
  }

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

Alternative Implementation

Basically what I’m trying to do is add gesture scrolling support to my java project so the user can ‘flick’ through the tables and tabs

Though Java 8 should allow you to achieve exactly what you want, if you prefer to use an older Java version, you could use the Swing based Multitouch for Java (MT4J) system for this instead of JavaFX.

Leave a Comment