How to detect double click on TextFieldTableCell in JavaFx? - java

I have the following tableColumn
TableColumn<TradePurchaseOrderManifest, Double> netweightCol = createColumn("netWeight", "Net Wgt",
Double.class);
and the createColumn method
public static <T> TableColumn<TradePurchaseOrderManifest, T> createColumn(String name, String columHeading,
Class<T> type) {
TableColumn<TradePurchaseOrderManifest, T> column = new TableColumn<>(columHeading);
column.setCellValueFactory(new PropertyValueFactory<>(name));
column.setResizable(true);
return column;
}
This table have other columns as well which are of types ComboBoxTableCell etc. I wish to have a double click handler on this TextFieldTableCell AND only on this column. What I am able to achieve by now is to have a doubleClick handler on the tableview(row).
When I click on this cell, it converts to a TextFieldTableCell and then does not respond to double-clicks even if I am checking if it is an instance of TextFieldTableCell
tableView.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
if (event.getTarget() instanceof TableCell<?,?>) {
System.out.println("dblCLick tableCell");
} else if (event.getTarget() instanceof TextFieldTableCell<?,?>) {
System.out.println("dblCLick textfield");
}
}
}
});
Any suggestion on how to apply double click handler ONLY on this column and when it is a TextFieldTableCell.

This is a workaround I use. I use ContextMenus to handle similar situations.
import java.util.Arrays;
import java.util.Optional;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextInputDialog;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
/**
* Sedrick (SedJ601)
* Uses code from https://gist.github.com/james-d/7758918, https://code.makery.ch/blog/javafx-dialogs-official/ and https://stackoverflow.com/questions/21009377/context-menu-on-a-row-of-tableview
*/
public class App extends Application {
private TableView<Person> table = new TableView<Person>();
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")
);
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<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
table.setItems(data);
table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));
table.setRowFactory((TableView<Person> tableView) -> {
final TableRow<Person> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem removeMenuItem = new MenuItem("Change last name");
removeMenuItem.setOnAction((ActionEvent event) -> {
Person tempPerson = table.getSelectionModel().getSelectedItem();
int rowIndex = table.getSelectionModel().getSelectedIndex();
TextInputDialog dialog = new TextInputDialog(tempPerson.getLastName());
dialog.setTitle("Text Input Dialog");
dialog.setHeaderText("Look, a Text Input Dialog");
dialog.setContentText("Please enter a last name:");
// Traditional way to get the response value.
Optional<String> result = dialog.showAndWait();
if (result.isPresent()){
tempPerson.setLastName(result.get());
tableView.getItems().set(rowIndex, tempPerson);
}
});
contextMenu.getItems().add(removeMenuItem);
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu)null)
.otherwise(contextMenu)
);
return row ;
});
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 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);
}
}
}

Related

Javafx - Add action to button click on table row [duplicate]

I am working on a TableView (FXML) where I want to have all the rows accompanied with a delete button at the last column.
Here's a video that shows what I mean: YouTube Delete Button in TableView
Here's what I have in my main controller class:
public Button del() {
Button del = new Button();
del.setText("X");
del.setPrefWidth(30);
del.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
int i = index.get();
if(i > -1) {
goals.remove(i);
list.getSelectionModel().clearSelection();
}
}
});
return del;
}
private SimpleIntegerProperty index = new SimpleIntegerProperty();
#Override
public void initialize(URL location, ResourceBundle resources){
//DateFormat df = new SimpleDateFormat("dd MMM yyyy");
sdate.setValue(LocalDate.now());
edate.setValue(LocalDate.now());
seq.setCellValueFactory(new PropertyValueFactory<Goals, Integer>("id"));
gol.setCellValueFactory(new PropertyValueFactory<Goals, String>("goal"));
sdt.setCellValueFactory(new PropertyValueFactory<Goals, Date>("sdte"));
edt.setCellValueFactory(new PropertyValueFactory<Goals, Date>("edte"));
prog.setCellValueFactory(new PropertyValueFactory<Goals, Integer>("pb"));
del.setCellValueFactory(new PropertyValueFactory<Goals, Button>("x"));
list.setItems(goals);
list.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observable,
Object oldValue, Object newValue) {
index.set(goals.indexOf(newValue));
System.out.println("Index is: "+goals.indexOf(newValue));
}
});
}
Each time I launch the application, I will try to click the delete button from random rows but it always delete the first row. I guess the addListener method I use for list is not properly implemented and indexOf(newValue) is always 0 at every initialisation.
However, it will work if I click a row first and then click the delete button. But this is not what I want. I want users to be able to delete any row if they press the delete button without selecting the row.
Appreciate your help guys!
You need a custom cell factory defined for the column containing the delete button.
TableColumn<Person, Person> unfriendCol = new TableColumn<>("Anti-social");
unfriendCol.setCellValueFactory(
param -> new ReadOnlyObjectWrapper<>(param.getValue())
);
unfriendCol.setCellFactory(param -> new TableCell<Person, Person>() {
private final Button deleteButton = new Button("Unfriend");
#Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
if (person == null) {
setGraphic(null);
return;
}
setGraphic(deleteButton);
deleteButton.setOnAction(
event -> getTableView().getItems().remove(person)
);
}
});
Here is a sample app. It doesn't use FXML, but you could adapt it to work with FXML very easily. Just click on an "Unfriend" button in the "Anti-social" column to delete a friend. Do it a lot and you will soon run out of friends.
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Priority;
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<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final Label label = new Label("Friends");
label.setFont(new Font("Arial", 20));
final Label actionTaken = new Label();
TableColumn<Person, Person> unfriendCol = new TableColumn<>("Anti-social");
unfriendCol.setMinWidth(40);
unfriendCol.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue()));
unfriendCol.setCellFactory(param -> new TableCell<Person, Person>() {
private final Button deleteButton = new Button("Unfriend");
#Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
if (person == null) {
setGraphic(null);
return;
}
setGraphic(deleteButton);
deleteButton.setOnAction(event -> data.remove(person));
}
});
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
table.setItems(data);
table.getColumns().addAll(unfriendCol, firstNameCol, lastNameCol);
table.setPrefHeight(250);
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 Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
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);
}
}
}

Global table under TabPane - JavaFX

I am going through the Oracle tutorial with tables, and have implemented my own tabPane into this code. What I want is for the table to be a global table, so that when I go onto tab 2, it is the exact same as it is on Tab 1, just only that i can edit certain columns in tab 2 compared to editing it all in tab 1.
Is there anyway this can be done instead of basically duplicating code and creating a second table, which i believe is tedious as I don't want two tables, I want one global table.
public class TableSample extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"));
public static void main(String[] args) {
launch(args);
}
private TableView table = new TableView();
public void start(Stage stage) {
Scene scene = new Scene(new Group());
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("Tab 1");
tabPane.getTabs().add(tab1);
Tab tab2 = new Tab("Tab 2");
tabPane.getTabs().add(tab2);
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setFirstName(t.getNewValue());
}
}
);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
lastNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setLastName(t.getNewValue());
}
}
);
TableColumn emailCol = new TableColumn("Email");
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(TextFieldTableCell.forTableColumn());
emailCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setEmail(t.getNewValue());
}
}
);
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.getChildren().addAll(tabPane, 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 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);
}
}
}
Your code structure is already setup correctly. You just need to add Nodes to the different Tabs
Key Code: You need to add a root node to each Tab. Each root node will hold the other nodes that give each Tab it looks.
Tab tab1 = new Tab("Tab 1");
Label tab1Label = new Label("Use tab1Root to add nodes for tab1!");
StackPane tab1Root = new StackPane(tab1Label);
tab1Root.setPrefSize(500, 500);
tab1.setContent(tab1Root);
tabPane.getTabs().add(tab1);
Tab tab2 = new Tab("Tab 2");
Button tab2Button = new Button("Press Me!");
tab2Button.setOnAction((event) -> {
System.out.println("Use tab2Root to add nodes for tab2!");
});
StackPane tab2Root = new StackPane(tab2Button);
tab2Root.setPrefSize(500, 500);
tab2.setContent(tab2Root);
tabPane.getTabs().add(tab2);
Full Code
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TreeTableColumn.CellEditEvent;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestingGround extends Application
{
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data
= FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"));
public static void main(String[] args)
{
launch(args);
}
public void start(Stage stage)
{
Scene scene = new Scene(new Group());
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("Tab 1");
Label tab1Label = new Label("Use tab1Root to add nodes for tab1!");
StackPane tab1Root = new StackPane(tab1Label);
tab1Root.setPrefSize(500, 500);
tab1.setContent(tab1Root);
tabPane.getTabs().add(tab1);
Tab tab2 = new Tab("Tab 2");
Button tab2Button = new Button("Press Me!");
tab2Button.setOnAction((event) -> {
System.out.println("Use tab2Root to add nodes for tab2!");
});
StackPane tab2Root = new StackPane(tab2Button);
tab2Root.setPrefSize(500, 500);
tab2.setContent(tab2Root);
tabPane.getTabs().add(tab2);
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>()
{
#Override
public void handle(CellEditEvent<Person, String> t)
{
// ((Person) t.getTableView().getItems().get(
// t.getTablePosition().getRow())).setFirstName(t.getNewValue());
}
}
);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
lastNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>()
{
#Override
public void handle(CellEditEvent<Person, String> t)
{
// ((Person) t.getTableView().getItems().get(
// t.getTablePosition().getRow())).setLastName(t.getNewValue());
}
}
);
TableColumn emailCol = new TableColumn("Email");
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(TextFieldTableCell.forTableColumn());
emailCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>()
{
#Override
public void handle(CellEditEvent<Person, String> t)
{
// ((Person) t.getTableView().getItems().get(
// t.getTablePosition().getRow())).setEmail(t.getNewValue());
}
}
);
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.getChildren().addAll(tabPane, 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 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);
}
}
}

JavaFX property adapter for TableView

For DTO I use POJO. So to make bidirectional binding I create adapter. I mean, something like that:
POJO:
public class Temp{
private BigDecimal weight;
private final PropertyChangeSupport propertyChangeSupport;
public Temp() {
this.propertyChangeSupport = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public BigDecimal getWeight() {
return weight;
}
public void setWeight(BigDecimal weight) {
BigDecimal pv = this.weight;
this.weight = weight;
propertyChangeSupport.firePropertyChange("weight", pv, weight);
}
}
I have the following adapter:
public class TempAdapter {
private ObjectProperty<BigDecimal> weightProperty;
public TempAdapter(Temp temp) {
try {
weightProperty=new JavaBeanObjectPropertyBuilder<BigDecimal>().bean(temp).name("weight").build();
weightProperty.addListener(new ChangeListener<BigDecimal>() {
#Override
public void changed(ObservableValue<? extends BigDecimal> ov, BigDecimal t, BigDecimal t1) {
....
}
});
} catch (NoSuchMethodException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
public ObjectProperty<BigDecimal> getWeightProperty() {
return weightProperty;
}
However, I can't understand how to use this adapter with TableView. The reason I want to use adapter for TableView is that otherwise we will have to duplicate the code of the adapter in TableView if we using POJO for DTO with TableView.
As I understand for every row in TableView we must create a new Instance of Adapter and I can't understand how to do it.
Solution without an adapter class
First note you don't necessarily need an adapter class; you can just create the JavaBeanProperty instances where you need them: in this case in the cell value factory for the table. If there is just one (or perhaps two) places in the UI where you need to bind directly to a JavaFX Property corresponding to properties in your POJO, then this is probably the way to go.
Here's a complete example of this technique, using the usual Oracle Person table example. In this example, there is no adapter class: the table just creates the JavaBeanStringProperty adapters in the cell value factory. There is an edit form, which just interacts with the POJO class directly.
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.application.Application;
import javafx.beans.property.adapter.JavaBeanStringProperty;
import javafx.beans.property.adapter.JavaBeanStringPropertyBuilder;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
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.control.TextField;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class PojoTable extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.setEditable(true);
TableColumn<Person, String> firstNameColumn = createColumn("First Name", "firstName");
TableColumn<Person, String> lastNameColumn = createColumn("Last Name", "lastName");
table.getColumns().add(firstNameColumn);
table.getColumns().add(lastNameColumn);
Button button = new Button("Show data");
button.setOnAction(e -> {
table.getItems().stream().map(person -> person.getFirstName() + " " + person.getLastName())
.forEach(System.out::println);
System.out.println();
});
Button edit = new Button("Edit");
edit.disableProperty().bind(table.getSelectionModel().selectedItemProperty().isNull());
edit.setOnAction(e -> edit(table.getSelectionModel().getSelectedItem(), primaryStage));
table.getItems().addAll(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
);
HBox buttons = new HBox(10, button, edit);
buttons.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(table, null, null, buttons, null);
BorderPane.setAlignment(buttons, Pos.CENTER);
BorderPane.setMargin(buttons, new Insets(10));
root.setPadding(new Insets(10));
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private void edit(Person person, Stage primaryStage) {
GridPane editPane = new GridPane();
TextField firstNameField = new TextField(person.getFirstName());
TextField lastNameField = new TextField(person.getLastName());
Button okButton = new Button("OK");
Button cancelButton = new Button("Cancel");
HBox buttons = new HBox(10, okButton, cancelButton);
editPane.addRow(0, new Label("First Name:"), firstNameField);
editPane.addRow(1, new Label("Last Name:"), lastNameField);
editPane.add(buttons, 0, 2, 2, 1);
GridPane.setHalignment(buttons, HPos.CENTER);
GridPane.setMargin(buttons, new Insets(10));
editPane.setPadding(new Insets(10));
Scene scene = new Scene(editPane);
Stage stage = new Stage();
stage.setScene(scene);
stage.initOwner(primaryStage);
stage.initModality(Modality.APPLICATION_MODAL);
stage.initStyle(StageStyle.UNDECORATED);
cancelButton.setOnAction(e -> stage.hide());
okButton.setOnAction(e -> {
person.setFirstName(firstNameField.getText());
person.setLastName(lastNameField.getText());
stage.hide();
});
stage.show();
}
private TableColumn<Person, String> createColumn(String title, String property) {
TableColumn<Person, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> {
Person p = cellData.getValue();
try {
JavaBeanStringProperty prop = new JavaBeanStringPropertyBuilder()
.bean(p)
.name(property)
.build();
return prop;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
col.setCellFactory(TextFieldTableCell.forTableColumn());
return col ;
}
public static class Person {
private String firstName ;
private String lastName ;
private PropertyChangeSupport support ;
public Person(String firstName, String lastName) {
this.firstName = firstName ;
this.lastName = lastName ;
support = new PropertyChangeSupport(this);
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
String previous = this.firstName ;
this.firstName = firstName;
support.firePropertyChange("firstName", previous, firstName);
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
String previous = this.lastName ;
this.lastName = lastName;
support.firePropertyChange("lastName", previous, lastName);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
}
public static void main(String[] args) {
launch(args);
}
}
Solution using an adapter class
Note that in the above example, the text fields in the editor can't use bindings directly with the POJO class (because it doesn't expose any JavaFX properties); if you wanted to do so you could create more JavaBeanStringPropertys for the purpose, but that would end up duplicating code. If you wanted to be able to do this, then it might become beneficial to use an adapter class. Here is what the code might look like using this solution. Note that now the adapter class exposes JavaFX properties, so the table's cell value factory can just map directly to those properties: the creation of the JavaBeanStringPropertys is encapsulated in one place (the adapter class):
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.beans.property.adapter.JavaBeanStringPropertyBuilder;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
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.control.TextField;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class PojoTable extends Application {
#Override
public void start(Stage primaryStage) {
TableView<PersonAdapter> table = new TableView<>();
table.setEditable(true);
TableColumn<PersonAdapter, String> firstNameColumn = createColumn("First Name", PersonAdapter::firstNameProperty);
TableColumn<PersonAdapter, String> lastNameColumn = createColumn("Last Name", PersonAdapter::lastNameProperty);
table.getColumns().add(firstNameColumn);
table.getColumns().add(lastNameColumn);
List<Person> data = Arrays.asList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
);
Button button = new Button("Show data");
button.setOnAction(e -> {
data.stream().map(person -> person.getFirstName() + " " + person.getLastName())
.forEach(System.out::println);
System.out.println();
});
Button edit = new Button("Edit");
edit.disableProperty().bind(table.getSelectionModel().selectedItemProperty().isNull());
edit.setOnAction(e -> edit(table.getSelectionModel().getSelectedItem(), primaryStage));
data.stream().map(PersonAdapter::new).forEach(table.getItems()::add);
HBox buttons = new HBox(10, button, edit);
buttons.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(table, null, null, buttons, null);
BorderPane.setAlignment(buttons, Pos.CENTER);
BorderPane.setMargin(buttons, new Insets(10));
root.setPadding(new Insets(10));
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private void edit(PersonAdapter person, Stage primaryStage) {
GridPane editPane = new GridPane();
TextField firstNameField = new TextField();
firstNameField.textProperty().bindBidirectional(person.firstNameProperty());
TextField lastNameField = new TextField();
lastNameField.textProperty().bindBidirectional(person.lastNameProperty());
Button okButton = new Button("OK");
HBox buttons = new HBox(10, okButton);
editPane.addRow(0, new Label("First Name:"), firstNameField);
editPane.addRow(1, new Label("Last Name:"), lastNameField);
editPane.add(buttons, 0, 2, 2, 1);
GridPane.setHalignment(buttons, HPos.CENTER);
GridPane.setMargin(buttons, new Insets(10));
editPane.setPadding(new Insets(10));
Scene scene = new Scene(editPane);
Stage stage = new Stage();
stage.setScene(scene);
stage.initOwner(primaryStage);
stage.initModality(Modality.APPLICATION_MODAL);
stage.initStyle(StageStyle.UNDECORATED);
okButton.setOnAction(e -> {
stage.hide();
});
stage.show();
}
private TableColumn<PersonAdapter, String> createColumn(String title, Function<PersonAdapter, StringProperty> property) {
TableColumn<PersonAdapter, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(TextFieldTableCell.forTableColumn());
return col ;
}
public static class Person {
private String firstName ;
private String lastName ;
private PropertyChangeSupport support ;
public Person(String firstName, String lastName) {
this.firstName = firstName ;
this.lastName = lastName ;
support = new PropertyChangeSupport(this);
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
String previous = this.firstName ;
this.firstName = firstName;
support.firePropertyChange("firstName", previous, firstName);
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
String previous = this.lastName ;
this.lastName = lastName;
support.firePropertyChange("lastName", previous, lastName);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
}
public static class PersonAdapter {
private final Person person ;
private final StringProperty firstName ;
private final StringProperty lastName ;
public PersonAdapter(Person person) {
this.person = person ;
try {
this.firstName = new JavaBeanStringPropertyBuilder()
.bean(person)
.name("firstName")
.build();
this.lastName = new JavaBeanStringPropertyBuilder()
.bean(person)
.name("lastName")
.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Person getPerson() {
return person ;
}
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 static void main(String[] args) {
launch(args);
}
}
The one possible disadvantage to this approach is that changes to the underlying list (data in the simple example) will not propagate to the table (this means adding or removing elements from data will not change the table; calling setFirstName or setLastName on the existing elements of the table will allow for updates). For techniques to manage this, see Best practice to decorate an ObservableList and retain change events

TableView - Attempting to make it behave like Excel

I'm attempting to make TableView behave like Excel. I am having trouble selected the correct cells.
In Excel, if you select a column(say 5 adjacent cells in the column) and then you click SHIFT + LEFT, excel will automatically select all corresponding left most cell.
e.g.
Say in Excel I have [A1][A2][A3] selects and then I click SHIFT + RIGHT, know the selected cells would be:
[A1][B1][A2][B2][A3][B3]
How can I achieve similar behavior with a TableView.
I've tried doing something like the following with no luck.
// Table view
TableView tv = new TableView();
// Some more code goes here
// Select right most element
tv.getSelectionModel().selectRightCell();
The problem with selectRightCell() or selectLeftCell() function is that it selects just a single cell (to the left or right of the selected cell) not the all left or right cell of the selected cells.
Here is sample code below, you can select by dragging by mouse or by pressing control+arrow keys.
import com.sun.javafx.scene.control.behavior.TableCellBehavior;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.Arrays;
public class DragSelectionTable extends Application {
private TableView<Person> table = new TableView<Person>();
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")
);
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));
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
final Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory = new DragSelectionCellFactory();
firstNameCol.setCellFactory(cellFactory);
lastNameCol.setCellFactory(cellFactory);
emailCol.setCellFactory(cellFactory);
table.setEditable(true);
table.setItems(data);
table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.addEventHandler(KeyEvent.KEY_RELEASED, new ControlDownSelectionEventHandler());
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 class DragSelectionCellFactory implements Callback<TableColumn<Person, String>, TableCell<Person, String>> {
#Override
public TableCell<Person, String> call(final TableColumn<Person, String> col) {
return new DragSelectionCell();
}
}
public class DragSelectionCell extends TableCell<Person, String> {
public DragSelectionCell() {
setOnDragDetected(new DragDetectedEventHandler(this));
setOnMouseDragEntered(new DragEnteredEventHandler(this));
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item);
}
}
}
public class DragDetectedEventHandler implements EventHandler<MouseEvent> {
TableCell<Person, String> tableCell;
public DragDetectedEventHandler(TableCell<Person, String> tableCell) {
this.tableCell = tableCell;
}
#Override
public void handle(final MouseEvent event) {
tableCell.startFullDrag();
}
}
public class DragEnteredEventHandler implements EventHandler<MouseDragEvent> {
TableCell<Person, String> tableCell;
public DragEnteredEventHandler(TableCell<Person, String> tableCell) {
this.tableCell = tableCell;
}
#Override
public void handle(final MouseDragEvent event) {
performSelection(tableCell.getTableView(), tableCell.getTableColumn(), tableCell.getIndex());
}
}
public class ControlDownSelectionEventHandler implements EventHandler<KeyEvent> {
#Override
public void handle(final KeyEvent event) {
KeyCode code = event.getCode();
if (event.isShiftDown() && (KeyCode.UP.equals(code) || KeyCode.DOWN.equals(code) || KeyCode.LEFT.equals(code) || KeyCode.RIGHT.equals(code))) {
int index = table.getFocusModel().getFocusedCell().getRow();
TableColumn column = table.getFocusModel().getFocusedCell().getTableColumn();
performSelection(table, column, index);
}
}
}
protected void performSelection(TableView<Person> table, TableColumn<Person, String> column, int index) {
final TablePositionBase anchor = TableCellBehavior.getAnchor(table, table.getFocusModel().getFocusedCell());
int columnIndex = table.getVisibleLeafIndex(column);
int minRowIndex = Math.min(anchor.getRow(), index);
int maxRowIndex = Math.max(anchor.getRow(), index);
TableColumnBase minColumn = anchor.getColumn() < columnIndex ? anchor.getTableColumn() : column;
TableColumnBase maxColumn = anchor.getColumn() >= columnIndex ? anchor.getTableColumn() : column;
table.getSelectionModel().clearSelection();
final int minColumnIndex = table.getVisibleLeafIndex((TableColumn) minColumn);
final int maxColumnIndex = table.getVisibleLeafIndex((TableColumn) maxColumn);
for (int _row = minRowIndex; _row <= maxRowIndex; _row++) {
for (int _col = minColumnIndex; _col <= maxColumnIndex; _col++) {
table.getSelectionModel().select(_row, table.getVisibleLeafColumn(_col));
}
}
table.getFocusModel().focus(index, column);
}
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);
}
}
}

How do I make something happen on hover of a row in a JavaFX TableView?

Right now I have dialog show up at the mouse position when an row was selected in the tableview.
I'm looking to have the dialog show up when I hover over each row, there seems to be a CSS :hover so I assume it can be caught in java code in some capacity.
You can create a custom table row factory which adds a listener to the hover property of the row and takes action when the hover status changes.
Here is some sample code which updates a label as the user hovers over table rows:
table.setRowFactory(tableView -> {
final TableRow<Person> row = new TableRow<>();
row.hoverProperty().addListener((observable) -> {
final Person person = row.getItem();
if (row.isHover() && person != null) {
label.setText("Address Book: "
+ person.getFirstName() + " "
+ person.getLastName()
);
} else {
label.setText("Address Book");
}
});
return row;
});
In the sample image the mouse pointer (not shown) is hovered over the row for Emma Jones, so the title header is modified to read "Address Book: Emma Jones"
Here is a complete sample adopted from the Oracle JavaFX TableView tutorial sample code:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewSample extends Application {
private TableView<Person> table = new TableView<Person>();
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")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
table.setRowFactory(tableView -> {
final TableRow<Person> row = new TableRow<>();
row.hoverProperty().addListener((observable) -> {
final Person person = row.getItem();
if (row.isHover() && person != null) {
label.setText(
"Address Book: "
+ person.getFirstName() + " "
+ person.getLastName()
);
} else {
label.setText("Address Book");
}
});
return row;
});
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox(10);
vbox.setPadding(new Insets(10));
vbox.getChildren().addAll(label, table);
Scene scene = new Scene(vbox);
stage.setScene(scene);
stage.show();
}
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);
}
}
}

Categories