我正在尝试将TextArea的textProperty绑定到控制器的initialize()方法中的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);
});
}
}
使用此控制器,当我输入序列&#39; abcd&#39;到了textarea,我得到了:
textArea: a
textArea: ab
textArea: abc
textArea: abcd
似乎没有触发toBind对象的change事件。
然后我尝试在textArea的监听器中打印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
为什么会这样?该事件被正确解雇。
答案 0 :(得分:4)
您的绑定和toBind
属性正在收集垃圾。
&#34;过早垃圾收集的简洁描述&#34; Tomas Mikula在blog上提供了问题。
首先,对于试图重现此问题的任何人来说,请快速放弃。由于所描述的行为取决于发生的垃圾收集,因此可能并不总是发生(它取决于内存分配,正在使用的GC实现以及其他因素)。如果添加行
root.setOnMouseClicked(e -> System.gc());
到start()
方法,然后单击场景中的空白区域将请求垃圾收集,并且问题将(至少更有可能)在此之后显现(如果它没有&t; t的话)。
问题在于绑定使用WeakListener
来侦听属性中的更改并将这些更改传播到绑定属性。如果没有对该属性的其他实时引用,则弱侦听器被设计为不阻止其附加的属性被垃圾收集。 (理由是避免在属性不再在范围内时强制程序员手动清理绑定。)
在您的示例代码中,控制器及其属性toBind
符合垃圾回收的条件。
start()
方法完成后,保证引用的所有内容都是在您调用Application
时创建的launch()
实例,即显示的Stage
,以及那些引用的任何东西。这当然包括Scene
(由Stage
引用),其root
,root
的子女,他们的子女等,这些属性以及(非-weak)任何这些属性的监听器。
因此stage
引用了scene
,它引用了GridPane
作为其根,并且引用了TextArea
。
TextArea
引用了附加到它的侦听器,但该侦听器不保留其他引用。
(在您的代码的第二个版本中,ChangeListener
附带的非弱textArea.textProperty()
引用了toBind
。因此在该版本中,ChangeListener
阻止toBind
成为GC&#39; d,你会看到监听器的输出。)
加载FXML时,FXMLLoader
会创建控制器实例。虽然该控制器实例具有对字符串属性和文本区域的引用,但反之则不然。因此,一旦加载完成,就没有对控制器的实时引用,它有资格进行垃圾收集,以及它定义的StringProperty
。文本区域textProperty()
只有{em>弱引用给toBind
上的侦听器,因此文本区域无法阻止toBind
被垃圾回收。< / p>
在大多数实际情况中,这不会成为问题。除非您打算在某处使用它,否则您不太可能创建此额外的StringProperty
。因此,如果您在&#34;自然&#34;中添加任何使用此代码的代码。你可能会看到这个问题消失了。
因此,例如,假设你添加了一个标签:
<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&#39; d标签的textProperty
通过绑定到toBind
而具有弱引用。由于label
不是GC&#39; d,弱引用仍然存在垃圾回收,而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表达式不会这样做。
答案 1 :(得分:1)
我找到了2个解答你的答案
<强> 1 强>
将StringProperty声明为static
或2:
在TextArea Listener中,只需致电toBind.get()
但说实话,我不知道为什么会这样做。