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