a.disableProperty().bind(b.visibleProperty()) 在 Java FX 10 中导致无效元素渲染

a.disableProperty().bind(b.visibleProperty()) causes invalid element rendering in Java FX 10

这是 Java10 中的回归,请参阅错误报告以获取进一步更新:JDK-8204949


考虑以下 Java FX 代码,其中 b 开始不可见: a.disableProperty().bind(b.visibleProperty())

如果使用此类代码的应用程序是 运行 在 Java 10 VM 上,那么从第一次 b 变得可见时,a 将始终呈现为它被禁用了。

当然,在 a 未真正禁用期间(尽管呈现为灰色覆盖),您仍然可以与该元素进行交互。例如。您可以在输入控件中编辑文本,单击 buttons/links 等等,事件得到正确传播等等。

如果应用程序 运行 在 Java 8 VM 上,它会按预期工作。

我目前没有EOS Java 9,所以我只测试过:


Java10 的示例:

b 有机会变得可见之前,它在第一次启动时的样子:


一个 SSCCE(对于一个有点晦涩的问题是强制性的;a 将是 propertyProviderb 将是 invalidRenderFieldinvalidRenderButton):

package application;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        System.out.println("Executing start(Stage) in " + Thread.currentThread().getName());
        try {
            TextField invalidRenderField = new TextField();
            Button invalidRenderButton = new Button("Click to reproduce");
            Label propertyProvider = new Label();
            propertyProvider.setVisible(false);
            VBox invalidRenderParent = new VBox(invalidRenderField, invalidRenderButton, propertyProvider);
            TitledPane invalidRenderPane = new TitledPane("Incorrectly rendered elements", invalidRenderParent);
            Accordion root = new Accordion(invalidRenderPane);
            root.setExpandedPane(invalidRenderPane);

            invalidRenderField.disableProperty().bind(propertyProvider.visibleProperty());
            invalidRenderButton.disableProperty().bind(propertyProvider.visibleProperty());
            invalidRenderButton.setOnAction(e -> {
                System.out.println("Executing 1st onAction(ActionEvent) in " + Thread.currentThread().getName());

                propertyProvider.setVisible(true);
                propertyProvider.setText("At this point of time you cannot modify the field or click the button");

                Task<Void> task = new Task<>() {
                    @Override
                    protected Void call() throws Exception {
                        System.out.println("Executing non-JFX code in " + Thread.currentThread().getName());
                        Thread.sleep(3_000L);
                        return null;
                    }
                };
                task.setOnSucceeded(e2 -> {
                    System.out.println("Executing onSuccess(WorkerStateEvent) in " + Thread.currentThread().getName());

                    propertyProvider.setVisible(false);

                    Label infoLabel = new Label("Either click the button below or just select the previous pane within the accordion");
                    Button closePlaceholderButton = new Button("View failure");
                    VBox placeholder = new VBox(infoLabel, closePlaceholderButton);
                    TitledPane placeholderPane = new TitledPane("Placeholder", placeholder);

                    closePlaceholderButton.setOnAction(e3 -> {
                        System.out.println("Executing 2nd onAction(ActionEvent) in " + Thread.currentThread().getName());
                        root.getPanes().remove(placeholderPane);
                        root.setExpandedPane(invalidRenderPane);
                    });

                    root.getPanes().add(1, placeholderPane);
                    root.setExpandedPane(placeholderPane);
                });
                new Thread(task, "WhateverThread").start();
            });

            Scene scene = new Scene(root, 800, 450);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println("Executing main(String[]) in " + Thread.currentThread().getName());
        launch(args);
    }
}

单击 'Click to reproduce' 一次后的 SSCCE 输出:

Executing main(String[]) in main
Executing start(Stage) in JavaFX Application Thread
Executing 1st onAction(ActionEvent) in JavaFX Application Thread
Executing non-JFX code in WhateverThread
Executing onSuccess(WorkerStateEvent) in JavaFX Application Thread

正如预期的那样,JavaFX 代码在指定的线程上执行。


问题是,我是不是做错了什么 and/or 遗漏了一些明显的东西?我似乎无法用这段代码找到任何东西"wrong",它看起来很正常,但它却出现故障。这是一个错误吗(我需要 lrn2search)?

总而言之,欢迎提出解决方法的任何建议。我需要此代码(或等效的解决方法)才能在 Java 10 上工作,没有任何借口。

首先,我想确认我观察到的行为与您相同。

  • Windows 10 x64 主页
  • Java 1.8.0_172 x64 -- 按预期工作
  • Java 10.0.1 x64 -- 失败(显示为已禁用但实际上并未禁用)

这似乎是一个错误,您应该报告它。


可能的解决方法

我发现如果您将以下代码添加到所有受影响的节点,相关节点将正确呈现:

node.disableProperty().addListener((observable, oldValue, newValue) -> {
    if (!newValue) { // if no longer disabled
        node.applyCss();
    }
});

如果您有很多节点可以禁用并且受与您问题中描述的相同事件序列的影响,这可能会变得乏味。创建某种类型的实用方法来处理这个问题可能是明智的。

编辑

我发现让 propertyProvider 不可见的顺序会影响我提供的解决方法。如果你让 propertyProvider 不可见 之后显示新的 TitledPane 然后,当你回到另一个 TitledPane 时,TextFieldButton 在未禁用时仍呈现为禁用。还没有找到解决方法。