gpt4 book ai didi

java - 如何将监听器添加到双向绑定(bind)的对象

转载 作者:塔克拉玛干 更新时间:2023-11-01 23:01:58 26 4
gpt4 key购买 nike

我试图在控制器的initialize()方法中将TextArea的textProperty绑定到StringProperty。

值更改时,侦听器会监听这两个对象,以执行某些行为。

但是有些奇怪的事情发生了。

我建立了一个简单的模型来重现这种情况。

Main.java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
}


public static void main(String[] args) {
launch(args);
}
}

sample.fxml
<?import javafx.scene.layout.GridPane?>

<?import javafx.scene.control.TextArea?>
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10"
prefHeight="300" prefWidth="400">
<TextArea fx:id="textArea"/>
</GridPane>

我认为以上代码与该问题无关。但以防万一,我把它放在这里。

这是控制器。

Controller.java
package sample;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

public class Controller {

@FXML
TextArea textArea;

private StringProperty toBind = new SimpleStringProperty();

public void initialize() {
textArea.textProperty().bindBidirectional(toBind);

textArea.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.print("textArea: ");
System.out.println(newValue);
});

toBind.addListener((observable, oldValue, newValue) -> {
System.out.print("toBind: ");
System.out.println(newValue);
});
}
}

使用此控制器,当我将序列“abcd”输入到文本区域时,我得到:
textArea: a
textArea: ab
textArea: abc
textArea: abcd

似乎未触发toBind对象的change事件。

因此,我尝试在textArea的Listener中打印toBind的值。

新的代码是:
package sample;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

public class Controller {

@FXML
TextArea textArea;

private StringProperty toBind = new SimpleStringProperty();

public void initialize() {
textArea.textProperty().bindBidirectional(toBind);

textArea.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.print("textArea: ");
System.out.println(newValue);

// ----- New statements. -----
System.out.print("toBind value in textArea: ");
System.out.println(toBind.get());
// ----- New statements. -----
});

toBind.addListener((observable, oldValue, newValue) -> {
System.out.print("toBind: ");
System.out.println(newValue);
});
}
}

然后我得到:
toBind: a
textArea: a
toBind value in textArea: a
toBind: ab
textArea: ab
toBind value in textArea: ab
toBind: abc
textArea: abc
toBind value in textArea: abc
toBind: abcd
textArea: abcd
toBind value in textArea: abcd

为什么会这样?该事件已正确触发。

最佳答案

您的绑定和toBind属性正在被垃圾回收。

托马斯·米库拉(Tomas Mikula)在他的blog上对“过早的垃圾收集”问题进行了简要描述。

首先,对于任何试图重现此问题的人来说,请速记一点。由于所描述的行为取决于发生的垃圾回收,因此它可能并不总是发生(取决于内存分配,所使用的GC实现以及其他因素)。如果添加行

root.setOnMouseClicked(e -> System.gc());

start()方法中,然后单击场景中的空白区域将请求垃圾回收,问题将(至少更有可能)在那之后(如果尚未出现)显现出来。

问题是绑定使用 WeakListener侦听属性更改并将这些更改传播到绑定的属性。如果没有其他 Activity 引用,弱侦听器的设计目的是不要阻止对其附加的属性进行垃圾回收。 (其基本原理是避免在属性不再在范围内时强迫程序员强制清理绑定。)

在您的示例代码中,控制器及其属性 toBind可以进行垃圾回收。
start()方法完成后,可以保证拥有的所有引用都是调用 Application时显示的 launch()实例,显示的 Stage以及从其中引用的所有内容。当然,这包括 Scene(由 Stage引用),其 rootroot的子代,它们的子代等,它们的属性以及这些属性中任何一个的(非弱)侦听器。

因此, stage引用了 scene,后者引用了作为其根的 GridPane,并且引用了 TextArea
TextArea附加了对该侦听器的引用,但该侦听器未保留其他引用。

(在第二版代码中,附加到 ChangeListener的非弱 textArea.textProperty()引用了 toBind。因此,在该版本中, ChangeListener防止 toBind进行GC,然后您可以在其上看到来自侦听器的输出。 )

加载FXML时, FXMLLoader创建控制器实例。虽然该控制器实例具有对string属性和文本区域的引用,但事实并非如此。因此,一旦加载完成,就没有对控制器的实时引用,并且可以对其进行定义的 StringProperty进行垃圾回收。文本区域的 textProperty()仅对 toBind上的侦听器进行弱引用,因此,文本区域无法防止 toBind被垃圾回收。

在大多数实际情况下,这将不是问题。除非要在某个地方使用它,否则您不太可能创建此附加的 StringProperty。因此,如果您添加以“自然”方式使用此代码的任何代码,则很可能会看到问题消失。

因此,例如,假设您添加了一个标签:

<Label fx:id="label" GridPane.rowIndex="1"/>

并将其文本绑定到属性:
  public void initialize() {
textArea.textProperty().bindBidirectional(toBind);

textArea.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.print("textArea: ");
System.out.println(newValue);
});

toBind.addListener((observable, oldValue, newValue) -> {
System.out.print("toBind: ");
System.out.println(newValue);
});

label.textProperty().bind(toBind);
}

然后,场景具有对标签的引用,等等,因此它不是GC标记,并且标签的 textProperty通过绑定到 toBind而具有弱引用。由于 label未进行GC处理,因此弱引用可幸免于垃圾回收,而 toBind无法进行GC处理,因此您将看到期望的输出。

另外,如果您在其他地方引用了 toBind属性,例如在 Application实例中:
public class Controller {

@FXML
TextArea textArea;

private StringProperty toBind = new SimpleStringProperty();

public void initialize() {
textArea.textProperty().bindBidirectional(toBind);

textArea.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.print("textArea: ");
System.out.println(newValue);
});

toBind.addListener((observable, oldValue, newValue) -> {
System.out.print("toBind: ");
System.out.println(newValue);
});

}

public StringProperty boundProperty() {
return toBind ;
}
}

然后
package sample;

import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

private StringProperty boundProperty ;

@Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
boundProperty = controller.boundProperty();
root.setOnMouseClicked(e -> System.gc());
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
}


public static void main(String[] args) {
launch(args);
}
}

您会再次看到预期的行为(即使在垃圾回收之后)。

最终(最后一点很微妙),如果您用匿名内部类替换 textArea.textProperty()上的侦听器:
textArea.textProperty().addListener(new ChangeListener<String>() {

@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
System.out.print("textArea: ");
System.out.println(newValue);
}
});

那么这也可以防止 toBind的GC。原因是匿名内部类的实例包含对封闭实例的隐式引用(在这种情况下,即控制器的实例):此处,控制器保留对 toBind的引用。相比之下,Lambda表达式则不这样做。

关于java - 如何将监听器添加到双向绑定(bind)的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48237645/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com