On Mon, 25 Jul 2022 17:44:25 GMT, Andy Goryachev <[email protected]> wrote:
>> **Issue:**
>> When the `TableView` is set with built-in `CONSTRAINED_RESIZE_POLICY`, the
>> horizontal scroll bar keeps flickering when the `TableView` width is reduced.
>>
>> **Cause:**
>> The table columns widths are recalculated only if the difference of the
>> `TableView` width and the total columns width is greater than 1px. Because
>> of this improper calculations, if the `TableView` width is reduced by 1px,
>> the columns widths are not updated. Which results to computing for
>> horizontal scroll bar visibility in `VirtualFlow` to improper results and
>> let the horizontal scroll bar to be visible.
>>
>> Where as if the `TableView` width is reduced by more than 1px, the
>> calculations are done correctly and the horizontal scroll bar visibility is
>> turned off. Because of this behaviour, it looks like the horizontal scroll
>> bar is flickering when the `TableView` width is reduced (let’s say by
>> dragging).
>>
>> To confirm this behaviour, please find the below example that showcases the
>> issue:
>> When the tableView width is reduced/increased by 1px, the column widths are
>> recalculated only after every alternate 1px change. Whereas if the tableView
>> width is reduced/increased by more than 1px (say 10px), the column widths
>> are calculated correctly.
>>
>>
>> import javafx.application.Application;
>> 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.*;
>> import javafx.scene.layout.HBox;
>> import javafx.scene.layout.VBox;
>> import javafx.stage.Stage;
>>
>> public class ConstrainedResizePolicyIssueDemo 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> priceCol = new TableColumn<>("Price");
>> priceCol.setMinWidth(100);
>> priceCol.setMaxWidth(150);
>> priceCol.setCellValueFactory(param ->
>> param.getValue().priceProperty());
>>
>> TableColumn<Person, String> totalCol = new TableColumn<>("Total");
>> totalCol.setMinWidth(100);
>> totalCol.setMaxWidth(150);
>> totalCol.setCellValueFactory(param ->
>> param.getValue().totalProperty());
>>
>> ObservableList<Person> persons = FXCollections.observableArrayList();
>> persons.add(new Person("Harry", "200.00", "210.00"));
>> persons.add(new Person("Jacob", "260.00", "260.00"));
>>
>> TableView<Person> tableView = new TableView<>();
>> tableView.getColumns().addAll(fnCol, priceCol, totalCol);
>> tableView.setItems(persons);
>> tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
>> tableView.setMinWidth(500);
>> tableView.maxWidthProperty().bind(tableView.minWidthProperty());
>>
>> Button button1 = new Button("Reduce 1px");
>> button1.setOnAction(e ->
>> tableView.setMinWidth(tableView.getMinWidth() - 1));
>> Button button2 = new Button("Reduce 10px");
>> button2.setOnAction(e ->
>> tableView.setMinWidth(tableView.getMinWidth() - 10));
>> Button button3 = new Button("Reset");
>> button3.setOnAction(e -> tableView.setMinWidth(500));
>> Button button4 = new Button("Increase 1px");
>> button4.setOnAction(e ->
>> tableView.setMinWidth(tableView.getMinWidth() + 1));
>> Button button5 = new Button("Increase 10px");
>> button5.setOnAction(e ->
>> tableView.setMinWidth(tableView.getMinWidth() + 10));
>>
>> HBox row = new HBox(button1, button2, button3, button4, button5);
>> row.setSpacing(10);
>> TextArea output = new TextArea();
>>
>> addWidthListeners(tableView, output);
>> VBox root = new VBox(row, new Group(tableView), output);
>> root.setSpacing(10);
>> root.setPadding(new Insets(10));
>>
>> Scene scene = new Scene(root);
>> primaryStage.setScene(scene);
>> primaryStage.setTitle("Constrained Resize Policy Issue TableView");
>> primaryStage.show();
>> }
>>
>> private void addWidthListeners(TableView<Person> tableView, TextArea
>> output) {
>> tableView.widthProperty().addListener((obs, old, val) -> {
>> String str = "Table width changed :: " + val + "\n";
>> output.setText(output.getText() + str);
>> output.positionCaret(output.getText().length());
>> });
>> tableView.getColumns().forEach(column -> {
>> column.widthProperty().addListener((obs, old, width) -> {
>> String str = " ---> " + column.getText() + " : width changed
>> to : " + column.getWidth()+"\n";
>> output.setText(output.getText() + str);
>> output.positionCaret(output.getText().length());
>> });
>> });
>> }
>>
>> class Person {
>> private StringProperty firstName = new SimpleStringProperty();
>> private StringProperty price = new SimpleStringProperty();
>> private StringProperty total = new SimpleStringProperty();
>>
>> public Person(String fn, String price, String total) {
>> setFirstName(fn);
>> setPrice(price);
>> setTotal(total);
>> }
>>
>> public StringProperty firstNameProperty() {
>> return firstName;
>> }
>>
>> public void setFirstName(String firstName) {
>> this.firstName.set(firstName);
>> }
>>
>> public StringProperty priceProperty() {
>> return price;
>> }
>>
>> public void setPrice(String price) {
>> this.price.set(price);
>> }
>>
>> public StringProperty totalProperty() {
>> return total;
>> }
>>
>> public void setTotal(String total) {
>> this.total.set(total);
>> }
>> }
>> }
>>
>>
>> **Fix:**
>> On investigating the code, it is noticed that there is an explicit line of
>> code to check if the difference in widths is greater than 1px. I think this
>> should be greater than 0px. Because we need to recompute the columns widths
>> even if the width is increased by 1px.
>>
>> The part of the code that need to be updated is in TableUtil.java ->
>> constrainedResize(..) method -> line 226
>>
>> `if (Math.abs(colWidth - tableWidth) > 1) {`
>>
>> If I update the value 1 to 0, the issue is fixed and the horizontal scroll
>> bar visibility is computed correctly.
>
> Two comments:
> 1. when doing a floating point arithmetic (which is imprecise) it is possible
> to get a value which is close, but not exactly the same as expected result.
> In other words (colWidth - tableWidth) may be close to 0, but != 0. Should
> we take this into account?
> 2. With TableView.CONSTRAINED_RESIZE_POLICY set, the application developer
> expects to see the horizontal scroll bar NEVER. Is this the only place where
> h.s.b. gets made visible?
@andy-goryachev-oracle Below are my comments for the points you raised:
1. I agree with this and upon inspecting the current code, I noticed that this
was already considered for some other bounds checking within the same method
`TableUtil.java` -> `constrainedResize(...)`.
// Check for zero. This happens when the distribution of the delta
// finishes early due to a series of "fixed" entries at the end.
// In this case, lowerBound == upperBound, for all subsequent terms.
double newSize;
if (Math.abs(totalLowerBound - totalUpperBound) < .0000001) {
newSize = lowerBound;
} else {
double f = (target - totalLowerBound) / (totalUpperBound - totalLowerBound);
newSize = Math.round(lowerBound + f * (upperBound - lowerBound));
}
Will it be ok if I create a variable named `double EPSILON=.0000001` and use
this in both the places?
2. I don't think setting TableView.CONSTRAINED_RESIZE_POLICY means to always
hide the horizontal scroll bar. The most common use case with the
CONSTRAINED_RESIZE_POLICY, is to auto stretch the columns if there is enough
space, and set to the minimum widths and show horizontal scroll bar if there is
not enough space. I included an example in this stackoverflow
[question](https://stackoverflow.com/questions/73060277/horizontal-scrollbar-is-visible-in-tableview-with-constrained-resize-policy)
and the gif show-casing the requirement (2nd gif). And the current code
already handles this very well.
-------------
PR: https://git.openjdk.org/jfx/pull/848