How can I compile a TreeTableView java7-compatible version? - java

I am looking into the Tree Table View user interface component since it seems right what I want to implement in my javaFX application.
Here is the sample oracle's code for the TreeTableView class:
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class TreeTableViewSample extends Application {
List<Employee> employees = Arrays.<Employee>asList(
new Employee("Ethan Williams", "ethan.williams#example.com"),
new Employee("Emma Jones", "emma.jones#example.com"),
new Employee("Michael Brown", "michael.brown#example.com"),
new Employee("Anna Black", "anna.black#example.com"),
new Employee("Rodger York", "roger.york#example.com"),
new Employee("Susan Collins", "susan.collins#example.com"));
private final ImageView depIcon = new ImageView (
new Image(getClass().getResourceAsStream("department.png"))
);
final TreeItem<Employee> root =
new TreeItem<>(new Employee("Sales Department", ""), depIcon);
public static void main(String[] args) {
Application.launch(TreeTableViewSample.class, args);
}
#Override
public void start(Stage stage) {
root.setExpanded(true);
employees.stream().forEach((employee) -> {
root.getChildren().add(new TreeItem<>(employee));
});
stage.setTitle("Tree Table View Sample");
final Scene scene = new Scene(new Group(), 400, 400);
scene.setFill(Color.LIGHTGRAY);
Group sceneRoot = (Group) scene.getRoot();
TreeTableColumn<Employee, String> empColumn =
new TreeTableColumn<>("Employee");
empColumn.setPrefWidth(150);
empColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Employee, String> param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getName())
);
TreeTableColumn<Employee, String> emailColumn =
new TreeTableColumn<>("Email");
emailColumn.setPrefWidth(190);
emailColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Employee, String> param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getEmail())
);
TreeTableView<Employee> treeTableView = new TreeTableView<>(root);
treeTableView.getColumns().setAll(empColumn, emailColumn);
sceneRoot.getChildren().add(treeTableView);
stage.setScene(scene);
stage.show();
}
public class Employee {
private SimpleStringProperty name;
private SimpleStringProperty email;
public SimpleStringProperty nameProperty() {
if (name == null) {
name = new SimpleStringProperty(this, "name");
}
return name;
}
public SimpleStringProperty emailProperty() {
if (email == null) {
email = new SimpleStringProperty(this, "email");
}
return email;
}
private Employee(String name, String email) {
this.name = new SimpleStringProperty(name);
this.email = new SimpleStringProperty(email);
}
public String getName() {
return name.get();
}
public void setName(String fName) {
name.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
}
The only issue here is that I am developing a java7-compatible version and lambda expressions such as the one below are only supported by java8:
emailColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Employee, String> param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getEmail())
);
How can I implement this class so that it is supported by JRE7?
I'm still new to lambda expressions as I've been working on java applications for JRE7 environments so I don't really know how to "convert" lambda-expressions to their "non-lambda" equivalent.

Related

How to detect double click on TextFieldTableCell in JavaFx?

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);
}
}
}

JavaFX - how to recognize the position of ScrollBar in a TableView

I have written this little example application:
package application;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
public Person(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty lastNameProperty() {
return lastName;
}
}
#Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
TableView<Person> tv = new TableView<>();
TableColumn<Person, String> col = new TableColumn<Person, String>("FirstName");
col.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
tv.getColumns().add(col);
tv.setEditable(true);
col = new TableColumn<Person, String>("LastName");
col.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
col.setCellFactory(TextFieldTableCell.forTableColumn());
col.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> event) {
System.out.println(tv.getItems().get(1).getLastName());
}
});
tv.getColumns().add(col);
for (int i = 0; i < 30; i++) {
tv.getItems().add(new Person("Test" + i, "Test" + i));
}
root.getChildren().add(tv);
Scene scene = new Scene(root, 400, 200);
primaryStage.setScene(scene);
primaryStage.show();
tv.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
// ...
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I want to perform action when the ScrollBar has reached the bottom. Then I want to reload more datas from the database. But only then, when the user has seen all the already loaded datas (= scrollbar on the bottom). Do you have nice suggestions to solve this issue?
My first idea was to catch the MOUSE_RELEASED event (when the users drags the bar) of the TableView and then to check the position of the ScrollBar:
- getValue() gets the position of the bar
- getMax() the maximum value (=bottom).
But I can't find a way (without using the css-selector via this method) to get the ScrollBar from a given TableView. So I can't check the position of it in a certain TableView.
Do you have any ideas??
I am excited. Thanks for your help.
The only way to get the scroll bar is via a lookup, which is a bit of a hack, but it will work as long as you do it after the table has been rendered on the scene. You need
ScrollBar verticalBar = (ScrollBar) table.lookup(".scroll-bar:vertical");
Note that there's no need to mess with user events: you can just observe the scroll bar's value property directly:
verticalBar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue.doubleValue() >= verticalBar.getMax()) {
// add more data...
}
});
SSCCE:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class AddMoreTableDataOnScrollToBottom extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
addMoreData(table, 20);
Scene scene = new Scene(new BorderPane(table), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
ScrollBar verticalBar = (ScrollBar) table.lookup(".scroll-bar:vertical");
verticalBar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue.doubleValue() >= verticalBar.getMax()) {
addMoreData(table, 20);
}
});
}
private void addMoreData(TableView<Item> table, int numItems) {
Task<List<Item>> dataRetrieveTask = new Task<List<Item>>() {
#Override
public List<Item> call() throws Exception {
// mimic connect to db:
Thread.sleep(500);
List<Item> items = new ArrayList<>();
int nextItem = table.getItems().size() + 1 ;
for (int i = nextItem; i < nextItem + numItems; i++ ){
items.add(new Item("Item "+i, i));
}
return items ;
}
};
dataRetrieveTask.setOnSucceeded(e -> table.getItems().addAll(dataRetrieveTask.getValue()));
new Thread(dataRetrieveTask).start();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}

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 ChangeListener Conflict

I have two TableView (table1 and table2) one next to the other
What I need to do is:
When you select an item in table1 the corresponding item is selected in table2
So far so good was easy, but I need to reproduce the same effect in table2, and it is when
arises the NPE the listener applied in table1 conflict with the listener of table2.
I tried to create an event in focusedProperty () but without success :(
I made a test application to post here, as it would not fit all code follows
download link TableView - Teste.
This feels like it might be a bug, but I don't have time to experiment with it properly. A workaround seems to be to update the selection in the "other" table inside a Platform.runLater(). You need to be careful not to create an infinite number of these calls by checking the selection really is different:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class TwoTableTest extends Application {
private ChangeListener<Number> table1SelectionListener ;
private ChangeListener<Number> table2SelectionListener ;
#Override
public void start(Stage primaryStage) {
TableView<Person> table1 = createTableView() ;
TableView<Person> table2 = createTableView() ;
table1.getSelectionModel().select(0);
table2.getSelectionModel().select(0);
table1SelectionListener = (obs, oldIndex, newIndex) -> {
int table1SelectedIndex = table1.getSelectionModel().getSelectedIndex() ;
int table2SelectedIndex = table2.getSelectionModel().getSelectedIndex() ;
if (table1SelectedIndex != table2SelectedIndex) {
Platform.runLater(() -> table2.getSelectionModel().select(table1SelectedIndex));
}
};
table2SelectionListener = (obs, oldIndex, newIndex) -> {
int table1SelectedIndex = table1.getSelectionModel().getSelectedIndex() ;
int table2SelectedIndex = table2.getSelectionModel().getSelectedIndex() ;
if (table1SelectedIndex != table2SelectedIndex) {
Platform.runLater(() -> table1.getSelectionModel().select(table2SelectedIndex));
}
};
table1.getSelectionModel().selectedIndexProperty().addListener(table1SelectionListener);
table2.getSelectionModel().selectedIndexProperty().addListener(table2SelectionListener);
HBox root = new HBox(5, table1, table2);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private TableView<Person> createTableView() {
TableView<Person> table = new TableView<>();
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(data -> data.getValue().firstNameProperty());
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(data -> data.getValue().lastNameProperty());
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setCellValueFactory(data -> data.getValue().emailProperty());
table.getColumns().addAll(firstNameCol, lastNameCol);
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")
);
return table ;
}
public static void main(String[] args) {
launch(args);
}
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty email ;
Person(String firstName, String lastName, String email) {
this.firstName = new SimpleStringProperty(this, "firstName",
firstName);
this.lastName = new SimpleStringProperty(this, "lastName", lastName);
this.email = new SimpleStringProperty(this, "email", email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getEmail() {
return email.get();
}
public void setEmail(String email) {
this.email.set(email);
}
public StringProperty emailProperty() {
return email ;
}
#Override
public String toString() {
return firstName.get() + " " + lastName.get();
}
}
}

javafx tableview updates data only after sorting

I have two javafx tableviews. If I change a value in the first one, the second one shows the changes only after sorting (although the model is updated).
My entities
package Entity;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
public class Arbeitgeber {
private SimpleIntegerProperty id = new SimpleIntegerProperty();
private SimpleStringProperty name = new SimpleStringProperty();
public int getId() {
return id.get();
}
public void setId(int id) {
this.id.set(id);
}
...
package Entity;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private SimpleIntegerProperty id = new SimpleIntegerProperty();
private SimpleStringProperty name = new SimpleStringProperty();
private SimpleStringProperty gruppe = new SimpleStringProperty();
private ObjectProperty<Arbeitgeber> arbeitgeber = new SimpleObjectProperty<>();
public int getId() {
return id.get();
}
public void setId(int id) {
this.id.set(id);
}
...
My testcase :
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import Entity.Arbeitgeber;
import Entity.Person;
public class TableviewTest extends Application {
private TableView<Arbeitgeber> arbeitgeberTable;
private TableView<Person> personTable;
ObservableList<String> gruppeOptions =
FXCollections.observableArrayList(
"Arbeiter",
"Angestellter",
"Leiter"
);
private ObservableList<Arbeitgeber> arbeitgeberListe = FXCollections.observableArrayList(new Arbeitgeber(1, "Goldgräber Inc."), new Arbeitgeber(2, "Hamster GmbH"), new Arbeitgeber(3, "Wissen"));
private ObservableList<Person> personen = FXCollections.observableArrayList(
new Person(1, "Max Musterman", gruppeOptions.get(1), arbeitgeberListe.get(0)),
new Person(2, "Bernd Brot", gruppeOptions.get(0), arbeitgeberListe.get(0)),
new Person(3, "Fred Feuer", gruppeOptions.get(2), arbeitgeberListe.get(1)),
new Person(4, "Uli Stein", gruppeOptions.get(1), arbeitgeberListe.get(2)));
#Override
public void start(Stage primaryStage) throws Exception {
createArbeitgeberTableview();
createPersonTableview();
VBox vbox = new VBox(15);
Label aLabel = new Label("Arbeitgeber");
Label pLabel = new Label("Personen");
vbox.getChildren().addAll(aLabel, arbeitgeberTable, pLabel, personTable);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args
*/
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private TableView<Arbeitgeber> createArbeitgeberTableview() {
arbeitgeberTable = new TableView<>(arbeitgeberListe);
arbeitgeberTable.setEditable(true);
TableColumn<Arbeitgeber, Integer> idCol = new TableColumn<>("ID");
TableColumn nameCol = new TableColumn<>("Name");
idCol.setCellValueFactory(new PropertyValueFactory<Arbeitgeber, Integer>("id"));
nameCol.setCellValueFactory(new PropertyValueFactory<Arbeitgeber, String>("name"));
nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
nameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Arbeitgeber,String>>() {
#Override
public void handle(CellEditEvent<Arbeitgeber, String> event) {
((Arbeitgeber) event.getTableView().getItems().get(event.getTablePosition().getRow())).setName(event.getNewValue());
for(Person p : personTable.getItems()) {
System.out.println(p.getArbeitgeber()); //shows the new value
}
}
});
arbeitgeberTable.getColumns().addAll(idCol, nameCol);
arbeitgeberTable.setTableMenuButtonVisible(true);
return arbeitgeberTable;
}
#SuppressWarnings("unchecked")
private TableView<Person> createPersonTableview(){
personTable = new TableView<>(personen);
TableColumn<Person, Integer> idCol = new TableColumn<>("ID");
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
TableColumn<Person, String> gruppeCol = new TableColumn<>("Gruppe");
TableColumn<Person, Arbeitgeber> arbCol = new TableColumn<>("Arbeitgeber");
idCol.setCellValueFactory(new PropertyValueFactory<Person, Integer>("id"));
nameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
gruppeCol.setCellValueFactory(new PropertyValueFactory<Person, String>("gruppe"));
arbCol.setCellValueFactory(new PropertyValueFactory<Person, Arbeitgeber>("arbeitgeber"));
personTable.getColumns().addAll(idCol, nameCol, gruppeCol, arbCol);
personTable.setTableMenuButtonVisible(true);
return personTable;
}
}
I tried something like :
for(TableColumn t : personTable.getColumns()){
t.setVisible(false);
t.setVisible(true);
}
but it does not work. I would like to solve it without sorting. Does anyone have an idea?
Thanks.
Problem Restatement
As I understand it you have the following situation.
A person has a reference to an arbeitgeber
You display the arbeitgeber in one table and the person in another table.
The person table has an Arbeitgeber column which provides information on the related arbeitgeber.
In the arbeitgeber, the name of the arbeitgeber is editable.
When you edit arbeitgeber name, in the arbeitgeber table, you want the name update for the arbeitgeber to be automatically reflected in your person table.
Solution Approach
Ensure that entity objects have property access methods so that the values of the individual entity fields can be observed for changes and appropriate action taken.
public StringProperty nameProperty() {
return name;
}
Set up a binding between the cell values of the Arbeitgeber column in the person table and the arbeitgeber name.
In Java 7:
arbCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>() {
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> p) {
return Bindings.selectString(
new ReadOnlyObjectWrapper<Object>(
p.getValue()
),
"arbeitgeber", "name"
);
}
});
In Java 8:
arbCol.setCellValueFactory(p ->
Bindings.selectString(p.getValue(), "arbeitgeber", "name")
);
Sample Output
Before and after edit snapshots:
Sample code
TableViewTest.java
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableViewTest extends Application {
private TableView<Arbeitgeber> arbeitgeberTable;
private TableView<Person> personTable;
ObservableList<String> gruppeOptions =
FXCollections.observableArrayList(
"Arbeiter",
"Angestellter",
"Leiter"
);
private ObservableList<Arbeitgeber> arbeitgeberListe = FXCollections.observableArrayList(new Arbeitgeber(1, "Goldgräber Inc."), new Arbeitgeber(2, "Hamster GmbH"), new Arbeitgeber(3, "Wissen"));
private ObservableList<Person> personen = FXCollections.observableArrayList(
new Person(1, "Max Musterman", gruppeOptions.get(1), arbeitgeberListe.get(0)),
new Person(2, "Bernd Brot", gruppeOptions.get(0), arbeitgeberListe.get(0)),
new Person(3, "Fred Feuer", gruppeOptions.get(2), arbeitgeberListe.get(1)),
new Person(4, "Uli Stein", gruppeOptions.get(1), arbeitgeberListe.get(2)));
#Override
public void start(Stage primaryStage) throws Exception {
createArbeitgeberTableview();
createPersonTableview();
VBox vbox = new VBox(15);
Label aLabel = new Label("Arbeitgeber");
Label pLabel = new Label("Personen");
vbox.getChildren().addAll(aLabel, arbeitgeberTable, pLabel, personTable);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args
*/
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private TableView<Arbeitgeber> createArbeitgeberTableview() {
arbeitgeberTable = new TableView<>(arbeitgeberListe);
arbeitgeberTable.setEditable(true);
TableColumn<Arbeitgeber, Integer> idCol = new TableColumn<>("ID");
TableColumn nameCol = new TableColumn<>("Name");
idCol.setCellValueFactory(new PropertyValueFactory<Arbeitgeber, Integer>("id"));
nameCol.setCellValueFactory(new PropertyValueFactory<Arbeitgeber, String>("name"));
nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
nameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Arbeitgeber,String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Arbeitgeber, String> event) {
((Arbeitgeber) event.getTableView().getItems().get(event.getTablePosition().getRow())).setName(event.getNewValue());
for(Person p : personTable.getItems()) {
System.out.println(p.getArbeitgeber()); //shows the new value
}
}
});
arbeitgeberTable.getColumns().addAll(idCol, nameCol);
arbeitgeberTable.setTableMenuButtonVisible(true);
return arbeitgeberTable;
}
#SuppressWarnings("unchecked")
private TableView<Person> createPersonTableview(){
personTable = new TableView<>(personen);
TableColumn<Person, Integer> idCol = new TableColumn<>("ID");
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
TableColumn<Person, String> gruppeCol = new TableColumn<>("Gruppe");
TableColumn<Person, String> arbCol = new TableColumn<>("Arbeitgeber");
idCol.setCellValueFactory(new PropertyValueFactory<Person, Integer>("id"));
nameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
gruppeCol.setCellValueFactory(new PropertyValueFactory<Person, String>("gruppe"));
arbCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>() {
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> p) {
return Bindings.selectString(
new ReadOnlyObjectWrapper<Object>(
p.getValue()
),
"arbeitgeber", "name"
);
}
});
personTable.getColumns().addAll(idCol, nameCol, gruppeCol, arbCol);
personTable.setTableMenuButtonVisible(true);
return personTable;
}
}
Person.java
import javafx.beans.property.*;
public class Person {
private IntegerProperty id = new SimpleIntegerProperty();
private StringProperty name = new SimpleStringProperty();
private StringProperty gruppe = new SimpleStringProperty();
private ObjectProperty<Arbeitgeber> arbeitgeber = new SimpleObjectProperty<>();
public Person(int id, String name, String gruppe, Arbeitgeber arbeitgeber) {
setId(id);
setName(name);
setGruppe(gruppe);
setArbeitgeber(arbeitgeber);
}
public int getId() {
return id.get();
}
public IntegerProperty idProperty() {
return id;
}
public void setId(int id) {
this.id.set(id);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getGruppe() {
return gruppe.get();
}
public StringProperty gruppeProperty() {
return gruppe;
}
public void setGruppe(String gruppe) {
this.gruppe.set(gruppe);
}
public Arbeitgeber getArbeitgeber() {
return arbeitgeber.get();
}
public ObjectProperty<Arbeitgeber> arbeitgeberProperty() {
return arbeitgeber;
}
public void setArbeitgeber(Arbeitgeber arbeitgeber) {
this.arbeitgeber.set(arbeitgeber);
}
#Override
public String toString() {
return "Person{" +
"id=" + id +
", name=" + name +
", gruppe=" + gruppe +
", arbeitgeber=" + arbeitgeber +
'}';
}
}
Arbeitgeber.java
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Arbeitgeber {
private IntegerProperty id = new SimpleIntegerProperty();
private StringProperty name = new SimpleStringProperty();
public Arbeitgeber(int id, String name) {
setId(id);
setName(name);
}
public IntegerProperty idProperty() {
return id;
}
public int getId() {
return id.get();
}
public void setId(int id) {
this.id.set(id);
}
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
#Override
public String toString() {
return "Arbeitgeber{" +
"id=" + id +
", name=" + name +
'}';
}
}

Categories