JavaFx property binding with multiple objects on on screen - javafx

I use JavaFx with property binding.
I got a object 'Person' with the properties 'name' and age.
These objects are stored in a ObservableList.
The properties are bound to labels on the gui. When I change the person in the ListBox the data also change on the right hand side.
GUI with person list:
And now it comes to my problem.
I want to disply all persons on one window, like the next picture shows.
GUI with multiple persons on one view:
How can I handle this. I thought about HBox but the binding doesn't work.
FYI: Here you can find the tutorial I used.
https://code.makery.ch/library/javafx-tutorial/part1/

This looks like a perfect time to use a ListView with custom ListCell implementations.
The sample application below shows a very basic application that displays each Person object in a ListView. We will provide our own ListCell so we can control exactly how each Person gets displayed.
I also added a profile photo just for fun :)
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Separator;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewDetailSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// First, let's create our list of Persons
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.addAll(
new Person("John", 34),
new Person("Cheyenne", 24),
new Person("Micah", 17),
new Person("Katelyn", 28)
);
// Create a ListView
ListView<Person> listView = new ListView<>();
// Bind our list to the ListView
listView.setItems(persons);
// Now, for the magic. We'll create our own ListCells for the ListView. This allows us to create a custom
// layout for each individual cell. For this sample, we'll include a profile picture, the name, and the age.
listView.setCellFactory(new Callback<ListView<Person>, ListCell<Person>>() {
#Override
public ListCell<Person> call(ListView<Person> param) {
return new ListCell<Person>() {
#Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
// Set any empty cells to show nothing
if (person == null || empty) {
setText(null);
setGraphic(null);
} else {
// Here we can build our layout. We'll use a HBox for our root container
HBox cellRoot = new HBox(5);
cellRoot.setAlignment(Pos.CENTER_LEFT);
cellRoot.setPadding(new Insets(5));
// Add our profile picture
ImageView imgProfilePic = new ImageView("/sample/user.png");
imgProfilePic.setFitHeight(24);
imgProfilePic.setFitWidth(24);
cellRoot.getChildren().add(imgProfilePic);
// A simple Separator between the photo and the details
cellRoot.getChildren().add(new Separator(Orientation.VERTICAL));
// Now, create a VBox to hold the name and age
VBox vBox = new VBox(5);
vBox.setAlignment(Pos.CENTER_LEFT);
vBox.setPadding(new Insets(5));
// Add our Person details
vBox.getChildren().addAll(
new Label("Name: " + person.getName()),
new Label("Age: " + person.getAge())
);
// Add our VBox to the cellRoot
cellRoot.getChildren().add(vBox);
// Finally, set this cell to display our custom layout
setGraphic(cellRoot);
}
}
};
}
});
// Now, add our ListView to the root layout
root.getChildren().add(listView);
// Show the Stage
primaryStage.setWidth(450);
primaryStage.setHeight(400);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
// Simple Person class
class Person {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty age = new SimpleIntegerProperty();
public Person(String name, int age) {
this.name.set(name);
this.age.set(age);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public int getAge() {
return age.get();
}
public IntegerProperty ageProperty() {
return age;
}
public void setAge(int age) {
this.age.set(age);
}
}
The Result:
Without ListView:
If you'd prefer not to use a ListView for this display, you can keep another list of your Person displays and bind that to the children list of whichever container you want:
// Create a list to hold our individual Person displays
ObservableList<Node> personDisplays = FXCollections.observableArrayList();
// Now add a new PersonDisplay to the list for each Person in the personsList
persons.forEach(person -> personDisplays.add(new PersonDisplay(person)));
// Bind our personsDisplay list to the children of our root VBox
Bindings.bindContent(root.getChildren(), personDisplays);
PersonDisplay class:
class PersonDisplay extends HBox {
public PersonDisplay(Person person) {
// First, let's configure our root layout
setSpacing(5);
setAlignment(Pos.CENTER_LEFT);
setPadding(new Insets(5));
// Add our profile picture
ImageView imgProfilePic = new ImageView("/user.png");
imgProfilePic.setFitHeight(24);
imgProfilePic.setFitWidth(24);
getChildren().add(imgProfilePic);
// A simple Separator between the photo and the details
getChildren().add(new Separator(Orientation.VERTICAL));
// Now, create a VBox to hold the name and age
VBox vBox = new VBox(5);
vBox.setAlignment(Pos.CENTER_LEFT);
vBox.setPadding(new Insets(5));
// Add our Person details
vBox.getChildren().addAll(
new Label("Name: " + person.getName()),
new Label("Age: " + person.getAge())
);
// Add our VBox to the layout
getChildren().add(vBox);
}
}
The Result:

Related

AutoComplete ComboBox with ControlsFX triggers auto completion when selecting value with mouse

I have a simple ComboBox that I've used ControlsFX to make auto-completing. This works perfectly except for one annoying flaw: When the user uses the dropdown to select an item with the mouse, the autocomplete window opens up, essentially offering matching suggestions to the user.
The desired behavior is for the selection of a value with the mouse to close the popup altogether.
The code below will demonstrate:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.controlsfx.control.textfield.TextFields;
public class AutoCompleteComboBox extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Create a standard ComboBox
ComboBox<Person> comboBox = new ComboBox<>();
// Sample items
ObservableList<Person> people = FXCollections.observableArrayList();
people.addAll(
new Person("William", 34),
new Person("Ashley", 12),
new Person("Marvin", 63),
new Person("Suresh", 18),
new Person("Jackson", 24)
);
comboBox.setItems(people);
// Add StringConverter
comboBox.setConverter(new StringConverter<Person>() {
#Override
public String toString(Person person) {
return (person == null ? null : person.getName());
}
#Override
public Person fromString(String string) {
for (Person person : comboBox.getItems()) {
if (person.getName().equalsIgnoreCase(string)) {
return person;
}
}
return null;
}
});
// Make the ComboBox autocomplete using ControlsFX
comboBox.setEditable(true);
TextFields.bindAutoCompletion(comboBox.getEditor(), comboBox.getItems());
root.getChildren().add(comboBox);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
}
class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
#Override
public String toString() {
return name;
}
}
So typing into the editor and selecting a value with the keyboard works fine. But if you click the arrow to open the dropdown and select a value that way, you can see the issue (it essentially forces the user to select a value twice).
How would I go about preventing this behavior?

Why my filed component is not removing dynamically in javafx

I want to add textfield and combobox dynamically on button click but not able to do it. Below is the code which I tried. In this code I have added the field successfully but not able to remove the item one by one on button click. only last item is removing after adding the field multiple times. so please check my code where I did mistake.
package application;
import java.awt.Color;
import java.net.URL;
import java.util.ResourceBundle;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXTextField;
import com.sun.javafx.scene.layout.region.Margins;
import javafx.event.ActionEvent;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class CustomerController implements Initializable {
public JFXTextField
acc_no,ifsc_code,micr_code,acc_no1,ifsc_code1,micr_code1;
public JFXComboBox<String> bank_name,bank_name1;
public JFXButton add_row,rmv_row;
public GridPane grid_component;
public VBox vBox2_component, vbox1_component;
public AnchorPane anchor_pane;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
bank_name.getItems().removeAll(bank_name.getItems());
bank_name.getItems().addAll("Bank of India", "Dena Bank", "HDFC Bank");
new AutoCompleteComboBoxListener(bank_name);
}
// add button functionality
public void AddBankDetails(ActionEvent event) {
/*if(count == max_row-1){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Maximum of 10 rows can be added\",\"Failed!!");
alert.showAndWait();
return;
}
count++;*/
bank_name1 = new JFXComboBox();
bank_name1.getStyleClass().add("textfield_margin");
bank_name1.setLabelFloat(true);
bank_name1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
bank_name1.setPromptText("Bank Name");
bank_name1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vbox1_component.setMargin(bank_name1, new Insets(20, 10, 10, 10));
ifsc_code1 = new JFXTextField();
ifsc_code1.getStyleClass().add("textfield_margin");
ifsc_code1.setLabelFloat(true);
ifsc_code1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
ifsc_code1.setPromptText("IFSC Code");
ifsc_code1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vbox1_component.setMargin(ifsc_code1, new Insets(20, 10, 10, 10));
acc_no1 = new JFXTextField();
acc_no1.getStyleClass().add("textfield_margin");
acc_no1.setLabelFloat(true);
acc_no1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
acc_no1.setPromptText("Account number");
acc_no1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vBox2_component.setMargin(acc_no1, new Insets(20, 10, 10, 10));
micr_code1 = new JFXTextField();
micr_code1.getStyleClass().add("textfield_margin");
micr_code1.setLabelFloat(true);
micr_code1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
micr_code1.setPromptText("MICR Code");
micr_code1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vBox2_component.setMargin(micr_code1, new Insets(20, 10, 10, 10));
vbox1_component.getChildren().addAll(bank_name1,ifsc_code1);
vBox2_component.getChildren().addAll(acc_no1,micr_code1);
}
//remove button functionality
public void rmvBankDetails(ActionEvent events) {
vbox1_component.getChildren().removeAll(bank_name1,ifsc_code1);
vBox2_component.getChildren().removeAll(acc_no1,micr_code1);
}
}
You only store a reference to the controls that are created for the last call of AddBankDetails. This means if you click the add button multiple times and then click the remove button multiple times. You only get a single successful remove operation for the VBoxes.
Assuming this is the only code modifying the VBoxes and the VBoxes don't contain other children, you could simply remove the last 2 children from each list:
private static void removeElements(List<?> list, int count) {
int size = list.size();
if (size >= count) {
list.subList(size - count, size).clear();
}
}
public void rmvBankDetails(ActionEvent events) {
removeElements(vbox1_component.getChildren(), 2);
removeElements(vBox2_component.getChildren(), 2);
}
Additional notes:
Depending on your layout using GridPane could be a option. TableView may also be worth a look.
You probably need access to all your controls, so keeping a list of the newly added controls in a list could be desireable. Using this data you could also retrieve the controls to remove.
private static class Container {
final JFXComboBox bank_name1;
final JFXTextField ifsc_code1;
final JFXTextField acc_no1;
final JFXTextField micr_code1;
Container(JFXComboBox bank_name1, JFXTextField ifsc_code1, JFXTextField acc_no1, JFXTextField micr_code1) {
this.bank_name1 = bank_name1;
this.ifsc_code1 = ifsc_code1;
this.acc_no1 = acc_no1;
this.micr_code1 = micr_code1;
}
}
private final List<Container> bankDetailsContainers = new ArrayList<>();
public void AddBankDetails(ActionEvent event) {
...
bankDetailsContainers.add(new Container(bank_name1, ifsc_code1, acc_no1, micr_code1));
}
public void rmvBankDetails(ActionEvent events) {
if (!bankDetailsContainers.isEmpty()) {
Container c = bankDetailsContainers.remove(bankDetailsContainers.size() - 1);
vbox1_component.getChildren().removeAll(c.bank_name1, c.ifsc_code1);
vBox2_component.getChildren().removeAll(c.acc_no1, c.micr_code1);
}
}

JavaFX Dynamic Form Field UI

Does anyone know how to imitate the functionality from the UI components shown below? I want to replicate adding form fields when text is entered into the TextField box. I don't need the dropdown button, just the dynamic adding of the forms.
You could modify the children of a GridPane adding a new TextField & Button every time one of the buttons is activated. Listen to the text properties to enable/disable the Button and save the results.
private static void insertRow(GridPane grid, List<String> values, int index) {
// increment index of children with rowIndex >= index
for (Node n : grid.getChildren()) {
int row = GridPane.getRowIndex(n);
if (row >= index) {
GridPane.setRowIndex(n, row + 1);
}
}
TextField text = new TextField();
Button add = new Button("+");
add.setDisable(true);
add.setOnAction(evt -> {
insertRow(grid, values, GridPane.getRowIndex(add) + 1);
});
values.add(index, "");
text.textProperty().addListener((a, oldValue, newValue) -> {
add.setDisable(newValue.isEmpty());
values.set(GridPane.getRowIndex(add), newValue);
});
grid.addRow(index, text, add);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
List<String> list = new ArrayList<>();
insertRow(grid, list, 0);
Button print = new Button("print");
print.setOnAction(evt -> {
System.out.println(list);
});
grid.add(print, 0, 1);
Scene scene = new Scene(grid, 300, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
This may not be exactly what you're looking for and may not be the best way to do this, but should be easy to adapt it to your needs.
Basically, you will need a list of HBox objects to be added to a VBox in your application. You could create the list yourself and bind it to the children of your VBox, or just add/remove the HBoxes to/from the VBox using the getChildren().add() and getChildren().remove() methods.
Here is a complete little application to demonstrate the concept. I created an internal class to handle the HBox with the fields you need. This could be adapted to be more felixable:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private static VBox mainPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
mainPane = new VBox(5);
mainPane.setPadding(new Insets(10));
mainPane.setAlignment(Pos.TOP_CENTER);
mainPane.getChildren().add(new UIForms());
primaryStage.setScene(new Scene(mainPane));
primaryStage.show();
}
static void addField() {
mainPane.getChildren().add(new UIForms());
}
static void removeField(UIForms field) {
if (mainPane.getChildren().size() > 1) {
mainPane.getChildren().remove(field);
}
}
}
class UIForms extends HBox {
private TextField textField1;
private TextField textField2;
private Button btnAddField;
private Button btnRemoveField;
public UIForms() {
// Setup the HBox layout
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
// Create the UI controls
textField1 = new TextField();
textField2 = new TextField();
btnAddField = new Button("+");
btnRemoveField = new Button("-");
// Setup button actions
btnAddField.setOnAction(e -> Main.addField());
btnRemoveField.setOnAction(e -> Main.removeField(this));
// Add the UI controls
getChildren().addAll(
textField1, textField2, btnAddField, btnRemoveField
);
}
}

JavaFx Bindings : is there any way to bind value to observable List?

i have table view it's contents is observable list that contains numbers and i have a text field that should display the sum of these values in the table is there any way to bind this text fields to sum of the number properties .
note : the user may edit the values in this list , may add more elements , may delete some element how can i bind the sum of these numbers correctly using javafx binding instead of doing this by the old fashion way iterate over the list and sum the numbers manually and every change reiterate over it again .
An ObservableList will fire update events if (and only if) you create the list with an extractor. The extractor is a function that maps each element of the list to an array of Observables; if any of those Observables change their value, the list fires the appropriate update events and becomes invalid.
So the two steps here are:
Create the list with an extractor
Create a binding that computes the total whenever the list is invalidated.
So if you have a model class for your table such as:
public class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get();
}
public final void setValue(int value) {
valueProperty().set(value);
}
// other properties, etc...
}
Then you create the table with:
TableView<Item> table = new TableView<>();
table.setItems(FXCollections.observableArrayList(item ->
new Observable[] { item.valueProperty() }));
Now you can create the binding with
IntegerBinding total = Bindings.createIntegerBinding(() ->
table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
table.getItems());
Implementation note: the two arguments to createIntegerBinding above are a function that computes the int value, and any values to observe. If any of the observed values (here there is just one, table.getItems()) is invalidated, then the value is recomputed. Remember we created table.getItems() so it would be invalidated if any of the item's valueProperty()s changed. The function that is the first argument uses a lambda expression and the Java 8 Streams API, it is roughly equivalent to
() -> {
int totalValue = 0 ;
for (Item item : table.getItems()) {
totalValue = totalValue + item.getValue();
}
return totalValue ;
}
Finally, if you want a label to display the total, you can do something like
Label totalLabel = new Label();
totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
Here is an SSCCE:
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
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.collections.FXCollections;
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.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
import javafx.util.converter.NumberStringConverter;
public class TotallingTableView extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(
column("Item", Item::nameProperty, new DefaultStringConverter()));
table.getColumns().add(
column("Value", Item::valueProperty, new NumberStringConverter()));
table.setItems(FXCollections.observableArrayList(
item -> new Observable[] {item.valueProperty() }));
IntStream.rangeClosed(1, 20)
.mapToObj(i -> new Item("Item "+i, i))
.forEach(table.getItems()::add);
IntegerBinding total = Bindings.createIntegerBinding(() ->
table.getItems().stream().collect(Collectors.summingInt(Item::getValue)),
table.getItems());
Label totalLabel = new Label();
totalLabel.textProperty().bind(Bindings.format("Total: %d", total));
Button add = new Button("Add item");
add.setOnAction(e ->
table.getItems().add(new Item("New Item", table.getItems().size() + 1)));
Button remove = new Button("Remove");
remove.disableProperty().bind(
Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));
remove.setOnAction(e ->
table.getItems().remove(table.getSelectionModel().getSelectedItem()));
HBox buttons = new HBox(5, add, remove);
buttons.setAlignment(Pos.CENTER);
VBox controls = new VBox(5, totalLabel, buttons);
VBox.setVgrow(totalLabel, Priority.ALWAYS);
totalLabel.setMaxWidth(Double.MAX_VALUE);
totalLabel.setAlignment(Pos.CENTER_RIGHT);
BorderPane root = new BorderPane(table, null, null, controls, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title,
Function<S, ObservableValue<T>> property, StringConverter<T> converter) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(TextFieldTableCell.forTableColumn(converter));
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 java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.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);
}
}
Note that this is not the most efficient possible implementation, but the one that (IMHO) keeps the code the cleanest. If you have a very large number of items in the table, recomputing the total from scratch by iterating through and summing them all might be prohibitively expensive. The alternative approach is to listen for add/remove changes to the list. When an item is added, add its value to the total, and register a listener with the value property that updates the total if the value changes. When an item is removed from the list, remove the listener from the value property and subtract the value from the total. This avoids continually recomputing from scratch, but the code is harder to decipher.
I would just add a listener to the ObservableList and have it update the label any time there is a change.
list.addListener((obs, oldValue, newValue) -> {
textfield.setText(list.stream().mapToInt(x -> x.intValue()).sum();
});
Something like that should work

Expand table row on mouse click

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

Resources