我想听听JavaFX中某个阶段的
userData
的变化。我尝试将从 getUserData
方法返回的对象包装在 SimpleObjectProperty
内,然后向其添加侦听器,但它不起作用。
这是我的尝试:
SimpleObjectProperty<Object> userDataProperty = new SimpleObjectProperty<>(stage.getUserData());
userDataProperty.addListener((observable, oldValue, newValue) -> {
// print when userData is changed
System.out.println("new userdata:" + stage.getUserData());
});
// change the userData to test if the listener work
stage.setUserData(2);
System.out.println(stage.getUserData());
stage.setUserData(3);
System.out.println(stage.getUserData());
输出:
2
3
如何正确做?
Window
类不公开用户数据的可观察值,因此它不可直接观察。然而, Window#setUserData(Object)
方法有一些有点神秘的文档:
设置可在以后检索的单个对象属性的便捷方法。这在功能上等同于调用 getProperties().put(Object key, Object value) 方法。稍后可以通过调用 getUserData() 来检索该数据。
这表示用户数据保存在
ObservableMap
返回的
Window#getProperties()
中。如果您查看实现,您会发现情况确实如此。不幸的是,用户数据输入的密钥是私有的。您可以使用反射来获取密钥,或者可以说更好的是,使用 MapChangeListener
来捕获它。
这是一个使用
MapChangeListener
的示例。
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.collections.MapChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;
public class Main extends Application {
// Make sure to keep a strong reference to the binding for as long as you need it.
private ObjectBinding<Object> userData;
@Override
public void start(Stage primaryStage) {
userData = userDataBinding(primaryStage);
// Note: Using _ for unused parameters was standardized in Java 22.
userData.addListener(
(_, oldVal, newVal) -> System.out.printf("User data: %s -> %s%n", oldVal, newVal));
// Type something into the TextField then press ENTER to see the example at work.
var field = new TextField();
field.setMaxWidth(Region.USE_PREF_SIZE);
field.setOnAction(
e -> {
e.consume();
primaryStage.setUserData(field.getText());
field.clear();
});
primaryStage.setScene(new Scene(new StackPane(field), 500, 300));
primaryStage.show();
}
private ObjectBinding<Object> userDataBinding(Window window) {
// Use array because local variables must be (effectively) final when used inside
// a lambda expression or anonymous class.
Object[] keyHolder = new Object[1];
window
.getProperties()
.addListener(
new MapChangeListener<>() {
@Override
public void onChanged(Change<? extends Object, ? extends Object> change) {
keyHolder[0] = change.getKey();
// Only need to capture the key once.
change.getMap().removeListener(this);
}
});
var oldUserData = window.getUserData(); // save current value
window.setUserData(new Object()); // invoke the listener
window.setUserData(oldUserData); // restore to old value
return Bindings.valueAt(window.getProperties(), keyHolder[0]);
}
}
从文档来看,可以对
Scene
和 Node
使用相同的方法。