JavaFX how to highlight TableColumn instead of TableRow when mouse is hovered to column - css

I have tried to find a solution to highlight table column when mouse is on top of that column, but I have not been able to find a solution. Is someone already found some solution for that? I have already tried to take a look to css and components with ScenicView, but it is not helping very much.

If you're using Java 8, you can do this quite nicely using a CSS pseudoclass. The performance of CSS pseudoclasses is very good (anecdotally, at least, it is the fastest way to change the style of JavaFX controls), and since the table view only creates a small number of table cells, even for very large data sets, this should perform very well.
Define an external css class that sets the style of a table cell with a custom pseudoclass applied as follows:
.table-cell:column-hover {
-fx-background-color: -fx-cell-focus-inner-border, -fx-selection-bar ;
-fx-background-insets: 0, 0 0 1 0 ;
-fx-background: -fx-selection-bar ;
}
(The -fx-background-color here actually changes the background color: the -fx-background is just a trick to ensure the text fill stays a suitable color relative to the background.)
Now, for each column, define a boolean property. Create a cell factory for each column that sets the boolean property to true when the mouse moves over a cell, and sets it to false when the mouse moves off. Have each cell observe the boolean property and set the pseudoclass state for the cell when it changes.
Here's an example, using the usual Person table from the standard tutorial. The interesting code is in the createCol(...) method:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableViewTest extends Application {
private static final PseudoClass COLUMN_HOVER_PSEUDO_CLASS = PseudoClass.getPseudoClass("column-hover");
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
TableColumn<Person, String> firstNameCol = createCol("First Name", Person::firstNameProperty, 150);
TableColumn<Person, String> lastNameCol = createCol("Last Name", Person::lastNameProperty, 150);
TableColumn<Person, String> emailCol = createCol("Email", Person::emailProperty, 200);
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
table.getColumns().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.getColumns().add(emailCol);
VBox root = new VBox(15, table);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add("table-column-hover.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TableColumn<Person, String> createCol(String title,
Function<Person, ObservableValue<String>> mapper, double size) {
TableColumn<Person, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> mapper.apply(cellData.getValue()));
// Is the column being hovered over with the mouse?
BooleanProperty columnHover = new SimpleBooleanProperty();
col.setCellFactory(column -> {
// basic table cell:
TableCell<Person, String> cell = new TableCell<Person, String>() ;
cell.textProperty().bind(cell.itemProperty());
// when the mouse hovers over the cell, set the columnHover to indicate
// the mouse is over the column:
cell.hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
columnHover.set(isNowHovered);
});
// update the column-hover pseudoclass state for this cell when the column is hovered over
// note this will activate the pseudoclass when the mouse is over any cell in this column
columnHover.addListener((obs, columnWasHovered, columnIsNowHovered) ->
cell.pseudoClassStateChanged(COLUMN_HOVER_PSEUDO_CLASS, columnIsNowHovered)
);
return cell ;
});
col.setPrefWidth(size);
return col ;
}
public class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
}
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 StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}

I guess that is only possible with many "hacks".
The TableColumn only exists in the data model of the TableView, the view part that is rendered based on the model gets rendered in rows, not in columns. So each TableCell has a table row as parent - not a table column.
If it were implemented the other way round, the table gets rendered in columns we could easily achieve this.
Now if you really want to do this, you would have to do the following:
Write a custom TableCell and register it as the CellRenderer for all columns.
If your custom cell gets (un-)hovered, retrieve its column and notify all other cells that are in the same column.
But: This will slow your application down if the table grows larger and / or the computer where the application is running is slow.

Related

Horizontal ScrollBar is visible in TableView with constrained resize policy

The horizontal ScrollBar of the TableView(with constrained resize policy) keeps flashing when the TableView is resized(shrinking). I believe this is a long lasting issue as I can find the open ticket for this issue as JDK-8089009 and other reference issues JDK-8115476 & JDK-8089280.
The purpose of me asking this question now is to see if anyone has a solution or workaround to fix this existing issue.
Below is the demo code as provided in JDK-8089009 where the issue is reproducible with the latest version (18+) of JavaFX.
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HorizontalConstrainedTableScrolling extends Application {
#Override
public void start(final Stage primaryStage) throws Exception {
final TableView<Object> left = new TableView<>();
final TableColumn<Object, String> leftColumn = new TableColumn<>();
left.getColumns().add(leftColumn);
left.getItems().add(new Object());
left.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
final TableView<Object> right = new TableView<>();
final TableColumn<Object, String> rightColumn = new TableColumn<>();
right.getColumns().add(rightColumn);
right.getItems().add(new Object());
final SplitPane splitPane = new SplitPane();
splitPane.setOrientation(Orientation.HORIZONTAL);
splitPane.getItems().addAll(left, right);
Label osLabel = new Label(System.getProperty("os.name"));
Label jvmLabel = new Label(
System.getProperty("java.version") +
"-" + System.getProperty("java.vm.version") +
" (" + System.getProperty("os.arch") + ")"
);
primaryStage.setScene(new Scene(new BorderPane(splitPane, null, null, new VBox(osLabel, jvmLabel), null)));
primaryStage.setWidth(600);
primaryStage.setHeight(400);
primaryStage.setTitle("TableView in SplitPane");
primaryStage.show();
}
}
[Update]:
Below is the usecase I generally encounter for using the constrained resize policy.
The requirement is , usually one column is strechable while all the other columns have some min/max widths so that they cannot go beyond those sizes. All columns should fit in the tableView provided if they have enough space, if the space is less than they can fit, then the scroll bar should appear.
Below is the demo demonstrating the example (with the scroll bar issue):
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class ConstrainedResizePolicyDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TableColumn<Person, String> fnCol = new TableColumn<>("First Name");
fnCol.setMinWidth(100);
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
TableColumn<Person, String> act1Col = new TableColumn<>("Act1");
act1Col.setMinWidth(50);
act1Col.setMaxWidth(50);
act1Col.setResizable(false);
act1Col.setCellValueFactory(param -> param.getValue().act1Property());
TableColumn<Person, String> priceCol = new TableColumn<>("Price");
priceCol.setMinWidth(100);
priceCol.setMaxWidth(150);
priceCol.setCellValueFactory(param -> param.getValue().priceProperty());
TableColumn<Person, String> act2Col = new TableColumn<>("Act2");
act2Col.setMinWidth(50);
act2Col.setMaxWidth(50);
act2Col.setResizable(false);
act2Col.setCellValueFactory(param -> param.getValue().act2Property());
TableColumn<Person, String> totalCol = new TableColumn<>("Total");
totalCol.setMinWidth(100);
totalCol.setMaxWidth(150);
totalCol.setCellValueFactory(param -> param.getValue().totalProperty());
TableColumn<Person, String> act3Col = new TableColumn<>("Act3");
act3Col.setMinWidth(50);
act3Col.setMaxWidth(50);
act3Col.setResizable(false);
act3Col.setCellValueFactory(param -> param.getValue().act3Property());
TableColumn<Person, String> bidCol = new TableColumn<>("Bid");
bidCol.setMinWidth(100);
bidCol.setMaxWidth(150);
bidCol.setCellValueFactory(param -> param.getValue().bidProperty());
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.add(new Person("Harry", "A", "200.00", "B", "210.00", "C", "300.00"));
persons.add(new Person("Kingston", "D", "260.00", "E", "610.00", "F", "700.00"));
TableView<Person> tableView = new TableView<>();
tableView.getColumns().addAll(fnCol, act1Col, priceCol, act2Col, totalCol, act3Col, bidCol);
tableView.setItems(persons);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Scene scene = new Scene(tableView, 600, 150);
primaryStage.setScene(scene);
primaryStage.setTitle("Constrained Resize Policy TableView");
primaryStage.show();
}
class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty act1 = new SimpleStringProperty();
private StringProperty price = new SimpleStringProperty();
private StringProperty act2 = new SimpleStringProperty();
private StringProperty total = new SimpleStringProperty();
private StringProperty act3 = new SimpleStringProperty();
private StringProperty bid = new SimpleStringProperty();
public Person(String fn, String act1, String price, String act2, String total, String act3, String bid) {
setFirstName(fn);
setAct1(act1);
setPrice(price);
setAct2(act2);
setTotal(total);
setAct3(act3);
setBid(bid);
}
public StringProperty firstNameProperty() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty act1Property() {
return act1;
}
public void setAct1(String act1) {
this.act1.set(act1);
}
public StringProperty priceProperty() {
return price;
}
public void setPrice(String price) {
this.price.set(price);
}
public StringProperty act2Property() {
return act2;
}
public void setAct2(String act2) {
this.act2.set(act2);
}
public StringProperty totalProperty() {
return total;
}
public void setTotal(String total) {
this.total.set(total);
}
public StringProperty act3Property() {
return act3;
}
public void setAct3(String act3) {
this.act3.set(act3);
}
public StringProperty bidProperty() {
return bid;
}
public void setBid(String bid) {
this.bid.set(bid);
}
}
}
As always with long-standing bugs, the only way out is a hack. The basic idea is to set the scrollBar's sizing constraints depending on the resize policy: either fixed to 0 for constrained- or the usual useComputed for unconstrained policy. Below is a utility method that implements the hack.
Notes
has to be called after the table is visible
has to be called whenever the policy changes at runtime
caveat: there still seems to be a slight visual artefact when resizing to very small values: looks like one (or both) of the arrows appear (not their background, just the arrow)
The code:
public static void updateScrollBar(final TableView<Object> table) {
// lookup the horizontal scroll bar
ScrollBar hbar = null;
Set<Node> scrollBars = table.lookupAll(".scroll-bar");
for (Node node : scrollBars) {
ScrollBar bar = (ScrollBar) node;
if (bar.getOrientation() == Orientation.HORIZONTAL) {
hbar = bar;
break;
}
}
// choose sizing constraint as either 0 or useComputed, depending on policy
Callback<?, ?> policy = table.getColumnResizePolicy();
double pref = policy == CONSTRAINED_RESIZE_POLICY ? 0 : USE_COMPUTED_SIZE;
// set all sizing constraints
hbar.setPrefSize(pref, pref);
hbar.setMaxSize(pref, pref);
hbar.setMinSize(pref, pref);
}
I use a workaround that has worked for me since JavaFX 8 and still does in JavaFX 19, but using the workaround means that you have follow some rules.
In order for this to work:
any TableColumn that has maxWidth set must not be resizable
resizable columns must be next to each other or resizing them will sometimes show the hbar
you should then disable the column reordering for all columns so the user cannot rearrange them in a way where this workaround does not work.
Once that is order, you just need a binding and a listener. The binding takes care the scrollbar when resizing the table and listener takes care of the scrollbar when resizing the columns.
The following works for your first example code. No listener is needed because there is only the one column:
leftColumn.prefWidthProperty().bind(left.widthProperty().subtract(1));
The following works for your second example code, including the reorderable and resizable changes. The binding and the listener can be applied to your choice of the TableColumns provided that your chosen column is resizable and does not have maxWidth set:
#Override
public void start(Stage primaryStage) throws Exception {
TableColumn<Person, String> fnCol = new TableColumn<>("First Name");
fnCol.setMinWidth(100);
fnCol.setReorderable(false);
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
TableColumn<Person, String> priceCol = new TableColumn<>("Price");
priceCol.setMinWidth(100);
priceCol.setReorderable(false);
priceCol.setCellValueFactory(param -> param.getValue().priceProperty());
TableColumn<Person, String> totalCol = new TableColumn<>("Total");
totalCol.setMinWidth(100);
totalCol.setReorderable(false);
totalCol.setCellValueFactory(param -> param.getValue().totalProperty());
TableColumn<Person, String> bidCol = new TableColumn<>("Bid");
bidCol.setMinWidth(100);
bidCol.setReorderable(false);
bidCol.setCellValueFactory(param -> param.getValue().bidProperty());
TableColumn<Person, String> act1Col = new TableColumn<>("Act1");
act1Col.setMinWidth(50);
act1Col.setMaxWidth(50);
act1Col.setResizable(false);
act1Col.setReorderable(false);
act1Col.setCellValueFactory(param -> param.getValue().act1Property());
TableColumn<Person, String> act2Col = new TableColumn<>("Act2");
act2Col.setMinWidth(50);
act2Col.setMaxWidth(50);
act2Col.setResizable(false);
act2Col.setReorderable(false);
act2Col.setCellValueFactory(param -> param.getValue().act2Property());
TableColumn<Person, String> act3Col = new TableColumn<>("Act3");
act3Col.setMinWidth(50);
act3Col.setMaxWidth(50);
act3Col.setResizable(false);
act3Col.setReorderable(false);
act3Col.setCellValueFactory(param -> param.getValue().act3Property());
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.add(new Person("Harry", "A", "200.00", "B", "210.00", "C", "300.00"));
persons.add(new Person("Kingston", "D", "260.00", "E", "610.00", "F", "700.00"));
TableView<Person> tableView = new TableView<>();
tableView.setPadding(Insets.EMPTY);
tableView.getColumns().addAll(fnCol, priceCol, totalCol, bidCol, act1Col, act2Col, act3Col);
tableView.setItems(persons);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
fnCol.widthProperty().addListener((obs, ov, nv)->{
if (nv != null && !ov.equals(nv) && (double)nv > 0) {
if ((double)nv > (double)ov) {
fnCol.prefWidthProperty().unbind();
fnCol.setPrefWidth(tableView.getWidth() - act1Col.getWidth() - priceCol.getWidth() - act2Col.getWidth() - totalCol.getWidth() - act3Col.getWidth() - bidCol.getWidth());
fnCol.prefWidthProperty().bind(
tableView.widthProperty()
.subtract(act1Col.widthProperty())
.subtract(priceCol.widthProperty())
.subtract(act2Col.widthProperty())
.subtract(totalCol.widthProperty())
.subtract(act3Col.widthProperty())
.subtract(bidCol.widthProperty())
);
}
}
});
fnCol.prefWidthProperty().bind(
tableView.widthProperty()
.subtract(act1Col.widthProperty())
.subtract(priceCol.widthProperty())
.subtract(act2Col.widthProperty())
.subtract(totalCol.widthProperty())
.subtract(act3Col.widthProperty())
.subtract(bidCol.widthProperty())
);
Scene scene = new Scene(tableView, 600, 150);
primaryStage.setScene(scene);
primaryStage.setTitle("Constrained Resize Policy TableView");
primaryStage.show();
}
I'm assuming this works simply because of when precisely calculations are made of column widths and when horizontal scrollbar visibility is triggered and the order in which all these things happen.

position of TableColumn in Scene

I have a TableView with several TableColumns and I want to place a Node below a certain TableColumn. How do I get the exact position (x,y-coordinates) of the TableColumn so I can bind the translate properties of my node?
Here is a snippet of how I placed a button on the top right corner of my TableView:
button.translateXProperty().unbind();
button.translateXProperty().bind(tableView.widthProperty().divide(2.0).subtract(button.getWidth() / 2.0 + 2.0) + tableView.localToScene(0.0, 0.0).getX());
This works fine, but obviously only for the TableView. The TableColumns don't have those translate properties or the localToScene methods, so I can't directly get the position to which I would like to bind my Node.
My current solution (which doesn't really work that well) is to do the following:
I read out the position of my TableView in the Scene (PointA) and then go through the list of all columns (tableView.getColumns()) and check if each of them is visible, and if so, add their width to the X-value of PointA. I do this until I find the actual column that I want to place the Node below.
Now the problem is, that I can't really just bind the Nodes position to this point, because when I change the order of the columns, or make one of them invisible, my column changes position on the screen. I would have to add a listener to the column order and visibility...
Is there any more efficient way to do what I want? :D
I generally dislike using lookups, but you can retrieve the label that is used to display the column header using the lookup .table-view .column-header .label and then bind your button's layout properties using the bounds of that label.
Example:
import java.util.Optional;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class TableColumnLocationExample extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty, 120));
table.getColumns().add(column("Last Name", Person::lastNameProperty, 120));
table.getColumns().add(column("Email", Person::emailProperty, 250));
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
Pane root = new Pane(table);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
for (TableColumn<Person, ?> col : table.getColumns()) {
Optional<Label> header = findLabelForTableColumnHeader(col.getText(), root);
header.ifPresent(label -> {
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
label.getBoundsInLocal().getWidth(), label.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
label.getLocalToSceneTransform().transform(label.getBoundsInLocal()).getMinX(),
label.boundsInLocalProperty(), label.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
});
}
}
private Optional<Label> findLabelForTableColumnHeader(String text, Parent root) {
return root.lookupAll(".table-view .column-header .label")
.stream()
.map(Label.class::cast)
.filter(label -> label.getText().equals(text))
.findAny(); // assumes all columns have unique text...
}
private <S,T> TableColumn<S,T> column(String title, Function<S,ObservableValue<T>> property, double width) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
return col ;
}
public static class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
If you are allowed to use non-public api, you might consider to access the TableColumnHeader via its skin, provided it's of type TableViewSkinBase: it has api to access the TableRowHeader which is the container of all TableColumnHeaders and has api to find the header for any column it contains.
Code snippet (the width/location binding is copied from James' example, just to the header instead of the label)
private void buttonsPerHeader(TableView<Person> table, Pane root) {
if (!(table.getSkin() instanceof TableViewSkinBase)) return;
TableViewSkinBase skin = (TableViewSkinBase) table.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
for (TableColumn col : table.getColumns()) {
TableColumnHeader header = headerRow.getColumnHeaderFor(col);
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
header.getBoundsInLocal().getWidth(), header.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
header.getLocalToSceneTransform().transform(header.getBoundsInLocal()).getMinX(),
header.boundsInLocalProperty(), header.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
}
}

JavaFX TableView Filter Right Mouse Button Selection

When I press the Right Mouse button on a JavaFX TreeTableView it selects the node I clicked on. I can understand why this may be desirable in most cases, however not in all cases.
I am working on an application where I have multiple columns in the tree table with one column using a canvas widget for custom graphics (waveforms). The graphics column needs to be able to be interacted with for various reasons (setting markers, zooming, etc.) with the mouse. Because of this I don't want the mouse buttons to select the row in the table (or interact with the table).
I was able filter out the click with the first mouse button by putting a addEventFilter() for MouseEvent.MOUSE_CLICKED with a event.consume(). I can then process the mouse click the way I want.
However when I right click on the cell the selection in the table changes to that row. I have tried putting event filters on the cell, the table, the row, the column, nothing seems to work to filter the right click selection change. Note the canvas widget is set to be mouse transparent.
Here is an example using the standard address book example, except I replaced the email column with a canvas widget.
package sample;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
private TableView<Person> table = new TableView<Person>();
private ContextMenu menu = new ContextMenu();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
class MyTableCell extends TableCell<Person, String> {
public MyTableCell(ContextMenu menu) {
super();
setContextMenu(menu);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
setGraphic(null);
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
}
#Override
public void start(Stage stage) throws Exception{
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
menu.getItems().add(new MenuItem("Hello World"));
Callback cellFactory = param -> new MyTableCell(menu);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(cellFactory);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(cellFactory);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(param -> new MySpecialCell());
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
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);
}
}
}
This is using Java 8u20. In this example the MyTableCell class is used for the cells in the first two columns. It adds a context menu to those columns. The MySpecialCell class is for the last column and it puts a canvas in the table with the email text in it. It also goes way overboard and filters ANY mouse event.
If you run this you will see that if you use mouse button 1 in the first two columns you can change the selection in the table. Mouse button 1 in the third column does not change selection. However Mouse button 2 (right mouse button) DOES change selection in the third column.
I want it to not change selection. Can someone give me a hint on how to prevent selection from changing when using the right mouse button in the column?
NOTE: I have tried having the canvas process the mouse input (actually not make it mouse transparent and having it filter all mouse events) and the table still changes selection on right click. For various reasons (where the data is, the fact that the canvas is actually a stack of canvas widgets, the difficulty of figuring out what node the mouse is interacting with in the table, etc) in my real application I wanted the cell to handle the mouse input.
Thanks!
To repeat my comments: I think the behaviour might be rather a bug than a feature - I would expect that consuming any mouseEvent would prevent the right (secondary) button just the same as the left (primary) button normal actions.
Was evaluated as a feature: the missing piece was to consume the contextMenuEvents in addition to the mouseEvents, something like
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
// move Jonathan's code from table to cell level:
// need to consume contextMenuEvents as well
addEventFilter(ContextMenuEvent.ANY, e -> e.consume());
}
Back to disagreeing with the evaluation:
all mouse buttons should behave consistently to consuming all mouseEvents
contextMenuEvent is basically unrelated to selection, so consuming it should have no effect (in either way) on selection
The hack below might still be needed if contextMenu's triggered by keyboard on the special column are required, didn't dig further, though.
Anyway, a quick hack-around is to replace the default behaviour by a custom implementation. The collaborators
a custom TableCellBehaviour that overrides doSelect to do nothing on receiving a secondary button event
a custom TableCellSkin: that's only needed to plug-in the custom behaviour - needs to extend TableCellSkinBase (and c&p the implementation of its abstract methods from TableCellSkin) because only then we have a constructor taking our custom behaviour
let the custom TableCell create the custom skin instead of the default
Something like (note: while working, it's not properly tested):
class MySpecialCellBehavior extends TableCellBehavior {
public MySpecialCellBehavior(TableCell control) {
super(control);
}
#Override
protected void doSelect(double x, double y, MouseButton button,
int clickCount, boolean shiftDown, boolean shortcutDown) {
// do nothing on secondary button
if (button == MouseButton.SECONDARY) return;
super.doSelect(x, y, button, clickCount, shiftDown, shortcutDown);
}
}
class MySpecialCellSkin extends TableCellSkinBase {
private final TableColumn tableColumn;
public MySpecialCellSkin(TableCell tableCell) {
super(tableCell, new MySpecialCellBehavior(tableCell));
// doesn't make a difference
//consumeMouseEvents(true);
this.tableColumn = tableCell.getTableColumn();
super.init(tableCell);
}
#Override protected BooleanProperty columnVisibleProperty() {
return tableColumn.visibleProperty();
}
#Override protected ReadOnlyDoubleProperty columnWidthProperty() {
return tableColumn.widthProperty();
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
// create and return the custom skin
#Override
protected Skin<?> createDefaultSkin() {
return new MySpecialCellSkin(this);
}
}

Expand table row on mouse click

I found these examples:
http://www.jeasyui.com/tutorial/datagrid/datagrid21.php\
Can a table row expand and close?
Basically I want to create a JavaFX table which I can expand in order to see more data. Is there any similar example written in JavaFX?
EDIT
So, after reworking the problem with tableView specifics, I (sort of) quickly hacked together this example. Keep in mind, I didn't use the animation mentioned in the original answer, although it would be easy enough to adapt, and I didn't replicate the provided example exactly at all, since I honestly, didn't have time. But this gives the basic accordion feel, where you would just need to spend time messing around with various width and height properties of different fields to achieve something that was exactly that. (in the handler you might want to even insert a row where the first column has a huge width and a nested table view to achieve sort of exactly what they were doing). again, this is with 1 column, and it shows the basics of adding a bit of added information on expansion, you could take this as far as you want:
fileChooserExample.java:
package filechooserexample;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.*;
import javafx.util.Callback;
public class FileChooserExample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) {
stage.setTitle("People");
// stage.getIcons().add(new Image("http://icons.iconarchive.com/icons/icons-land/vista-people/72/Historical-Viking-Female-icon.png")); // icon license: Linkware (Backlink to http://www.icons-land.com required)
// create a table.
final TableView<Person> table = new TableView<>(
FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
)
);
// define the table columns.
TableColumn<Person, Boolean> actionCol = new TableColumn<>("Action");
actionCol.setSortable(false);
actionCol.setPrefWidth(1000);
// define a simple boolean cell value for the action column so that the column will only be shown for non-empty rows.
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Boolean>, ObservableValue<Boolean>>() {
#Override public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Person, Boolean> features) {
return new SimpleBooleanProperty(features.getValue() != null);
}
});
// create a cell value factory with an add button for each row in the table.
actionCol.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Person, Boolean>>() {
#Override public TableCell<Person, Boolean> call(TableColumn<Person, Boolean> personBooleanTableColumn) {
return new AddPersonCell(stage, table);
}
});
table.getColumns().setAll(actionCol);
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
stage.setScene(new Scene(table));
stage.show();
}
/** A table cell containing a button for adding a new person. */
private class AddPersonCell extends TableCell<Person, Boolean> {
// a button for adding a new person.
final Button addButton = new Button("Add");
// pads and centers the add button in the cell.
final VBox paddedButton = new VBox();
final HBox mainHolder = new HBox();
// records the y pos of the last button press so that the add person dialog can be shown next to the cell.
final DoubleProperty buttonY = new SimpleDoubleProperty();
/**
* AddPersonCell constructor
* #param stage the stage in which the table is placed.
* #param table the table to which a new person can be added.
*/
AddPersonCell(final Stage stage, final TableView table) {
paddedButton.setPadding(new Insets(3));
paddedButton.getChildren().add(addButton);
mainHolder.getChildren().add(paddedButton);
addButton.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
buttonY.set(mouseEvent.getScreenY());
if (getTableRow().getPrefHeight() == 100){
getTableRow().setPrefHeight(35);
paddedButton.getChildren().remove(1);
getTableRow().autosize();
}
else{
getTableRow().setPrefHeight(100);
Label myLabel = new Label();
myLabel.setText("This is new label text!");
myLabel.setTextFill(Color.BLACK);
paddedButton.getChildren().add(myLabel);
getTableRow().autosize();
}
}
});
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
table.getSelectionModel().select(getTableRow().getIndex());
}
});
}
/** places an add button in the row only if the row is not empty. */
#Override protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(paddedButton);
}
}
}
}
Person.java:
package filechooserexample;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private StringProperty firstName;
private StringProperty lastName;
public Person(String firstName, String lastName) {
setFirstName(firstName);
setLastName(lastName);
}
public final void setFirstName(String value) { firstNameProperty().set(value); }
public final void setLastName(String value) { lastNameProperty().set(value); }
public String getFirstName() { return firstNameProperty().get(); }
public String getLastName() { return lastNameProperty().get(); }
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
public StringProperty lastNameProperty() {
if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
return lastName;
}
}
Again, pardon the seemingly hackery of adding the various buttons with the named columns that do nothing, It just got super busy here so I borrowed the main table structure from :
original SO table dynamic row addition question
Who did a wonderful job of adding additional rows to a table.
again, if this is not at all what you need let me know, and I'll try to help as best I can.

JavaFX table- how to add components?

I have a swing project that uses many JTables to display all sorts of things from text to panels to a mix of buttons and check boxes. I was able to do this by overwriting the table cell renderer to return generic JComponents. My question is can a similar table be made using JavaFx?
I want to update all my tables in the project to use JavaFx to support gestures mostly. It seems that TableView is the JavaFx component to use and I tried adding buttons to it but when displayed it shows the string value of the button, not the button itself. It looks like I have to overwrite the row factory or cell factory to do what I want but there are not a lot of examples. Here is the code I used as an example that displays the button as a string.
import javax.swing.JButton;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class GestureEvents extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com","The Button"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com","The Button"),
new Person("Ethan", "Williams", "ethan.williams#example.com","The Button"),
new Person("Emma", "Jones", "emma.jones#example.com","The Button"),
new Person("Michael", "Brown", "michael.brown#example.com","The Button")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
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 btnCol = new TableColumn("Buttons");
btnCol.setMinWidth(100);
btnCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("btn"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, btnCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final JButton btn;
private Person(String fName, String lName, String email, String btn) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
this.btn = new JButton(btn);
}
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 JButton getBtn(){
return btn;
}
public void setBtn(String btn){
}
}
public static class ButtonPerson{
private final JButton btn;
private ButtonPerson(){
btn = new JButton("The Button");
}
public JButton getButton(){
return btn;
}
}
}
Edit: 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. 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. If someone knows how (or if it's even possible) to place a JFXPanel in a cell or a other generic JFx component like a Button that would be very helpful.
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
Use JavaFX Button instead instead of javax.swing.JButton.
Provide a cell rendering factory for your button.
Generate a button in the table cell rather than in the Person.
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.
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", "jacob.smith#example.com","Coffee"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com","Fruit"),
new Person("Ethan", "Williams", "ethan.williams#example.com","Fruit"),
new Person("Emma", "Jones", "emma.jones#example.com","Coffee"),
new Person("Michael", "Brown", "michael.brown#example.com","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.

Resources