How to fix rendering bug in javafx (ComboBox, ListView)

user27772 :

In a quite complex javafx application I faced a possible rendering bug. The last two days I could track it down to the following simple application. The following SSCCE demonstrates that in certain circumstances some javafx components are not correctly rendered. As a result, ComboBox and ListView do not show changed content:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ComboManagedBug extends Application {
  public static void main(String[] args) {
    launch(args);
  }

  public void start(Stage primaryStage) {
    // add combo box
    ComboBox<String> combo = new ComboBox<>();
    combo.setPromptText("Choose a value...");
    combo.getItems().setAll("1", "2", "3");

    // add list view
    ListView<Label> list = new ListView<>();

    // add "add" button
    Button add = new Button("Add");
    add.setOnAction(e -> list.getItems().add(
      new Label(combo.getSelectionModel().getSelectedItem())));

    // add tab pane
    Tab tab1 = new Tab("First", new VBox(combo, add, list));
    Tab tab2 = new Tab("Second");
    TabPane tabs = new TabPane(tab1, tab2);
    tabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE); // important!

    // add "next" and "cancel" buttons at bottom
    Button next = new Button("Next");
    Button cancel = new Button("Cancel (Triggers refresh)");
    HBox buttons = new HBox(next, cancel);

    // install tab listener
    tabs.getSelectionModel().selectedItemProperty().addListener((a, b, c) -> {
      // intention is to show next button only on first tab
      boolean firstTab = c == tab1;
      next.setVisible(firstTab);
      next.setManaged(firstTab); // important!
    });

    // show
    VBox root = new VBox(new VBox(tabs, buttons)); // important!
    Scene scene = new Scene(root, 400, 400);
    primaryStage.setTitle("ComboBox/ListView Rendering Bug Demo");
    primaryStage.setScene(scene);
    primaryStage.show();
  }
}

Steps to reproduce:

  1. Launch program
  2. Choose combo value -> ComboBox shows chosen value -> OK
  3. Click "Add" -> value gets added to the ListView and is shown -> OK
  4. Activate second tab
  5. Activate first tab
  6. Choose a combo value being different than the previous one -> value in ComboBox does not change -> BUG
  7. Click "Add" -> ListView content does not change -> BUG
  8. Click "Cancel" at the bottom -> although there was no listener added to this button, both the ComboBox and the ListView show the correct values now. So, both containers seem to contain the correct values but they won't be rendered correctly until some UI refreshing is triggered.

Note that there are three important reqiurements to reproduce this bug (marked with "important"):

  1. Toggle managed state of the "Next" button when toggling the tabs to hide this button (usual way to hide a node along with visible state of the node, see JavaFX - setVisible doesn't "hide" the element)
  2. TabClosingPolicy.UNAVAILABLE (a very common case)
  3. a VBox in a VBox (which can easily happen in real life when nesting different javafx component nodes).

Is this bug already known, and does anybody know a workaround for this? I tried Platform.runLater(cancel.fire()) and similar things but without success.

Thank you for any hints, Peter.

Btw., apart from this, our company uses javafx for some years now. In our experience, it is very reliable and programming javafx is fun. I hope there is a simple solution for our problem :)

user27772 :

As discussed in the comments of the original post, the bug does not occur in JavaFX 8 (Oracle) but in later versions (OpenJFX). I found the following workaround:

public static void fixTabRendering(TabPane tabs) {
    if (tabs.getTabClosingPolicy() != TabClosingPolicy.UNAVAILABLE) return;
    tabs.setTabClosingPolicy(TabClosingPolicy.SELECTED_TAB);
    for (Node node : tabs.lookupAll(".tab-close-button")) {
        // hide "close" button to imitate TabClosingPolicy.UNAVAILABLE
        node.setStyle("-fx-background-color:transparent;-fx-shape:null;-fx-pref-width:0.001");
    }
}

This code should be run after showing the stage (otherwise lookupAll() returns null) as well as after adding tabs to the tab pane. The latter could be achieved via a tab listener:

tabs.getTabs().addListener((Change<?> change) ->
    Platform.runLater(() -> fixTabRendering(tabs)));

Platform.runLater() is required because otherwise lookupAll() may not return the nodes of the added tabs.

Maybe this solution can help someone :)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=328640&siteId=1