在Kotlin中使用JavaFX我遇到了一个奇怪的问题,我在下面贴了一个最小的例子.问题是,如果不包括Button,那么侦听器不会打印出任何内容. 按钮的操作只需要以某种方式访问对象h,然后触发侦听器.按钮不需要点击,也就是说,动作代码不需要执行.它必须在那里.

import javafx.application.Application
import javafx.beans.property.SimpleObjectProperty
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.control.ComboBox
import javafx.scene.layout.Region
import javafx.scene.layout.VBox
import javafx.stage.Stage

fun main() {
    Application.launch(ExpRunner::class.java)
}

class ExpRunner : Application() {

    override fun start(primaryStage: Stage) {

        val myScene = Scene(buildView(), 400.0, 400.0)
        primaryStage.apply { title = "Strange" }
            .apply { scene = myScene }
        primaryStage.show()

    }

    fun buildView(): Region {
        val h = Holder()
        val cb = ComboBox<String>().apply { valueProperty().bindBidirectional(h.prop) }
            .apply { listOf("a", "b").forEach { items.add(it) } }
        h.prop.apply {
            addListener { _, _, v -> println("Prop value changed: $v") }
            return VBox(
                10.0,
                // If this button is not included, the listener above doesn't get triggered??
                Button("Check").apply { onAction = EventHandler<ActionEvent> { _ -> println(h.hashCode()) } },
                cb,
            )

        }
    }
}

class Holder {
    val prop = SimpleObjectProperty<String>("a")
}

要使用下面的代码进行复制,只需运行,然后更改组合框 Select .它将打印出一行,说明props 已更改.但是,如果您注释掉将按钮添加到VBox中的行,则在更改组合框 Select 时,不会打印出任何内容.

如有任何解释,我将不胜感激.

谢谢.

推荐答案

绑定使用幕后的弱监听程序.这意味着,如果没有对对象的其他活动引用,绑定将不会阻止对象被垃圾回收.

在您发布的代码中(我已经将其翻译成下面的Java,主要是为了理解问题),当start()方法退出时,除了到组合框valueProperty的双向绑定之外,不再有对Holder实例的引用.这意味着Holder实例有资格进行垃圾回收.


import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.List;

public class ExpRunner extends Application {

    @Override
    public void start(Stage stage) {
        var myScene = new Scene(buildView(), 400, 400);
        stage.setTitle("Strange");
        stage.setScene(myScene);
        stage.show();
    }

    private Region buildView() {
        var h = new Holder();
        var cb = new ComboBox<String>();
        cb.valueProperty().bindBidirectional(h.prop);
        List.of("a", "b").forEach(cb.getItems()::add);
        h.prop.addListener((obs, old, v) -> System.out.printf("Value changed: %s%n", v));
        return new VBox(10, cb);
    }

    class Holder {
        ObjectProperty<String> prop = new SimpleObjectProperty<>("a");
    }
}

(这可能不会在所有平台上重新引发该问题.如果运行它生成h.prop上的监听器的输出,则添加myScene.setOnMouseClicked(e -> System.gc());.然后,点击场景的空白部分应该会强制监听程序停止被调用.)

但是,当您创建具有其操作侦听器的按钮时,该按钮具有对Holder的引用.按钮被其父级(VBox)引用,其父级被Scene引用,Scene又被Stage引用,Stage的引用超出了start()的范围.因此,按钮(特别是它的动作侦听器)防止Holder被垃圾收集,并且它的侦听器被调用.

更自然的解决方法是在start()方法之外创建对Holder的引用:


import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.List;

public class ExpRunner extends Application {

    private Holder h;
    @Override
    public void start(Stage stage) {
        var myScene = new Scene(buildView(), 400, 400);
        stage.setTitle("Strange");
        stage.setScene(myScene);
        stage.show();
    }

    private Region buildView() {
        h = new Holder();
        var cb = new ComboBox<String>();
        cb.valueProperty().bindBidirectional(h.prop);
        List.of("a", "b").forEach(cb.getItems()::add);
        h.prop.addListener((obs, old, v) -> System.out.printf("Value changed: %s%n", v));
        return new VBox(10, cb);
    }

    class Holder {
        ObjectProperty<String> prop = new SimpleObjectProperty<>("a");
    }
}

请注意,在大多数应用程序中,您可能向这个Holder对象注册的任何侦听器都可能被持久模型中的元素引用(如果您使用某种类似MVC的设计),或者被场景图间接引用.因此,在大多数实际情况下,这种过早的垃圾收集不太可能发生.然而,值得注意的是为什么会发生这种情况.

Kotlin相关问答推荐

API迁移到Spring Boot 3后,Spring Security无法工作

为什么";";.equals(1)在柯特林语中是有效的,但";";=1是无效的?

try 一次性插入多条记录时,JOOQ连接为空错误

如何在 Kotlin 中初始化 Short 数组?

为什么 Kotlin main 函数需要 @JVMStatic 注解?

如何使用成员引用在 Kotlin 中创建属性的分层路径

高效匹配两个 Flux

is return inside function definition 也是 kotlin 中的表达式

从字符串列表构建字符串

类型不匹配:Required: Context, Found: Intent

对列表中数字的子集求和

无法从 XML 访问 NavHostFragment

将 jetpack compose 添加到现有元素

将协同路由调用放在存储库或ViewModel中哪个更好?

如何在Kotlin中创建填充空值的通用数组?

TypeConverter()在Android的TypeConverter错误中具有私有访问权限

如何使用协调器布局和行为在CardView上完成此动画?

Kotlin中的属性(properties)和参数(parameters)有什么区别?

Kotlin 的数据类 == C# 的 struct ?

为什么 Kotlin 会收到这样的 UndeclaredThrowableException 而不是 ParseException?