如何修复 javafx 中的渲染错误(ComboBox、ListView)

Posted

技术标签:

【中文标题】如何修复 javafx 中的渲染错误(ComboBox、ListView)【英文标题】:How to fix rendering bug in javafx (ComboBox, ListView) 【发布时间】:2020-01-22 11:08:12 【问题描述】:

在一个相当复杂的 javafx 应用程序中,我遇到了一个可能的渲染错误。在过去的两天里,我可以将其归结为以下简单的应用程序。以下 SSCCE 演示了在某些情况下某些 javafx 组件未正确呈现。因此,ComboBox 和 ListView 不会显示更改的内容:

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

重现步骤:

    启动程序 选择组合值 -> 组合框显示选择的值 -> 确定 单击“添加” -> 值被添加到 ListView 并显示 -> 确定 激活第二个标签 激活第一个标签 选择一个与前一个不同的组合值 -> ComboBox 中的值不会改变 -> BUG 点击“添加”->ListView内容不变->BUG 单击底部的“取消”-> 尽管没有向此按钮添加侦听器,但 ComboBox 和 ListView 现在都显示了正确的值。因此,两个容器似乎都包含正确的值,但在触发某些 UI 刷新之前它们不会正确呈现。

请注意,重现此错误有三个重要要求(标有“重要”):

    在切换选项卡以隐藏此按钮时切换“下一步”按钮的托管状态(隐藏节点以及节点的可见状态的常用方法,请参阅JavaFX - setVisible doesn't "hide" the element) TabClos​​ingPolicy.UNAVAILABLE(很常见的情况) VBox 中的 VBox(在现实生活中嵌套不同的 javafx 组件节点时很容易发生这种情况)。

此错误是否已知,是否有人知道解决此问题的方法?我尝试了 Platform.runLater(cancel.fire()) 和类似的东西,但没有成功。

感谢您的任何提示,彼得。

顺便说一句,除此之外,我们公司使用 javafx 已有几年了。根据我们的经验,它非常可靠并且编写 javafx 很有趣。我希望我们的问题有一个简单的解决方案:)

【问题讨论】:

如果您每次在组合框中选择一个项目时都在相关节点上调用#requestLayout(),会发生什么情况?由于单击取消按钮除了触发布局调用由于其状态更改之外不会执行任何与值相关的操作,我怀疑请求布局传递填充就足够了吗? 您使用的是什么 JRE 和什么操作系统? 不要将节点(在您的情况下为标签)作为项目添加到列表中 如果您想在ListView 中添加标签,请使用CellFactoryListView 永远不应使用节点,而应始终使用一些普通的 Java 类型(非原始)或为建模数据而创建的类/对象类型。示例ListView&lt;String&gt;ListView&lt;Integer&gt;ListView&lt;YourCustomObject&gt;。从不ListView&lt;SomeJavaFxNode&gt;. @Sedrick:感谢您的链接。我完全同意,对于大型列表,单元工厂很有意义。此外,感谢确认 Java 12 也显示错误。我也可以确认,在我的旧 Java 8 安装中,代码可以按预期工作。所以这个 bug 很可能是在 JavaFX 9,10 或 11 中引入的。我做了更多的测试:设置TabPane.setClosable(false) 带来与TabClosingPolicy.UNAVAILABLE 相同的效果。到目前为止,唯一的解决方法是通过setStyle() 隐藏选项卡关闭按钮,然后代码也可以正常工作。如果时间允许,我会创建一个错误报告。 【参考方案1】:

正如原始帖子的 cmets 中所讨论的,该错误不会出现在 JavaFX 8 (Oracle) 中,而是出现在更高版本 (OpenJFX) 中。我找到了以下解决方法:

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

此代码应在显示阶段后运行(否则 lookupAll() 返回 null)以及在标签窗格中添加标签后运行。后者可以通过标签监听器实现:

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

Platform.runLater() 是必需的,否则 lookupAll() 可能不会返回已添加选项卡的节点。

也许这个解决方案可以帮助某人:)

【讨论】:

以上是关于如何修复 javafx 中的渲染错误(ComboBox、ListView)的主要内容,如果未能解决你的问题,请参考以下文章

JavaFX FileChooser 引发错误(可能很容易修复,但仍然很困惑)

CSS 修复IE中的滚动渲染错误

修复IE中的滚动渲染错误

如何修复渲染 SLS 失败:Jinja 语法错误:预期令牌',',得到':'?

如何修复反应过多的重新渲染错误?

JavaFX 中的动画渲染效果