我有多个控制器,每个控制器与不同的FXML文件相关联。在一个节点中有一个事件需要跨其他节点进行同步,因此我决定使用另一个事件和各种控制器文件中的事件处理程序来执行此操作。
要注册事件处理程序,需要将事件处理程序方法设为静态(即addEventHandler(SomeEvent, ClassName::MethodName)
。
所以,控制器看起来像......
public class MyController {
private static MyController selfRef = null;
public MyController() {
selfRef = this;
}
public static void someEventHandler(Event event) {
if (selfRef != null) {
selfRef.doSomethingUseful();
}
}
private void doSomethingUseful() { /* synch the nodes */ }
}
这有效,但似乎有点黑客。是否有一种优选的机制来实现相同的最终结果?
答案 0 :(得分:2)
如果您摆脱所有静态内容并使事件处理程序成为控制器类的成员,您可能会有更多的灵活性,如下所示。
没有静态成员的示例实现
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.*;
import java.io.IOException;
class CustomerDialogController {
@FXML
private Label customerName;
private EventHandler<Event> customEventHandler = event -> {
// handle the event...
};
void initData(Customer customer) {
customerName.setText(customer.getName());
}
public EventHandler<Event> getCustomEventHandler() {
return customEventHandler;
}
}
public class EventHandling {
public Stage showCustomerDialog(Customer customer) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("customerDialog.fxml"));
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(new Scene(loader.load()));
CustomerDialogController controller = loader.getController();
controller.initData(customer);
stage.addEventHandler(Event.ANY, controller.getCustomEventHandler());
stage.show();
return stage;
}
}
class Customer {
private String name;
Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
有关实施选项的说明
在示例中,事件处理程序已添加到阶段,但它同样可以添加到任何场景或节点或任何能够处理事件的事物。
如果需要,您还可以为事件处理程序添加setter,以允许在外部更改事件处理逻辑。
除了上面的设置之外,您可能希望让控制器在其初始化方法中自行注册事件处理程序。是否这样做取决于您是否希望能够注册暴露在控制器外部的事件处理程序,或者是否要使用封装来隐藏控制器本地的所有事件处理逻辑。
关于(可能是更好的)替代方案的说明
作为一种替代方法,您可以使用第三方系统,例如Google Guava Event Bus,而不是在JavaFX中使用事件处理系统来实现自定义方法。
您还应该考虑为什么需要向应用程序添加自定义事件处理。 JavaFX支持非常灵活的绑定和观察者模式。通过将模型对象的属性公开为可观察对象,通常不需要具有自定义事件。通常,视图控制器可以观察对关联模型对象的任何更改,并根据UI交互修改模型对象的内部状态。如果您引入基于依赖注入的系统以将模型注入控制器(例如Guice,Spring,afterburner.fx或Gluon Ignite),情况尤其如此。
答案 1 :(得分:1)
也许您可以使用某种注册表来处理同步。这是一个快速而肮脏的例子:
public class Synchronizer {
private ObservableList<Node> nodes;
private boolean isSyncing;
public Synchronizer() {
nodes = FXCollections.observableArrayList();
}
public void addNode(Node node) {
nodes.add(node);
}
public void sync(Node sourceNode, Event event) {
if (isSyncing) {
return;
}
isSyncing = true;
for (Node node : nodes) {
if (node != sourceNode) {
node.fireEvent(event);
}
}
isSyncing = false;
}
}
在Controller中,您可以将要同步的事件节点添加到同步器,并在eventListener中调用sync()
。
public class Controller {
private StackPane root;
private Button button;
public Controller(Synchronizer synchronizer) {
button = new Button();
button.setOnAction(evt -> {
synchronizer.sync(button, evt);
//action
});
synchronizer.addNode(button);
root = new StackPane(button);
}
}
修改强>
这应该是一个更清洁的版本:
public class Starter extends Application {
@Override
public void start(Stage primaryStage) {
ViewController controller1 = new ViewController();
ViewController controller2 = new ViewController();
Synchronizer synchronizer = new Synchronizer();
synchronizer.add(controller1);
synchronizer.add(controller2);
VBox box = new VBox(controller1.root, controller2.root);
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public interface SyncTarget {
Node getSyncNode();
void triggerAction();
}
public class Synchronizer {
private ObservableList<SyncTarget> syncTargets;
private EventHandler<Event> eventHandler;
public Synchronizer() {
syncTargets = FXCollections.observableArrayList();
eventHandler = e -> sync();
}
public void add(SyncTarget target) {
syncTargets.add(target);
target.getSyncNode().addEventHandler(ActionEvent.ANY, eventHandler);
}
public void remove(SyncTarget target) {
syncTargets.remove(target);
target.getSyncNode().removeEventHandler(ActionEvent.ANY, eventHandler);
}
public void sync() {
for (SyncTarget target : syncTargets) {
target.triggerAction();
}
}
}
public class ViewController implements SyncTarget {
private StackPane root;
private Button button;
public ViewController() {
button = new Button();
root = new StackPane(button);
}
@Override
public Node getSyncNode() {
return button;
}
@Override
public void triggerAction() {
//action
}
}
}