我试图在控制器的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)在他的博客上简要地描述了“过早的垃圾收集”问题。
首先,对于任何试图重现此问题的人来说,请速记一点。由于所描述的行为取决于发生的垃圾回收,因此它可能并不总是发生(取决于内存分配,所使用的GC实现以及其他因素)。如果添加行
root.setOnMouseClicked(e -> System.gc());
对该start()
方法进行操作,然后单击场景中的空白区域将请求进行垃圾回收,并且该问题将(至少更有可能)在此之后(如果尚未出现)显现出来。
问题是绑定使用WeakListener
s侦听属性更改并将这些更改传播到绑定的属性。如果没有其他活动引用,弱侦听器的设计目的是不要阻止对其附加的属性进行垃圾回收。(其基本原理是避免在属性不再在范围内时强迫程序员强制清理绑定。)
在您的示例代码中,控制器及其属性toBind
可以进行垃圾回收。
在之后start()
方法完成,所有你都保证有被引用Application
,当你调用创建的实例launch()
中,Stage
结果表明,和任何从这些引用。当然,这包括Scene
(所引用的Stage
),的root
,的子代root
,其子代等,这些的属性,以及这些属性中任何一个的(非弱)侦听器。
因此,stage
对的引用是scene
,对的引用是,GridPane
这是其根,对的引用也对TextArea
。
在TextArea
有附加到它的侦听器的引用,但听者保持没有额外的引用。
(在代码的第二个版本中,ChangeListener
附加到的非弱textArea.textProperty()
引用有对的引用toBind
。因此,在该版本中,ChangeListener
禁止toBind
将GC进行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);
}
});
那么这也会阻止的GC
toBind
。原因是匿名内部类的实例包含对封闭实例的隐式引用(在这种情况下,即控制器的实例):此处控制器保留对的引用toBind
。相比之下,Lambda表达式则不这样做。
问题内容: 我有一个ArrayList,它向其中动态添加了一些对象,并且有一个JButton。运行我的程序时ArrayList为空,并且JButton设置为setEnabled(false)。我想在ArrayList中有2个或更多元素时启用我的按钮,如果ArrayList有一项或为空则再次禁用它。我该如何实现? 问题答案: 没有任何种类的通知机制。 我建议您编写自己的实现,该实现将委托给私有的实现
我正在用canvas构建一个新的Javascript游戏,我想为每个对象添加一个事件监听器。我想画一个有用的操纵杆(箭头),当游戏从智能手机/平板电脑打开。所以,玩家可以通过点击每个箭头来移动角色。 这就是我所拥有的: game.js:
问题内容: 在JavaScript中,删除使用bind()添加为事件侦听器的函数的最佳方法是什么? 例 我能想到的唯一方法是跟踪添加了bind的每个侦听器。 上面的示例使用此方法: 有没有更好的方法可以做到这一点? 问题答案: 尽管@machineghost所说的是正确的,但事件的添加和删除方式相同,但等式中缺少的部分是: 调用后会创建一个新的函数引用! 因此,要添加或删除它,请将引用分配给变量:
问题内容: 我正在尝试将鼠标侦听器添加到MapMarker,以便当鼠标将鼠标悬停在MapMarker上时,我可以对事件做出反应。 我实现了mouseListener,但是我不能真正添加一个监听器。问题是由于没有层次结构实现JPanel,所以我没有找到MapMarker添加addMouseListener的方法。 任何帮助表示赞赏 问题答案: 如前所述这里,默认的构造函数使用了“这实现了Map按
是否有办法将AWT侦听器添加到SWT组件? 我以前制作了一个主要在AWT和Swing组件中运行的应用程序。现在,我有热键功能,它依赖于一个定制的库,该库监听全局键事件并返回相应的AWT键代码。 当我改变整个应用程序并使用SWT组件时,我的问题就出现了。正如我们所知,一些键现在返回一个不同的键代码,这扰乱了整个热键功能。 我想到的最初解决方案是: A.使用javax。摆动jtextfield作为my
我用JavaFx制作了两个简单的JMS应用程序(一个是发送者,另一个是接收者)。 但是,我不能用来自发送方的新消息刷新接收方的GUI。 我使用onMessage事件在internet上查找,并重写了它(向那里的ObservableList添加一个项),但它不起作用。引发事件后,没有向ObservableList添加任何元素。 这是我的听筒: 我的发件人: