How to change selection behavior of TableView? - javafx

I've set multiple selection mode to my TableView and I want multiple rows to be selected with Lclick, not Ctrl + Lclick. Is there a simple way to do this.
I tried table.setOnMouseClicked() with null implementation but it doesn't prevents target row to be selected and previously selected row to be unselected, either setOnMousePressed() or setOnMouseReleased().
I really don't want to re-implement TableView.TableViewSelectionModel. There should be a layer between click and calling TableView.TableViewSelectionModel.clearAndSelect()
UPD
I've just found a few questions with the similar problem but not exactly the same. Those guys wanted to drag and select multiple, when I want to select one-by-one, but without keyboard.

In general, changing behavior for JavaFX UI controls is difficult (or impossible), and generally I'd recommend just accepting the default behaviors (even if they're not what your users might really want).
In this case, I think you can make this work by adding an event filter to the table rows, implementing the desired selection behavior and consuming the event (to prevent the default behavior getting invoked).
Here's an example:
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.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MultipleSelectTable extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
row.addEventFilter(MouseEvent.MOUSE_PRESSED, e-> {
if (! row.isEmpty() && e.getClickCount() == 1) {
Person person = row.getItem() ;
if (table.getSelectionModel().getSelectedItems().contains(person)) {
int index = row.getIndex() ;
table.getSelectionModel().clearSelection(index);
} else {
table.getSelectionModel().select(person);
}
e.consume();
}
});
return row ;
});
table.getColumns().add(column("First Name", Person::firstNameProperty));
table.getColumns().add(column("Last Name", Person::lastNameProperty));
table.getColumns().add(column("Email", Person::emailProperty));
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")
);
BorderPane root = new BorderPane(table);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String text, Function<S,ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(text);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(200);
return col ;
}
private static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final 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 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);
}
}

Related

JavaFX tableview Enable and Disable row selection

As a part of my project i want display a tableview which should be disabled for some time(on editting time nb:not table editting).So that i got a working code which will disable the tableview.This is the working code,
table.setSelectionModel(null);
So my problem is after editing process is over when click a button i want to enable it back,but unfortunately i could'nt find any alternative code for that.Any one please suggest me the code which will enable the row selection.Any answer will appreciable.
You can retrieve the default selection model when you create the table:
TableView<T> table = new TableView<>();
TableViewSelectionModel<T> defaultSelectionModel = table.getSelectionModel();
where T is the type for your table. (Of course, if you are using FXML, just put the second line in the controller's initialize() method.)
Then to disable row selection do
table.setSelectionModel(null);
and to enable it again
table.setSelectionModel(defaultSelectionModel);
Here is a SSCCE:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TableView.TableViewSelectionModel;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableWithDisabledSelection extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
TableViewSelectionModel<Person> defaultSelectionModel = table.getSelectionModel();
table.getColumns().add(column("First Name", Person::firstNameProperty));
table.getColumns().add(column("Last Name", Person::lastNameProperty));
table.getColumns().add(column("Email", Person::emailProperty));
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")
);
CheckBox enableSelection = new CheckBox("Enable selection");
enableSelection.setSelected(true);
enableSelection.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
table.setSelectionModel(defaultSelectionModel);
} else {
table.setSelectionModel(null);
}
});
BorderPane root = new BorderPane(table);
BorderPane.setAlignment(enableSelection, Pos.CENTER);
BorderPane.setMargin(enableSelection, new Insets(5));
root.setBottom(enableSelection);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title, Function<S,Property<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return col ;
}
public static void main(String[] args) {
launch(args);
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final 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);
}
}
}
I handled this situation with rowfactory like this;
tableView.setRowFactory(param -> new TableRow<Model>()
{
#Override
protected void updateItem(Model item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
disableProperty().bind(item.getFocusable().not());
}
}
});
so, you can bind the disableproperty with suitable property of your tableview model.

Update index of row in Tableview in JavaFX [duplicate]

I have a JavaFX TableView that I'm populating with an ObservableList of Tasks. I've been trying to create a column that displays the index of each row, which serves as the ID for the tasks in the table, but I've tried the method here and similar methods from other sources with little success.
My code for reference, which has no superficial errors (as far as Eclipse can tell):
#FXML private TableColumn<Task, String> taskIndexCol;
Callback<TableColumn<Task, String>, TableCell<Task, String>> cb =
new Callback<TableColumn<Task, String>, TableCell<Task, String>>(){
#Override
public TableCell<Task, String> call(TableColumn<Task, String> col) {
TableCell<Task, String> cell = new TableCell<Task, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setText("");
} else {
setText(getIndex()+"");
}
}
};
return cell;
}
};
taskIndexCol.setCellFactory(cb);
Currently, my code gives me a NullPointerException when I try to set the CellFactory of the column. I've tried it with a populated task list, but that didn't help. I've been stumped for a very long time -- and theoretically this should be pretty easy, since it's just numbering the rows? It feels like I'm jumping through a million hoops to do something frustratingly simple.
Edit: The last line gives me the NPE.
It's impossible to tell the cause of the Null pointer exception, because you haven't shown the stack trace, identified the line which throws the exception, or posted enough of your code (none of the code in your callback can throw a null pointer exception, so something is wrong somewhere else).
For your actual cell implementation, you didn't show if you had a cellValueFactory set on the column. If you don't, then the item will always be null, and so you will never see any text in the cells in that column. You can check the empty property (or method parameter) as a means to check if the cell is in an empty row or one with actual data. (Note this means the column really doesn't need to provide any data at all: it can be a TableColumn<Task, Void>.)
Additionally, it's probably safer to rely on using updateIndex(...) instead of updateItem(...). updateIndex is guaranteed to be called when the index changes; if you implement updateItem you are assuming the index is set before the item, which means you are relying on an implementation detail.
Your code is a lot shorter and easier to read if you use Java 8 lambda expressions:
taskIndexCol.setCellFactory(col -> new TableCell<Task, String>() {
#Override
protected void updateIndex(int index) {
super.updateIndex(index);
if (isEmpty() || index < 0) {
setText(null);
} else {
setText(Integer.toString(index));
}
}
});
or alternatively
taskIndexCol.setCellFactory(col -> {
TableCell<Task, String> cell = new TableCell<>();
cell.textProperty().bind(Bindings.when(cell.emptyProperty())
.then("")
.otherwise(cell.indexProperty().asString()));
return cell ;
});
Here is a SSCCE:
import java.util.function.Function;
import static javafx.beans.binding.Bindings.when ;
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.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewWithIndexColumn extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.setEditable(true);
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"));
TableColumn<Person, String> firstNameCol = createColumn("First Name",
Person::firstNameProperty, 150);
TableColumn<Person, String> lastNameCol = createColumn("Last Name",
Person::lastNameProperty, 150);
TableColumn<Person, String> emailCol = createColumn("Email",
Person::emailProperty, 150);
// index column doesn't even need data...
TableColumn<Person, Void> indexCol = createColumn("Index", person -> null, 50);
// cell factory to display the index:
// indexCol.setCellFactory(col -> {
//
// // just a default table cell:
// TableCell<Person, Void> cell = new TableCell<>();
//
// cell.textProperty().bind(
// when(cell.emptyProperty())
// .then("")
// .otherwise(cell.indexProperty().asString()));
//
// return cell ;
// });
indexCol.setCellFactory(col -> new TableCell<Person, Void>() {
#Override
public void updateIndex(int index) {
super.updateIndex(index);
if (isEmpty() || index < 0) {
setText(null);
} else {
setText(Integer.toString(index));
}
}
});
table.getColumns().add(indexCol);
table.getColumns().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.getColumns().add(emailCol);
primaryStage.setScene(new Scene(new BorderPane(table), 600, 400));
primaryStage.show();
}
private <S, T> TableColumn<S, T> createColumn(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 final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
public Person() {
this("", "", "");
}
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(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);
}
}

JavaFX getting cell value on hover

I was searching for a solution on how to get cell values by hovering over them, but haven't found any. I was thinking of a similar solution to this:
Can't select different cells in same row of tableview JavaFx , except not selecting, but only hover. How could I do it? Thanks.
Use a custom cell that updates a property when the mouse enters/exits it:
public class HoverCell extends TableCell<Person, String> {
public HoverCell(StringProperty hoverProperty) {
setOnMouseEntered(e -> hoverProperty.set(getItem()));
setOnMouseExited(e -> hoverProperty.set(null));
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item);
}
}
Complete example:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class HoverTableCells 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);
StringProperty hoveredProperty = new SimpleStringProperty();
firstNameCol.setCellFactory(tc -> new HoverCell(hoveredProperty));
lastNameCol.setCellFactory(tc -> new HoverCell(hoveredProperty));
emailCol.setCellFactory(tc -> new HoverCell(hoveredProperty));
Label currentHover = new Label();
currentHover.textProperty().bind(hoveredProperty);
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")
);
BorderPane root = new BorderPane(table);
BorderPane.setMargin(currentHover, new Insets(10));
root.setTop(currentHover);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class HoverCell extends TableCell<Person, String> {
public HoverCell(StringProperty hoverProperty) {
setOnMouseEntered(e -> hoverProperty.set(getItem()));
setOnMouseExited(e -> hoverProperty.set(null));
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item);
}
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final 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);
}
}

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

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

Resources