如何识别JavaFX属性更改的来源,即谁更改了属性?
-
示例:"选择" CheckBox的状态必须与另一个对象状态同步,而另一个对象不支持Properties或Bindings。显而易见的方法是在CheckBox的selectedProperty上注册ChangeHandler
以更新其他对象状态。在相反的方向,另一个通知工具用于调用CheckBox的setSelected()
或selectedProperty.set()
。这导致了一个问题:注册的ChangeHandler不仅在用户单击UI中的CheckBox时被调用,而且在另一个对象更改其状态时被调用。
在后一种情况下,我们当然不希望将更改传播回对象。令人惊讶的是,似乎没有任何东西可以让ChangeHandler决定是由UI控件本身还是从外部更改属性。处理函数的第一个参数仅引用更改的Property / Observabe ,而不是谁更改它。 oldValue
和newValue
参数可用于打破无限通知周期,但它们不足以阻止第一个不必要的,可能是有害的通知。
-
上述说明应该足够了,但如果您希望以最小工作示例的形式提出此问题,则以下代码演示了此问题:
"其他对象"一个布尔标志为" state" (现实世界中的数据库):
package sample;
public class SomeDatabaseEntry {
private boolean someFlag = false;
public boolean getSomeFlag()
{
return someFlag;
}
public void setSomeFlag(boolean state)
{
someFlag = state;
}
public void toggleSomeFlag()
{
someFlag = !someFlag;
}
}
主:
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 {
/*
* dummy object representing the "other side", e.g. a database
*/
public final SomeDatabaseEntry _someEntry = new SomeDatabaseEntry();
private Controller _controller;
@Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader fxmlLoader = new FXMLLoader();
Parent root = fxmlLoader.load(getClass().getResource("sample.fxml").openStream());
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
_controller = fxmlLoader.getController();
_controller.init(_someEntry);
startSomeDummyDatabaseUpdater();
}
/*
* dummy logic that emulates external changes (e.g. database updates)
* in the real world there would be a function that is called by the
* database with a description of the changes that occured.
* as said: this part is not under my control
*/
public void startSomeDummyDatabaseUpdater()
{
new Thread(() -> {
while (true)
{
_someEntry.toggleSomeFlag();
_controller.updateUIFromDatabase();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) {
launch(args);
}
}
控制器处理用户输入和其他对象状态(数据库):
package sample;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
public class Controller {
private SomeDatabaseEntry _someEntry;
@FXML
private CheckBox myCheckBox;
public void init(SomeDatabaseEntry entry)
{
_someEntry = entry;
myCheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// If the user toggled the CheckBox in the UI, update the state of the database
// PROBLEM: this handler is also called in the context of a database update (function below)
// in this case the database must not be updated (e. g. because the update is expensive)
_someEntry.setSomeFlag(newValue);
System.out.println("Database was updated: " + newValue);
}
});
}
public void updateUIFromDatabase()
{
myCheckBox.selectedProperty().setValue(_someEntry.getSomeFlag());
}
}
FXML:
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.CheckBox?>
<GridPane fx:id="root" fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<CheckBox fx:id="myCheckBox" text="someFlag"></CheckBox>
</GridPane>
答案 0 :(得分:1)
只需介绍一个额外的财产。通常这将是模型的一部分。
public class UIModel {
private final BooleanProperty value = new SimpleBooleanProperty();
public BooleanProperty valueProperty() {
return value ;
}
public final boolean getValue() {
return valueProperty().get();
}
public final void setValue(boolean value) {
valueProperty().set(value);
}
// other properties, etc...
}
现在您创建模型的实例并与感兴趣的各方共享该实例,例如使用依赖注入等
您的控制器应该执行类似
的操作public class Controller {
@Inject // or inject it by hand, just imagining DI here for simplicity
private UIModel model ;
@Inject
private DAO dao ;
@FXML
private CheckBox myCheckBox ;
public void initialize() {
model.valueProperty().addListener((obs, oldValue, newValue) ->
myCheckBox.setSelected(newValue));
myCheckBox.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (model.getValue() != isNowSelected) {
updateDatabase(isNowSelected);
model.setValue(isNowSelected);
}
});
}
private void updateDatabase(boolean value) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
dao.update(value);
return null ;
}
};
task.setOnFailed(e -> { /* handle errors */ });
new Thread(task).start() ; // IRL hand to executor, etc.
}
}
现在您的“数据库更新”(我假设它代表已经发生在外部数据库中的更改)看起来像
UIModel model = ... ; // from DI or wherever.
while (! Thread.currentThread().isInterrupted()) {
Platform.runLater(() -> model.setValue(! model.getValue()));
try {
Thread.sleep(2000);
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
}
}
这里的要点是您更新模型,使其与外部资源保持同步。模型上的监听器更新复选框,因此现在这些复选框是一致的。这会在复选框的选定状态下触发侦听器,但如果模型和复选框已经同步,则该侦听器不会更新数据库,如果由于模型更改而更改了复选框,则会出现这种情况。 / p>
另一方面,如果用户选中复选框,则模型将不会同步,因此侦听器会更新数据库,然后使模型与UI和数据库同步。
在任何实际应用程序中,无论如何都需要定义UI模型(可能不止一个类),因为您希望将UI状态与视图和控制器分开。如何管理模型到后端服务的确切接口可能会有所不同,您可能需要修改与该接口相关的位置,但这种分离应提供执行所需操作的方法,如本示例所示。