JavaFX: TableView Print Selected Row item - javafx

I currently have a tableview that displays a bunch of info on player stored on a database. What I'm trying to do, is simply print the first name of the name to console when its row is selected. Nothing is getting printed. I will post the entire code from table view to where I'm trying to print. I'm leaving out the function that loads players to the tableview as its unrelated
//Start Right Menu
TableView<TableDisplay> table = new TableView<>();
final ObservableList<TableDisplay> data =
FXCollections.observableArrayList();
TableColumn column1 = new TableColumn("Id");
column1.setMinWidth(100);
column1.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn column2 = new TableColumn("First Name");
column2.setMinWidth(100);
column2.setCellValueFactory(new PropertyValueFactory<>("firstName"));
TableColumn column3 = new TableColumn("Last Name");
column3.setMinWidth(100);
column3.setCellValueFactory(new PropertyValueFactory<>("lastName"));
TableColumn column4 = new TableColumn("Birthdate");
column4.setMinWidth(100);
column4.setCellValueFactory(new PropertyValueFactory<>("birthdate"));
TableColumn column5 = new TableColumn("Nationality");
column5.setMinWidth(100);
column5.setCellValueFactory(new PropertyValueFactory<>("nationality"));
TableColumn column6 = new TableColumn("Height");
column6.setMinWidth(100);
column6.setCellValueFactory(new PropertyValueFactory<>("height"));
TableColumn column7 = new TableColumn("Position");
column7.setMinWidth(100);
column7.setCellValueFactory(new PropertyValueFactory<>("Position"));
TableColumn column8 = new TableColumn("Foot");
column8.setMinWidth(100);
column8.setCellValueFactory(new PropertyValueFactory<>("foot"));
TableColumn column9 = new TableColumn("Team Id");
column9.setMinWidth(100);
column9.setCellValueFactory(new PropertyValueFactory<>("teamId"));
table.getColumns().addAll(column1, column2, column3, column4, column5, column6, column7, column8, column9);
rightEditMenu.getChildren().addAll(table);
//End Right Menu
//Start Left Menu 2
This is where I'm trying to print but not working
TableDisplay person = table.getSelectionModel().getSelectedItem();
if(table.getSelectionModel().getSelectedItem() != null) {
System.out.println(person.getFirstName());
}

As I do not see a listener of any type in your code, I am assuming you do not have one.
It seems like you are trying to print the value of a selection before the Scene has even been loaded, which means no selection has been made by the user yet.
So add the following code when setting up your TableView:
// Add a listener to print the selected item to console when selected
table.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue != null) {
System.out.println("Selected Person: "
+ newValue.getId() + " | "
+ newValue.getFirstName() + " " + newValue.getLastName()
);
}
});
Now whenever a row is selected, this Listener is triggered and prints the values from newValue which represents the object stored in the selected row.
Since you did not provide an MCVE of your own, see the below example. You also do not declare what type of class your TableView and TableColumn objects are meant to display; this is a poor design and should be updated as in the below example.
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.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple UI
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Setup the TableView and columns
TableView<Person> table = new TableView<>();
TableColumn<Person, Integer> colId = new TableColumn<>("ID");
colId.setMinWidth(100);
colId.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<Person, String> colFirstName = new TableColumn<>("First Name");
colFirstName.setMinWidth(100);
colFirstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> colLastName = new TableColumn<>("Last Name");
colLastName.setMinWidth(100);
colLastName.setCellValueFactory(new PropertyValueFactory<>("lastName"));
// Add the columns to the TableView
table.getColumns().addAll(colId, colFirstName, colLastName);
// Add a listener to print the selected item to console when selected
table.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue != null) {
System.out.println("Selected Person: "
+ newValue.getId() + " | "
+ newValue.getFirstName() + " " + newValue.getLastName()
);
}
});
// Sample Data
ObservableList<Person> people = FXCollections.observableArrayList();
people.addAll(
new Person(1, "John", "Smith"),
new Person(2, "William", "Scott"),
new Person(4, "Susan", "Ryder")
);
table.setItems(people);
root.getChildren().add(table);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.show();
}
public static class Person {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
Person(int id, String firstName, String lastName) {
this.id.set(id);
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public int getId() {
return id.get();
}
public IntegerProperty idProperty() {
return id;
}
public String getFirstName() {
return firstName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public StringProperty lastNameProperty() {
return lastName;
}
}
}

Related

Javafx Tableview with InputStreamReader removes row color marker

i can't get any further. I have a standart Tableview and inserted the part from James_D which gives me a colored row and a different colored selected cell
see here.
This works realy pretty good. Now i need to read the data from an internet site.
After inserting the code to read the data with InputStreamReader the colored row is not displayed any more at all. No other error. The read date is ok.
About any help or alternatives I would be very glad.
Thanks.
deleted the first "multi file version" of this problem.
css file: selected-row-table.css
.table-row-cell:selected-row {
-fx-background-color: lightskyblue ;
}
New all in one Error example based on James_D Tableview without .fxml file. Needed css file (see above) with name "selected-row-table.css".
Same error after "new InputStreamreader...".
package com.example.james1;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
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.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class HelloApplication extends Application {
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) throws IOException {
Scene scene = new Scene(new Group());
scene.getStylesheets().add(getClass().getResource("selected-row-table.css").toExternalForm());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(600);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
final TableView<Person> table = new TableView<>();
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")
);
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
final PseudoClass selectedRowPseudoClass = PseudoClass.getPseudoClass("selected-row");
final ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {
selectedRowIndexes.clear();
table.getSelectionModel().getSelectedCells().stream().map(TablePosition::getRow).forEach(row -> {
selectedRowIndexes.add(row);
});
});
table.setRowFactory(tableView -> {
final TableRow<Person> row = new TableRow<>();
BooleanBinding selectedRow = Bindings.createBooleanBinding(() ->
selectedRowIndexes.contains(new Integer(row.getIndex())), row.indexProperty(), selectedRowIndexes);
selectedRow.addListener((observable, oldValue, newValue) ->
row.pseudoClassStateChanged(selectedRowPseudoClass, newValue)
);
return row;
});
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(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
Button bt1 = new Button("add local");
Button bt2 = new Button("add internet");
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, bt1, bt2);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
/*************************************************/
bt1.setOnAction(value -> {
data.add(new Person("xi.", "2023-01-01", "add Local "));
});
/*************************************************/
bt2.setOnAction(value -> {
BufferedReader br;
String strLine;
data.add(new Person("Do.", "start reading", "readHoli-1"));
try {
URL url = new URL("https://www.spiketime.de/feiertagapi/feiertage/csv/2023/2023");
URLConnection urlCon = url.openConnection(); //feastUrl.openConnection();
InputStreamReader isr = new InputStreamReader(urlCon.getInputStream()); // ERROR ---- DOES NOT WORK after this statement
data.add(new Person("Do.", "after creating streamreader", "TEST-2"));
br = new BufferedReader(isr);
while ((strLine = br.readLine()) != null) {
if (!strLine.contains("Bayern"))
continue;
System.out.println(strLine);
/* split string/check and add to holidays: here only print */
}
br.close();
isr.close();
} catch (Exception ex) {
ex.printStackTrace();
}
data.add(new Person("Do.", "end reading", "readHoli-2"));
});
}
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty 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 StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
public StringProperty emailProperty() {
return email;
}
}
}
All Bindings in javafx are implemented to do their best to not cause memory leaks. They do so (in the internal class BindingHelperObserver) by
keep a weak reference to the binding: this allows being claimed by garbage collection as soon as there is no strong reference path to the object
remove any listeners to their dependencies if the binding is gc'ed
The former is the reason for the problem described in the question because the cell factory creates the binding as a local reference which is collectable as soon as the method returns. Requesting an InputStream from a UrlConnection seems to trigger a garbage run, thus collecting the weakly referenced bindings of all rows.
To make them survive the life-time of its owner, the owner has to keep a strong reference to it, f.i. in the context of a cell factory:
subclass the row
let it have a field referencing the binding
install the binding/listener in its constructor
Example:
table.setRowFactory(tableView -> {
final TableRow<Person> row = new TableRow<>() {
private BooleanBinding selectedRow;
{
selectedRow = Bindings.createBooleanBinding(
() -> selectedRowIndexes.contains(getIndex()),
indexProperty(),
selectedRowIndexes);
selectedRow.addListener((observable, oldValue, newValue) -> {
pseudoClassStateChanged(selectedRowPseudoClass, newValue);
});
}
};
return row;
});

Horizontal ScrollBar is visible in TableView with constrained resize policy

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

Javafx: On button click open second selection window

I'm making a button which opens a new window containing a gridview of members, which shows their name and rank. A member can be clicked to be set as currentMember. However my JavaFX knowledge is limited and I have no idea how to create this temporary window without changing the current controller. I'm not sure if what I have is a good way of doing it. Am I doing something wrong or is this the correct way of doing it?
This code gets run when the Select Person button is clicked
#FXML
private void setLid(ActionEvent event) {
Stage stage = new Stage();
VBox box = new VBox();
box.setPadding(new Insets(10));
TableView<Persoon> tablePers = new TableView<>();
TableColumn<Persoon, String> voornaam = new TableColumn<>();
TableColumn<Persoon, String> achternaam = new TableColumn<>();
TableColumn<Persoon, String> graad = new TableColumn<>();
voornaam.setCellValueFactory(cellData -> cellData.getValue().voornaamProperty());
voornaam.setCellFactory(TextFieldTableCell.forTableColumn());
achternaam.setCellValueFactory(cellData -> cellData.getValue().achternaamProperty());
graad.setCellValueFactory(cellData -> cellData.getValue().graadProperty());
Label label = new Label("Selecteer een persoon");
Button btnSelectCurrentLid = new Button();
btnSelectCurrentLid.setText("Bevestigen");
btnSelectCurrentLid.setOnAction((ActionEvent e) -> {
geselecteerdePersoon = (tablePers.getSelectionModel().getSelectedItem());
stage.close();
});
box.getChildren().add(label);
box.getChildren().add(tablePers);
box.getChildren().add(btnSelectCurrentLid);
Scene scene = new Scene(box, 250, 150);
stage.setScene(scene);
stage.show();
}
Here is a one-file mcve (copy paste the entire code into FxMain.java and run):
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
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.VBox;
import javafx.stage.Stage;
public class FxMain extends Application {
private Persoon geselecteerdePersoon;
#Override
public void start(Stage primaryStage) throws Exception{
Label selectedPersonInfo = new Label();
Button start = new Button("Show Tabel");
start.setOnAction(e-> {
new LoginDialog().showAndWait();
selectedPersonInfo.setText("Selected: " +geselecteerdePersoon.voornaamProperty().get()
+ " " +geselecteerdePersoon.achternaamProperty().get());
});
BorderPane root = new BorderPane(selectedPersonInfo);
root.setBottom(start);
primaryStage.setScene(new Scene(root, 300,300));
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
class LoginDialog extends Dialog {
public LoginDialog() {
VBox box = new VBox();
box.setPadding(new Insets(10));
TableView<Persoon> tablePers = new TableView<>();
TableColumn<Persoon, String> voornaam = new TableColumn<>("Name");
TableColumn<Persoon, String> achternaam = new TableColumn<>("Last Name");
TableColumn<Persoon, String> graad = new TableColumn<>("Grade");
voornaam.setCellValueFactory(cellData -> cellData.getValue().voornaamProperty());
voornaam.setCellFactory(TextFieldTableCell.forTableColumn());
achternaam.setCellValueFactory(cellData -> cellData.getValue().achternaamProperty());
graad.setCellValueFactory(cellData -> cellData.getValue().graadProperty());
tablePers.getColumns().addAll(voornaam, achternaam, graad);
tablePers.getItems().add(new Persoon("Alice", "Bee","70"));
tablePers.getItems().add(new Persoon("Charly", "Din","32"));
Label label = new Label("Selecteer een persoon");
Button btnSelectCurrentLid = new Button();
btnSelectCurrentLid.setText("Bevestigen");
btnSelectCurrentLid.setOnAction((ActionEvent e) -> {
geselecteerdePersoon = tablePers.getSelectionModel().getSelectedItem();
close();
});
box.getChildren().addAll(label, tablePers, btnSelectCurrentLid);
getDialogPane().setContent(box);
getDialogPane().getButtonTypes().addAll(ButtonType.CANCEL);
}
}
}
class Persoon {
private final SimpleStringProperty lName;
private final SimpleStringProperty fName;
private final SimpleStringProperty grade;
public Persoon(String fName, String lName, String grade) {
this.fName = new SimpleStringProperty(fName);
this.lName = new SimpleStringProperty(lName);
this.grade = new SimpleStringProperty(grade);
}
public final StringProperty achternaamProperty() { return lName; }
public final StringProperty voornaamProperty() { return fName; }
public final StringProperty graadProperty() { return grade; }
}

How do I select individual characters inside of a cell, instead of the whole cell in JavaFX TableView?

I know how to copy an entire cell in JavaFX's tableview, but is there a way I can only copy PART of the text in a single cell?
For example if I have the following in a cell:
1. apples
2. oranges
3. bananas
I want to be able to copy "2. oranges" from the cell without copying the entire text contents list. Currently if I click on a cell it highlights and copies the entire text.
One of the things I like to do is create options in the right-click context menu.
Key code:
//Use setRowFactory to set up the ContextMenu
table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>()
{
#Override
public TableRow<Person> call(TableView<Person> tableView)
{
final TableRow<Person> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem copyItem = new MenuItem("copy index and first name");
copyItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
final Clipboard clipboard = Clipboard.getSystemClipboard();
final ClipboardContent content = new ClipboardContent();
Person tempPerson = row.getItem();
//Put the row index and first name in clipboard
content.putString(row.getIndex() + ". " + tempPerson.getFirstName());
clipboard.setContent(content);
}
});
contextMenu.getItems().add(copyItem);
// 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;
}
});
Full Code - Altered code from: https://gist.github.com/james-d/7758918
Main Class
import java.util.Arrays;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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.cell.PropertyValueFactory;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
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)
{
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<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"));
table.setItems(data);
table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));
table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>()
{
#Override
public TableRow<Person> call(TableView<Person> tableView)
{
final TableRow<Person> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
final MenuItem copyItem = new MenuItem("copy index and first name");
copyItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
final Clipboard clipboard = Clipboard.getSystemClipboard();
final ClipboardContent content = new ClipboardContent();
Person tempPerson = row.getItem();
content.putString(row.getIndex() + ". " + tempPerson.getFirstName());
clipboard.setContent(content);
}
});
contextMenu.getItems().add(copyItem);
// 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();
}
}
Person Class
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javafxapplication206;
import javafx.beans.property.SimpleStringProperty;
public class Person
{
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
public 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 TableColumn: is there a way to generate a column header event?

I solved the basic problem I was looking at by setting a comparator on the entire table, but what I was initially trying to do was find a way to "click" the header to generate the sorting event.
I'd still like to know how to do this, as I currently do not know of a method to proc sorting methods of the columns, only the table itself.
Call getSortOrder() on the TableView: that returns a list of TableColumns representing the order by which rows are sorted:
An empty sortOrder list means that no sorting is being applied on the
TableView. If the sortOrder list has one TableColumn within it, the
TableView will be sorted using the sortType and comparator properties
of this TableColumn (assuming TableColumn.sortable is true). If the
sortOrder list contains multiple TableColumn instances, then the
TableView is firstly sorted based on the properties of the first
TableColumn. If two elements are considered equal, then the second
TableColumn in the list is used to determine ordering. This repeats
until the results from all TableColumn comparators are considered, if
necessary.
Then just add to, remove from, set, clear, etc the list as you need.
SSCCE:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewProgrammaticSort extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
TableColumn<Person, String> firstNameCol = column("First Name", Person::firstNameProperty);
TableColumn<Person, String> lastNameCol = column("Last Name", Person::lastNameProperty);
TableColumn<Person, String> emailCol = column("Email", Person::emailProperty);
table.getColumns().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.getColumns().add(emailCol);
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")
);
ComboBox<TableColumn<Person, ?>> sortCombo = new ComboBox<>();
sortCombo.getItems().add(firstNameCol);
sortCombo.getItems().add(lastNameCol);
sortCombo.getItems().add(emailCol);
sortCombo.setCellFactory(lv -> new ColumnListCell());
sortCombo.valueProperty().addListener((obs, oldColumn, newColumn) -> {
table.getSortOrder().clear();
if (newColumn != null) {
table.getSortOrder().add(newColumn);
}
});
sortCombo.setButtonCell(new ColumnListCell());
BorderPane root = new BorderPane(table, sortCombo, null, null, null);
BorderPane.setMargin(table, new Insets(10));
BorderPane.setMargin(sortCombo, new Insets(10));
BorderPane.setAlignment(sortCombo, Pos.CENTER);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class ColumnListCell extends ListCell<TableColumn<Person, ?>> {
#Override
public void updateItem(TableColumn<Person, ?> column, boolean empty) {
super.updateItem(column, empty);
if (empty) {
setText(null);
} else {
setText(column.getText());
}
}
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final java.lang.String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final java.lang.String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}

Resources