how to make a table visible with custom rows & column - javafx

i have the following code to create a custom Table. but in the output it shows many rows which doesn't contain any values. i would like to display just two rows and 1 columns. is there any solution for this, else Javafx produces this by default. Is there any alternate way to create a table. May be using a GridPaneBuilder
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob"),
new Person("Isabella")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
table.setItems(data);
table.getColumns().addAll(firstNameCol);
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 Person(String fName) {
this.firstName = new SimpleStringProperty(fName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
}

You can set that the columns take as much space as possible by:
myTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
I don't know if there is an easy way to set height of the table according to the amount of rows, but you could set the maxHeight of the table accoring to the amount of rows multiplied by the rowHeight:
myTable.setMaxHeight(countOfRows * rowHeight + headerHeight);
And a more flexible way would be to use JavaFX binding, so when you add or delete a row the height of the table changes.

Related

How to implement correct doubleclick-to-edit in TableView

Problem
The TableView seems to go into edit mode as soon as you click on an already selected cell. This is unwanted and inconsistent behavior, because the selection changes if you click outside of the current selection. It doesn't change when you click inside the current selection.
If anything, a real double click should be required to go into edit mode, at least when you work on a desktop.
Reproduction:
Create a TableView with this selection mode:
// cell selection mode instead of row selection
table.getSelectionModel().setCellSelectionEnabled(true);
// allow selection of multiple cells
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Now select multiple cells (via shift keypress). Then click on a single cell of the currently selected cells.
The current behavior:
the table goes into edit mode
Question
The requested behavior:
only the selection should change, i. e. the single cell should be selected; edit mode should only happen on a double-click
How can you achieve this?
Code
Here's a full example. I took the code from the Oracle samples website, added the above lines and pre-selected all cells. When you click on a single cell after program start, the table goes directly into edit mode:
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"));
final HBox hb = new HBox();
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(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
firstNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setFirstName(t.getNewValue());
}
}
);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
lastNameCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setLastName(t.getNewValue());
}
}
);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(TextFieldTableCell.forTableColumn());
emailCol.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setEmail(t.getNewValue());
}
}
);
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final TextField addFirstName = new TextField();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
final TextField addLastName = new TextField();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");
final TextField addEmail = new TextField();
addEmail.setMaxWidth(emailCol.getPrefWidth());
addEmail.setPromptText("Email");
final Button addButton = new Button("Add");
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
data.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText()));
addFirstName.clear();
addLastName.clear();
addEmail.clear();
}
});
hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
// cell selection mode instead of row selection
table.getSelectionModel().setCellSelectionEnabled(true);
// allow selection of multiple cells
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// select all for demo purposes
table.getSelectionModel().selectAll();
}
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);
}
}
}
You can try to clear selection on mouse pressed event:
table.addEventFilter( MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>()
{
#Override
public void handle( MouseEvent event )
{
if( event.isControlDown()) {
return;
}
if ( table.getEditingCell() == null) {
table.getSelectionModel().clearSelection();
}
}
});

JAVAFX - The hidden column's mysterious solution

I have a TableView which shows all the data from a Derby table, except the "ID" of the items (I don't want the user to see it.)
The method is simple, I made an SQL statement to select all the attributes but I did not ask for the ID column. I got back a TableData and this variable is shown in the TableView with all of the records (It is a simple namelist).
I want to allow the user to delete from the table with a "delete button".
So first, there should be an "OnAction" method, (when the user clicks the delete button) when we gather the ACTUAL selected row's (if not null) ID, and we send a statement to the database to delete it, where the (HIDDEN) ID of the selected item can be found at the derby table.
Since it is a Namelist, and the user is allowed to make another record with the exactly same data, in the tableview the records can be clones, with no differences. (only the ID is uniqe, but the tableview does not contain the id-s, that makes it so hard).
So how could we delete the selected line without knowing it's ID? Or how can we know the ID when it is not shown in the table? (searching on the name is not working, since there can be several records with the same name)
What is the "hidden column's mysterious solution"? :)
Your question has a simple solution. If you don't want to show the ID on the tableview, you don't have to. TableView binds with your class, in your case user, and not with its fields. When you add the values to the tableview, you can control which fields you want to show on it. Whether you show it on the tableview or not, you still have your complete user object with you (which still has the ID).
Now, there are multiple ways to do this. One of the way is to bind the id with the delete button that you want to show in each row. Whenever the delete button is pressed, delete the item from the DB and delete it from the tableview.
I have create an working example which shows how this work. I have hardcoded my values and instead of deleting it from the database, I am printing the ID value on the console.
In this example, the Person class can be considered equivalent to your user, each of which has an id and other attributes.
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewSample extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person(10, "Jacob", "Smith", "jacob.smith#example.com"),
new Person(20, "Isabella", "Johnson", "isabella.johnson#example.com"),
new Person(30, "Ethan", "Williams", "ethan.williams#example.com"),
new Person(40, "Emma", "Jones", "emma.jones#example.com"),
new Person(50, "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(600);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
TableColumn deleteCol = new TableColumn("Delete");
deleteCol.setMinWidth(100);
deleteCol.setCellFactory(param -> new ButtonCell());
deleteCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("id"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, deleteCol);
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 SimpleIntegerProperty id;
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(Integer id, String fName, String lName, String email) {
this.id = new SimpleIntegerProperty(id);
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public int getId() {
return id.get();
}
public void setId(int id) {
this.id.set(id);
}
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);
}
}
private class ButtonCell extends TableCell<Person, Integer> {
Image buttonDeleteImage = new Image("https://cdn1.iconfinder.com/data/icons/nuove/22x22/actions/fileclose.png");
final Button cellDeleteButton = new Button("", new ImageView(buttonDeleteImage));
ButtonCell() {
cellDeleteButton.setOnAction(actionEvent -> {
System.out.println("Deleted Id : " + getItem());// Make a DB call and delete the person with ID
getTableView().getItems().remove(getIndex());
});
}
#Override
protected void updateItem(Integer t, boolean empty) {
super.updateItem(t, empty);
if (!empty) {
setGraphic(cellDeleteButton);
} else {
setGraphic(null);
}
}
}
}
To do it without binding ID
You need :
deleteCol.setCellValueFactory(p -> {
return new ReadOnlyObjectWrapper<Person>((Person)p.getValue());
});
The custom ButtonCell should extend TableCell<Person, Person>
The logic to delete item becomes :
System.out.println("Deleted ID : " +
getItem().getId());// Make a DB call and delete the person with ID
getTableView().getItems().remove(getIndex());
Complete Example:
public class TableViewSample extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person(10, "Jacob", "Smith", "jacob.smith#example.com"),
new Person(20, "Isabella", "Johnson", "isabella.johnson#example.com"),
new Person(30, "Ethan", "Williams", "ethan.williams#example.com"),
new Person(40, "Emma", "Jones", "emma.jones#example.com"),
new Person(50, "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(600);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
TableColumn deleteCol = new TableColumn("Delete");
deleteCol.setMinWidth(100);
deleteCol.setCellFactory(param -> new ButtonCell());
deleteCol.setCellValueFactory(p -> {
return new ReadOnlyObjectWrapper<Person>((Person)p.getValue());
});
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, deleteCol);
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 SimpleIntegerProperty id;
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(Integer id, String fName, String lName, String email) {
this.id = new SimpleIntegerProperty(id);
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public int getId() {
return id.get();
}
public void setId(int id) {
this.id.set(id);
}
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);
}
}
private class ButtonCell extends TableCell<Person, Person> {
Image buttonDeleteImage = new Image("https://cdn1.iconfinder.com/data/icons/nuove/22x22/actions/fileclose.png");
final Button cellDeleteButton = new Button("", new ImageView(buttonDeleteImage));
ButtonCell() {
cellDeleteButton.setOnAction(actionEvent -> {
System.out.println("Deleted ID : " +
getItem().getId());// Make a DB call and delete the person with ID
getTableView().getItems().remove(getIndex());
});
}
#Override
protected void updateItem(Person t, boolean empty) {
super.updateItem(t, empty);
if (!empty) {
setGraphic(cellDeleteButton);
} else {
setGraphic(null);
}
}
}
}
I have found an easier way, where the user can list anything, and he decides what to show and what to hide. This solution also works if we don't know what data will come to us, but We are sure that will be "ID" column we do not wan't to show in attention:
if (*ColumNameWeWantToHide*.equals(String.valueOf(columnName))) {
col.prefWidthProperty().bind(*TableViewName*.widthProperty().divide(100));
}
It makes us also able to work with ID numbers simply, but also we are abla to watch the ID if we want, but on default we do not see them. That is a good mixture of useability and easy coding.

Tableview Integer sorting bugged?

I have a tableview and one of my columns consist of Integer values:
1
444
-9
If I sort on this column, I would expect 444 to be the "highest" number and therefor be the first row, but JavaFX believes 9 is higher?
Any tips?
MVCE:
public class TableViewTEST extends Application {
private TableView table = new TableView();
final ObservableList<Person> data = FXCollections.observableArrayList(
new Person("1", "Smith", "jacob.smith#example.com"),
new Person("9", "Johnson", "isabella.johnson#example.com"),
new Person("444", "Williams", "ethan.williams#example.com")
);
#Override
public void start(Stage stage) {
table.getItems().addAll(data);
Scene scene = new Scene(new Group());
stage.setTitle(
"Table View Sample");
stage.setWidth(
300);
stage.setHeight(
500);
final Label label = new Label("Address Book");
label.setFont(
new Font("Arial", 20));
TableColumn firstNameCol = new TableColumn("First");
TableColumn lastNameCol = new TableColumn("Last Name");
TableColumn emailCol = new TableColumn("Email");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName")
);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName")
);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email")
);
table.getColumns()
.addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(
5);
vbox.setPadding(
new Insets(10, 0, 0, 10));
vbox.getChildren()
.addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Edit
Okay, I have found out the reason for this is because I use String in the model.. So the updated question is, how can you create a new sorting functionality on the table column so that it sorts as integer even though there's string in it?
Your values are Strings, so they are sorted lexicographically; since the character '4' comes before the character '9', '444' comes before '9'.
If you make those fields integer fields, then they will be sorted numerically. It's helpful here to use properly typed TableViews and TableColumns, instead of raw types.
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.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewSortTest extends Application {
private TableView<Person> table = new TableView<>();
final ObservableList<Person> data = FXCollections.observableArrayList(
new Person(1, "Smith", "jacob.smith#example.com"),
new Person(9, "Johnson", "isabella.johnson#example.com"),
new Person(444, "Williams", "ethan.williams#example.com")
);
#Override
public void start(Stage stage) {
table.getItems().addAll(data);
Scene scene = new Scene(new Group());
stage.setTitle(
"Table View Sample");
stage.setWidth(
300);
stage.setHeight(
500);
final Label label = new Label("Address Book");
label.setFont(
new Font("Arial", 20));
TableColumn<Person, Integer> idCol = new TableColumn<>("Id");
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
idCol.setCellValueFactory(
new PropertyValueFactory<Person, Integer>("id")
);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("name")
);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email")
);
table.getColumns()
.addAll(idCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(
5);
vbox.setPadding(
new Insets(10, 0, 0, 10));
vbox.getChildren()
.addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class Person {
private final IntegerProperty id = new SimpleIntegerProperty(this, "id");
private final StringProperty name = new SimpleStringProperty(this, "name");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(int id, String name, String email) {
this.id.set(id);
this.name.set(name);
this.email.set(email);
}
public final IntegerProperty idProperty() {
return this.id;
}
public final int getId() {
return this.idProperty().get();
}
public final void setId(final int id) {
this.idProperty().set(id);
}
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 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);
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
You can create a Comparator:
firstNameCol.setComparator(new CustomComparator());
EDIT
Assuming you have a String type in your column, as it was originally posted, and you want to sort it numerically trying to parse all the strings, and in case there'are some non-integer strings, sort these lexicographically, this will be a valid Comparator:
private class CustomComparator implements Comparator<String>{
#Override
public int compare(String o1, String o2) {
if (o1 == null && o2 == null) return 0;
if (o1 == null) return -1;
if (o2 == null) return 1;
Integer i1=null;
try{ i1=Integer.valueOf(o1); } catch(NumberFormatException ignored){}
Integer i2=null;
try{ i2=Integer.valueOf(o2); } catch(NumberFormatException ignored){}
if(i1==null && i2==null) return o1.compareTo(o2);
if(i1==null) return -1;
if(i2==null) return 1;
return i1-i2;
}
}

JavaFX Table/TreeTable Cell Overflow

I want to get the text in a treetable cell to overflow into the adjacent cells. I believe that I read a while ago that there is no Span function in tables or treetables, but it might be accomplished through css.
See this image. I want the date in the first cell to extend past the others so it can be read.
http://imgur.com/KvK0adK
I thought I would use -overflow: visible; but I read in the Oracle documentation:
JavaFX CSS does not support CSS layout properties such as float, position, overflow, and width.
Can anyone point me in the right direction here? CSS would be preferable, but would a custom TreeTableRow be the right solution?
Thanks in advance.
Sample Solution
Here is an overflow cell for a TableView, you could probably adapt it for a TreeTableView.
class OverflowCell extends TableCell<Person, String> {
private Label overflowLabel = new Label();
private Group overflowGroup = new Group(overflowLabel);
public OverflowCell() {
// destroy the clip.
clipProperty().addListener((observable, oldClip, newClip) -> {
if (newClip != null) {
setClip(null);
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
overflowLabel.setText(item);
setGraphic(
overflowGroup
);
}
}
}
Normally the cells in a TableView are clipped, so the listener is added in the constructor of the OverflowCell to remove any clip added to the cell. The updateItem call uses a Group so that the Label is not resized to a size smaller than it's preferred size and elided.
There are probably other ways to do this. A custom row factory might be an alternate solution. This OverflowCell hack was just a simple thing which came to mind.
Sample Code
Here is it in action, you can see how the last name in the first line overflows into the next column.
Apologies for the amount of code here.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableCellOverflow extends Application {
private TableView<Person> table = new TableView<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Krzyzanowski", ""),
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");
table.setPrefHeight(200);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMaxWidth(80);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
firstNameCol.getStyleClass().add("left-header");
TableColumn<Person, String> lastNameCol = new TableColumn<>();
lastNameCol.setMaxWidth(60);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
lastNameCol.setCellFactory(param -> new OverflowCell());
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setMaxWidth(100);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
class OverflowCell extends TableCell<Person, String> {
private Label overflowLabel = new Label();
private Group overflowGroup = new Group(overflowLabel);
public OverflowCell() {
// destroy the clip.
clipProperty().addListener((observable, oldClip, newClip) -> {
if (newClip != null) {
setClip(null);
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
overflowLabel.setText(item);
setGraphic(
overflowGroup
);
}
}
}
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);
}
}
}

Can a JavaFX TableColumn's header text be made to bleed over to another column?

I have a TableView with two TableColumns. On the left is a wide column that needs to be able to show entries up to 30 characters. On the right is a narrow column that needs to be able to show just one character. But the title of the right column needs to be a long word (it is required). But I don't have the luxury in terms of space to have a column as fat as the required header text. If I make the right column as wide as its header text, the data in the left column gets truncated. I would like the header text of the column on the right to bleed over into the column on the left (there is no worry of it running into the left column's header text). Is this possible by setting some fx css of my TableView and/or one or both of the TableColumns?
Here is an example where the column header "Long Last Name" bleeds into the column header to the left of it.
bleed.css
.column-header.left-header > .label {
-fx-alignment: baseline-left;
}
.column-header.bleed-header > .label {
-fx-alignment: baseline-right;
-fx-padding: 0 3 0 0;
}
Code snippet which enables the "bleed"
TableColumn<Person, String> lastNameCol = new TableColumn<>();
lastNameCol.setMinWidth(80);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
lastNameCol.getStyleClass().add("bleed-header");
Label headerLabel = new Label("Long Last Name");
headerLabel.setMinWidth(Control.USE_PREF_SIZE);
lastNameCol.setGraphic(headerLabel);
The solution required a bit of trickery:
Use of css to set alignment properties.
Use a label graphic for the header.
Supplying the label graphic gives you direct control of the minimum width (to prevent the default behaviour of the header label being elided when it is too long).
Executable code sample
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewHeaderBleed extends Application {
private TableView<Person> table = new TableView<>();
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");
table.setPrefHeight(200);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<>("firstName"));
firstNameCol.getStyleClass().add("left-header");
TableColumn<Person, String> lastNameCol = new TableColumn<>();
lastNameCol.setMinWidth(80);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<>("lastName"));
lastNameCol.getStyleClass().add("bleed-header");
Label headerLabel = new Label("Long Last Name");
headerLabel.setMinWidth(Control.USE_PREF_SIZE);
lastNameCol.setGraphic(headerLabel);
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setMinWidth(100);
emailCol.setCellValueFactory(
new PropertyValueFactory<>("email"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
scene.getStylesheets().add(getClass().getResource("bleed.css").toExternalForm());
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);
}
}
}

Resources