Java FX2.0 Table View - javafx

Table view is not resizing when window maximizes.there any properly for this ?
Is used the table as log viewer in my project,i need it to be occupying the whole window.

Here is some example code for a table which will resize to occupy all available space in a window, regardless of the window size, maximize state, etc.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TableResize extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person,String>("firstName")
);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person,String>("lastName")
);
TableView table = new TableView();
table.getColumns().addAll(firstNameCol, lastNameCol);
table.setItems(FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams")
));
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
StackPane layout = new StackPane();
layout.getChildren().add(table);
stage.setScene(new Scene(layout));
stage.show();
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
public String getFirstName() { return firstName.get(); }
public void setFirstName(String fName) { firstName.set(fName); }
public String getLastName() { return lastName.get(); }
public void setLastName(String fName) { lastName.set(fName); }
}
}
The example is a modified version of the code from the JavaFX TableView tutorial.
Perhaps you are using a Group rather than an appropriate resizable layout pane as your root node. Or perhaps you did not set an appropriate column resize policy on the table. It is hard to say what your layout issue is without provided code, but hopefully you can get your code to work with the help of the above example.

Related

Adding a CheckBox column to an existing TableView

I recently wanted to add a CheckBox column to an existing TableView. To study the problem in isolation, I started with Example 13-6 Creating a Table and Adding Data to It. I added a BooleanProperty and accessors to the Person model class, and I added a new TableColumn with a CheckBoxTableCell as the cell factory. As shown in the image, I see a CheckBox on each row. Although all values are true, none are checked; the checkboxes are live, but setActive() is never called. Recent questions on this topic suggest that I'm missing something; I'd welcome any insight.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
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.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
/**
* Example 13-6 Creating a Table and Adding Data to It
* https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm#CJAGAAEE
*/
public class TableViewSample extends Application {
private final 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) {
stage.setTitle("Table View Sample");
stage.setWidth(600);
stage.setHeight(400);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn<Person, Boolean> active = new TableColumn<>("Active");
active.setCellValueFactory(new PropertyValueFactory<>("active"));
active.setCellFactory(CheckBoxTableCell.forTableColumn(active));
TableColumn<Person, String> firstName = new TableColumn<>("First Name");
firstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
lastName.setCellValueFactory(new PropertyValueFactory<>("lastName"));
TableColumn<Person, String> email = new TableColumn<>("Email");
email.setCellValueFactory(new PropertyValueFactory<>("email"));
table.setItems(data);
table.getColumns().addAll(active, firstName, lastName, email);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(8));
vbox.getChildren().addAll(label, table);
stage.setScene(new Scene(vbox));
stage.show();
}
public static class Person {
private final BooleanProperty active;
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty email;
private Person(String fName, String lName, String email) {
this.active = new SimpleBooleanProperty(true);
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public boolean getActive() {
return active.get();
}
public void setActive(boolean b) {
active.set(b);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String s) {
firstName.set(s);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String s) {
lastName.set(s);
}
public String getEmail() {
return email.get();
}
public void setEmail(String s) {
email.set(s);
}
}
}
Summary: As noted here, this is likely a bug; steps to avoid the pitfall include these:
Verify that the data model exports properties correctly, as shown here.
Critically examine the value of replacing PropertyValueFactory with an explicit Callback, when possible, as outlined here, here, here, here and here.
The problem is that CheckBoxTableCell can't find or bind the ObservableProperty<Boolean> based on the parameter supplied:
active.setCellFactory(CheckBoxTableCell.forTableColumn(active));
The CheckBoxTableCell defers to the table column for access to the target Boolean property. To see the effect, replace the active parameter with a Callback that returns the ObservableValue<Boolean> for row i explicitly:
active.setCellFactory(CheckBoxTableCell.forTableColumn(
(Integer i) -> data.get(i).active));
While this makes the checkboxes work, the underlying problem is that the Person class needs an accessor for the active property. Using JavaFX Properties and Binding discusses the property method naming conventions, and the Person class of the Ensemble8 tablecellfactory illustrates a working model class with a property getter for each attribute, also shown below.
With this change PropertyValueFactory can find the newly added BooleanProperty, and the original form of forTableColumn() works. Note that the convenience of PropertyValueFactory comes with some limitations. In particular, the factory's fall-through support for the previously missing property accessor goes unnoticed. Fortunately, the same accessor allows substitution of a simple Callback for each column's value factory. As shown here, instead of PropertyValueFactory,
active.setCellValueFactory(new PropertyValueFactory<>("active"));
Pass a lamda expression that returns the corresponding property:
active.setCellValueFactory(cd -> cd.getValue().activeProperty());
Note also that Person can now be private. Moreover, the use of explicit type parameters affords stronger type checking during compilation.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
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.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
/**
* https://stackoverflow.com/a/68969223/230513
*/
public class TableViewSample extends Application {
private final 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) {
stage.setTitle("Table View Sample");
stage.setWidth(600);
stage.setHeight(400);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn<Person, Boolean> active = new TableColumn<>("Active");
active.setCellValueFactory(cd -> cd.getValue().activeProperty());
active.setCellFactory(CheckBoxTableCell.forTableColumn(active));
TableColumn<Person, String> firstName = new TableColumn<>("First Name");
firstName.setCellValueFactory(cd -> cd.getValue().firstNameProperty());
TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
lastName.setCellValueFactory(cd -> cd.getValue().lastNameProperty());
TableColumn<Person, String> email = new TableColumn<>("Email");
email.setCellValueFactory(cd -> cd.getValue().emailProperty());
table.setItems(data);
table.getColumns().addAll(active, firstName, lastName, email);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(8));
vbox.getChildren().addAll(label, table);
stage.setScene(new Scene(vbox));
stage.show();
}
private static class Person {
private final BooleanProperty active;
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty email;
private Person(String fName, String lName, String email) {
this.active = new SimpleBooleanProperty(true);
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public BooleanProperty activeProperty() {
return active;
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty lastNameProperty() {
return lastName;
}
public StringProperty emailProperty() {
return email;
}
}
}

position of TableColumn in Scene

I have a TableView with several TableColumns and I want to place a Node below a certain TableColumn. How do I get the exact position (x,y-coordinates) of the TableColumn so I can bind the translate properties of my node?
Here is a snippet of how I placed a button on the top right corner of my TableView:
button.translateXProperty().unbind();
button.translateXProperty().bind(tableView.widthProperty().divide(2.0).subtract(button.getWidth() / 2.0 + 2.0) + tableView.localToScene(0.0, 0.0).getX());
This works fine, but obviously only for the TableView. The TableColumns don't have those translate properties or the localToScene methods, so I can't directly get the position to which I would like to bind my Node.
My current solution (which doesn't really work that well) is to do the following:
I read out the position of my TableView in the Scene (PointA) and then go through the list of all columns (tableView.getColumns()) and check if each of them is visible, and if so, add their width to the X-value of PointA. I do this until I find the actual column that I want to place the Node below.
Now the problem is, that I can't really just bind the Nodes position to this point, because when I change the order of the columns, or make one of them invisible, my column changes position on the screen. I would have to add a listener to the column order and visibility...
Is there any more efficient way to do what I want? :D
I generally dislike using lookups, but you can retrieve the label that is used to display the column header using the lookup .table-view .column-header .label and then bind your button's layout properties using the bounds of that label.
Example:
import java.util.Optional;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
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.layout.Pane;
import javafx.stage.Stage;
public class TableColumnLocationExample extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty, 120));
table.getColumns().add(column("Last Name", Person::lastNameProperty, 120));
table.getColumns().add(column("Email", Person::emailProperty, 250));
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")
);
Pane root = new Pane(table);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
for (TableColumn<Person, ?> col : table.getColumns()) {
Optional<Label> header = findLabelForTableColumnHeader(col.getText(), root);
header.ifPresent(label -> {
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
label.getBoundsInLocal().getWidth(), label.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
label.getLocalToSceneTransform().transform(label.getBoundsInLocal()).getMinX(),
label.boundsInLocalProperty(), label.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
});
}
}
private Optional<Label> findLabelForTableColumnHeader(String text, Parent root) {
return root.lookupAll(".table-view .column-header .label")
.stream()
.map(Label.class::cast)
.filter(label -> label.getText().equals(text))
.findAny(); // assumes all columns have unique text...
}
private <S,T> TableColumn<S,T> column(String title, Function<S,ObservableValue<T>> property, double width) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
return col ;
}
public static class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
If you are allowed to use non-public api, you might consider to access the TableColumnHeader via its skin, provided it's of type TableViewSkinBase: it has api to access the TableRowHeader which is the container of all TableColumnHeaders and has api to find the header for any column it contains.
Code snippet (the width/location binding is copied from James' example, just to the header instead of the label)
private void buttonsPerHeader(TableView<Person> table, Pane root) {
if (!(table.getSkin() instanceof TableViewSkinBase)) return;
TableViewSkinBase skin = (TableViewSkinBase) table.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
for (TableColumn col : table.getColumns()) {
TableColumnHeader header = headerRow.getColumnHeaderFor(col);
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
header.getBoundsInLocal().getWidth(), header.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
header.getLocalToSceneTransform().transform(header.getBoundsInLocal()).getMinX(),
header.boundsInLocalProperty(), header.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
}
}

creating RadioButton in TableView Column

I followed the tutorial about TableView in Oracle docs, and I want to do the same thing, but instead of showing a TextField to modified items, I want to show a RadioButton.
(I created the TableView with RadionButton on it)
I used the tutorial at https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm (see Editing Data in the Table) and extended it. As a basis for the values, which should be set by radio buttons I assume an Enumeration.
In my example I extended the Person class with the enum Participation (indicating whether or not the people of the list attending an fictive event) ...
public static enum Participation {
YES,
NO,
MAYBE;
public String toString() {
return super.toString().toLowerCase();
};
}
...
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final SimpleObjectProperty<Participation> participation;
...
I implemented a RadioButtonCell, which takes an arbitraty EnumSet<T>. So you can use it for every Enumeration and every TableColumn, which should contain RadioButtons.
public static class RadioButtonCell<S,T extends Enum<T>> extends TableCell<S,T>{
private EnumSet<T> enumeration;
public RadioButtonCell(EnumSet<T> enumeration) {
this.enumeration = enumeration;
}
#Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
// gui setup
HBox hb = new HBox(7);
hb.setAlignment(Pos.CENTER);
final ToggleGroup group = new ToggleGroup();
// create a radio button for each 'element' of the enumeration
for (Enum<T> enumElement : enumeration) {
RadioButton radioButton = new RadioButton(enumElement.toString());
radioButton.setUserData(enumElement);
radioButton.setToggleGroup(group);
hb.getChildren().add(radioButton);
if (enumElement.equals(item)) {
radioButton.setSelected(true);
}
}
// issue events on change of the selected radio button
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#SuppressWarnings("unchecked")
#Override
public void changed(ObservableValue<? extends Toggle> observable,
Toggle oldValue, Toggle newValue) {
getTableView().edit(getIndex(), getTableColumn());
RadioButtonCell.this.commitEdit((T) newValue.getUserData());
}
});
setGraphic(hb);
}
}
}
You now have to adjust the CellFactory of the particular TableColumn
participationColumn.setCellFactory((param) -> new RadioButtonCell<Person, Participation>(EnumSet.allOf(Participation.class)));
Finally update the actual value of your data on a commit as usual:
participationColumn.setCellValueFactory(new PropertyValueFactory<Person, Participation>("participation"));
participationColumn.setOnEditCommit(
new EventHandler<CellEditEvent<Person, Participation>>() {
#Override
public void handle(CellEditEvent<Person, Participation> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setParticipation(t.getNewValue());
}
}
);
gkri, thank you for the nice code! This is very useful. I have one additional remark: Of course the new SimpleObjectProperty needs its get & set methods. Without the getter the table will not properly update, especially when sorting or, with TreeTableView, when expanding or collapsing nodes:
public void setParticipation(Participation p){
participation.set(p);
}
public Participation getParticipation(){
return participation.get();
}
So the full code sample:
import java.util.EnumSet;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TableViewSample extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com",Participation.MAYBE),
new Person("Isabella", "Johnson", "isabella.johnson#example.com",Participation.MAYBE),
new Person("Ethan", "Williams", "ethan.williams#example.com",Participation.MAYBE),
new Person("Emma", "Jones", "emma.jones#example.com",Participation.MAYBE),
new Person("Michael", "Brown", "michael.brown#example.com",Participation.MAYBE));
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(650);
stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
table.setMinWidth(640);
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"));
TableColumn<Person,Participation> participationColumn = new TableColumn("Participation");
participationColumn.setCellFactory((param) -> new RadioButtonCell<Person, Participation>(EnumSet.allOf(Participation.class)));
participationColumn.setCellValueFactory(new PropertyValueFactory<Person, Participation>("participation"));
participationColumn.setOnEditCommit(
new EventHandler<CellEditEvent<Person, Participation>>() {
#Override
public void handle(CellEditEvent<Person, Participation> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setParticipation(t.getNewValue());
}
}
);
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, participationColumn );
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((ActionEvent e) -> {
data.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText(),
Participation.NO
));
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();
}
public static enum Participation {
YES,
NO,
MAYBE;
public String toString() {
return super.toString().toLowerCase();
};
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final SimpleObjectProperty<Participation> participation = new SimpleObjectProperty<Participation>();
private Person(String fName, String lName, String email, Participation p ) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
this.participation.setValue(p);
}
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);
}
public void setParticipation(Participation p){
participation.set(p);
}
public Participation getParticipation(){
return participation.get();
}
}
public static class RadioButtonCell<S,T extends Enum<T>> extends TableCell<S,T>{
private EnumSet<T> enumeration;
public RadioButtonCell(EnumSet<T> enumeration) {
this.enumeration = enumeration;
}
#Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
// gui setup
HBox hb = new HBox(7);
hb.setAlignment(Pos.CENTER);
final ToggleGroup group = new ToggleGroup();
// create a radio button for each 'element' of the enumeration
for (Enum<T> enumElement : enumeration) {
RadioButton radioButton = new RadioButton(enumElement.toString());
radioButton.setUserData(enumElement);
radioButton.setToggleGroup(group);
hb.getChildren().add(radioButton);
if (enumElement.equals(item)) {
radioButton.setSelected(true);
}
}
// issue events on change of the selected radio button
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#SuppressWarnings("unchecked")
#Override
public void changed(ObservableValue<? extends Toggle> observable,
Toggle oldValue, Toggle newValue) {
getTableView().edit(getIndex(), getTableColumn());
RadioButtonCell.this.commitEdit((T) newValue.getUserData());
}
});
setGraphic(hb);
}
}
}
}
Simly do it like in the example 12-8 Editing a Table Cell.
Define a EditingCell like there(at the bottom) but simply replace the TextField with a RadioButton.
If you need further assistance, write a comment under this answer.
Happy Coding,
Kalasch

How to dynamically add data to the tableView only in the last rows without disturbing other rows with observavbleList in javafx?

In nutshell I want to add the row silently at the end of the table without affecting other rows.
This is my controller class code excerpt to add entries to the table. The problem is when I add an item to the sourceTree, instead of adding a row at the end silently it disturbs the complete table. Suppose there were already some entries in the table and the user expands one of the titled pane in the table, now when a new row will be added to the table, complete table will blink and this titled pane will automatically shrink. This blinking led me to conclude that instead of adding an entry whole table is refreshing probably. Please help...
#FXML
public static TableView<NodeInfo> tableView;
#FXML
public static TableColumn<NodeInfo, TitledPane> nodeTree;
#FXML
private TableColumn<NodeInfo, String> name;
#FXML
private TableColumn<NodeInfo, CheckBox> favourite;
#FXML
private TableColumn<NodeInfo, Button> updates;
#FXML
public static ObservableList<NodeInfo> sourceTree = FXCollections.observableArrayList();
tableView.setPlaceholder(new Label("You have no Nodes in the network at the moment!!!"));
nodeTree.setCellValueFactory(new PropertyValueFactory<NodeInfo, TitledPane>("TitledPaneNode"));
nodeTree.prefWidthProperty().bind(tableView.widthProperty().divide(3));
name.setCellValueFactory(new PropertyValueFactory<NodeInfo, String>("name"));
name.prefWidthProperty().bind(tableView.widthProperty().divide(3));
favourite.setCellValueFactory(new PropertyValueFactory<NodeInfo, CheckBox>("favourite"));
favourite.prefWidthProperty().bind(tableView.widthProperty().divide(6));
updates.setCellValueFactory(new PropertyValueFactory<NodeInfo, Button>("NoOfUpdates"));
updates.prefWidthProperty().bind(tableView.widthProperty().divide(6));
tableView.setItems(sourceTree);
The following is a SSCCE code I have simulated your use case. At the result there was no table disturbing, or pane shrinking. Test it yourself and compare it with yours:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class PaneDemo extends Application {
private TableView<NodeInfo> table = new TableView<NodeInfo>();
private final ObservableList<NodeInfo> data = FXCollections.observableArrayList();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setWidth(450);
stage.setHeight(500);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<NodeInfo, String>("firstName"));
TableColumn paneCol = new TableColumn("Pane");
paneCol.setMinWidth(100);
paneCol.setCellValueFactory(new PropertyValueFactory<NodeInfo, TitledPane>("titledPane"));
for (int i = 0; i < 5; i++) {
TitledPane pane = new TitledPane("title " + i, new Text("text " + i));
data.add(new NodeInfo("name " + i, pane));
}
table.setItems(data);
table.getColumns().addAll(firstNameCol, paneCol);
Button btn = new Button("add new item");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
TitledPane pane = new TitledPane("title new", new Text("text new"));
data.add(new NodeInfo("name new", pane));
}
});
final VBox vbox = new VBox(20);
vbox.getChildren().addAll(table, btn);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class NodeInfo {
private final SimpleStringProperty firstName;
private TitledPane titledPane;
private NodeInfo(String fName, TitledPane titledPane) {
this.firstName = new SimpleStringProperty(fName);
this.titledPane = titledPane;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public TitledPane getTitledPane() {
return titledPane;
}
public void setTitledPane(TitledPane fName) {
titledPane = fName;
}
}
}

JavaFX table- how to add components?

I have a swing project that uses many JTables to display all sorts of things from text to panels to a mix of buttons and check boxes. I was able to do this by overwriting the table cell renderer to return generic JComponents. My question is can a similar table be made using JavaFx?
I want to update all my tables in the project to use JavaFx to support gestures mostly. It seems that TableView is the JavaFx component to use and I tried adding buttons to it but when displayed it shows the string value of the button, not the button itself. It looks like I have to overwrite the row factory or cell factory to do what I want but there are not a lot of examples. Here is the code I used as an example that displays the button as a string.
import javax.swing.JButton;
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.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class GestureEvents extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com","The Button"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com","The Button"),
new Person("Ethan", "Williams", "ethan.williams#example.com","The Button"),
new Person("Emma", "Jones", "emma.jones#example.com","The Button"),
new Person("Michael", "Brown", "michael.brown#example.com","The Button")
);
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"));
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 btnCol = new TableColumn("Buttons");
btnCol.setMinWidth(100);
btnCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("btn"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, btnCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final JButton btn;
private Person(String fName, String lName, String email, String btn) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
this.btn = new JButton(btn);
}
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);
}
public JButton getBtn(){
return btn;
}
public void setBtn(String btn){
}
}
public static class ButtonPerson{
private final JButton btn;
private ButtonPerson(){
btn = new JButton("The Button");
}
public JButton getButton(){
return btn;
}
}
}
Edit: After investigating further I've found examples that override cell graphics using predefined cell types like text and checks. It is not clear if any generic jfx component can be placed in a cell like a JFXPanel. This is unlike JTable, since using a JTable I can place anything that inherits from JComponent as long as I setup the render class correctly. If someone knows how (or if it's even possible) to place a JFXPanel in a cell or a other generic JFx component like a Button that would be very helpful.
Issues with your Implementation
You can embed JavaFX components in Swing applications (by placing the JavaFX component in a JFXPanel). But you can't embed a Swing component in JavaFX (unless you are using JavaFX 8+).
JavaFX has it's own button implementation anyway, so there is no reason to embed a javax.swing.JButton on a JavaFX scene, even if you were using Java8 and it would work.
But that won't fix all of your issues. You are providing a cell value factory for your button column to supply buttons, but not supplying a custom cell rendering factory. The default table cell rendering factory renders the toString output on the respective cell value, which is why you just see the to string representation of the button in your table implementation.
You are putting buttons in your Person object. Don't do that - they don't belong there. Instead, dynamically generate a button in the cell rendering factory. This allows you to take advantage of a table's virtual flow technology whereby it only creates visual nodes for what you can see on the screen, not for every element in the table's backing data store. For example, if there are 10 rows visible on the screen and 10000 elements in the table, only 10 buttons will be created rather than 10000.
How to fix it
Use JavaFX Button instead instead of javax.swing.JButton.
Provide a cell rendering factory for your button.
Generate a button in the table cell rather than in the Person.
To set the button (or any arbitrary JavaFX node) in the table cell, use the cell's setGraphic method.
Correct Sample Code
This code incorporates the suggested fixes and a couple of improvements.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class GestureEvents extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com","Coffee"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com","Fruit"),
new Person("Ethan", "Williams", "ethan.williams#example.com","Fruit"),
new Person("Emma", "Jones", "emma.jones#example.com","Coffee"),
new Person("Michael", "Brown", "michael.brown#example.com","Fruit")
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("Table View Sample");
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
final Label actionTaken = new Label();
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<Person, Person> btnCol = new TableColumn<>("Gifts");
btnCol.setMinWidth(150);
btnCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Person>, ObservableValue<Person>>() {
#Override public ObservableValue<Person> call(CellDataFeatures<Person, Person> features) {
return new ReadOnlyObjectWrapper(features.getValue());
}
});
btnCol.setComparator(new Comparator<Person>() {
#Override public int compare(Person p1, Person p2) {
return p1.getLikes().compareTo(p2.getLikes());
}
});
btnCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() {
#Override public TableCell<Person, Person> call(TableColumn<Person, Person> btnCol) {
return new TableCell<Person, Person>() {
final ImageView buttonGraphic = new ImageView();
final Button button = new Button(); {
button.setGraphic(buttonGraphic);
button.setMinWidth(130);
}
#Override public void updateItem(final Person person, boolean empty) {
super.updateItem(person, empty);
if (person != null) {
switch (person.getLikes().toLowerCase()) {
case "fruit":
button.setText("Buy fruit");
buttonGraphic.setImage(fruitImage);
break;
default:
button.setText("Buy coffee");
buttonGraphic.setImage(coffeeImage);
break;
}
setGraphic(button);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
actionTaken.setText("Bought " + person.getLikes().toLowerCase() + " for: " + person.getFirstName() + " " + person.getLastName());
}
});
} else {
setGraphic(null);
}
}
};
}
});
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, btnCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 10, 10, 10));
vbox.getChildren().addAll(label, table, actionTaken);
VBox.setVgrow(table, Priority.ALWAYS);
stage.setScene(new Scene(vbox));
stage.show();
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private final SimpleStringProperty likes;
private Person(String fName, String lName, String email, String likes) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
this.likes = new SimpleStringProperty(likes);
}
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);
}
public String getLikes() {
return likes.get();
}
public void setLikes(String likes) {
this.likes.set(likes);
}
}
// icons for non-commercial use with attribution from: http://www.iconarchive.com/show/veggies-icons-by-iconicon/bananas-icon.html and http://www.iconarchive.com/show/collection-icons-by-archigraphs.html
private final Image coffeeImage = new Image(
"http://icons.iconarchive.com/icons/archigraphs/collection/48/Coffee-icon.png"
);
private final Image fruitImage = new Image(
"http://icons.iconarchive.com/icons/iconicon/veggies/48/bananas-icon.png"
);
}
On Using Swing Components in JavaFX
After investigating further I've found examples that override cell graphics using predefined cell types like text and checks. It is not clear if any generic jfx component can be placed in a cell like a JFXPanel.
A JFXPanel is for embedding a JavaFX component in Swing not a Swing component in JavaFX, hence it would make absolutely no sense to try to place a JFXPanel in a JavaFX TableView. This is why you find no examples of anybody attempting such a thing.
This is unlike JTable, since using a JTable I can place anything that inherits from JComponent as long as I setup the render class correctly.
A JavaFX TableView is similar to a Swing JTable in this regard. Instead of a JComponent, a Node is the basic building block for JavaFX - they are analogous, though different. You can render any Node in a JavaFX TableView as long as you supply the appropriate cell factory for it.
In Java 8, there is a SwingNode that can contain a Swing JComponent. A SwingNode allows you to render any Swing component inside a JavaFX TableView.
The code to use a SwingNode is very simple:
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.*;
public class SwingFx extends Application {
#Override public void start(Stage stage) {
SwingNode swingNode = new SwingNode();
SwingUtilities.invokeLater(() -> swingNode.setContent(new JButton("Click me!")));
stage.setScene(new Scene(new StackPane(swingNode), 100, 50));
stage.show();
}
public static void main(String[] args) { launch(SwingFx.class); }
}
Alternative Implementation
Basically what I'm trying to do is add gesture scrolling support to my java project so the user can 'flick' through the tables and tabs
Though Java 8 should allow you to achieve exactly what you want, if you prefer to use an older Java version, you could use the Swing based Multitouch for Java (MT4J) system for this instead of JavaFX.

Resources