1

I have multiple controllers, each associated to a different FXML file. There is an event in one node that requires synchronization across other nodes, so I decided to do this with another event, and event handlers in the various controller files.

To register the event handlers requires the event handler method to be static (i.e., addEventHandler(SomeEvent, ClassName::MethodName).

So, the controller looks something like...

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 */ }
}

This works, but seems a bit of a hack. Is there a preferred mechanism to achieve the same end result?

Makoto
  • 104,088
  • 27
  • 192
  • 230
SoCal
  • 801
  • 1
  • 10
  • 23
  • It looks python-y but hey if it works it works – Norsk Apr 14 '16 at 22:18
  • guess u just need to make singleton – Raman Shrivastava Apr 14 '16 at 22:25
  • 1
    Have you considered the Google Guava EventBus? It looks like a good fit for your need. – vl4d1m1r4 Apr 14 '16 at 22:38
  • 1
    "There is an event in one node, that requires synchronization across the other nodes". Use an MVC pattern. Each controller should have a reference to a shared model instance. The controllers update the model in response to user input, and observe the data in the model so they can update the view if relevant data changes. See http://stackoverflow.com/questions/32342864/applying-mvc-with-javafx – James_D Apr 15 '16 at 00:05

2 Answers2

2

You might have more flexibility with this if you get rid of all the static stuff and make the event handler a member of your controller class as demonstrated below.

Sample implementation without static members

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;
    }
}

Notes on implementation options

In the example the event handler has been added to the stage, but it could equally have been added to any scene or node or anything that has the ability to handle events.

If desired, you could also add a setter for the event handler to allow changing of the event handling logic externally.

In addition to the setup above you might wish to have the controller self-register the event handler in it's initialize method. Whether you do so or not just depends on whether you want the ability to register event handlers exposed outside the controller or if you want to use encapsulation to hide all of the event handling logic local to the controller.

Notes on (perhaps superior) alternatives

As an alternate approach, rather than using the event handling system within JavaFX for your custom approach, you could make use of a third party system such as the Google Guava Event Bus.

You should also consider why you need to add custom event handling to your application. JavaFX supports very flexible binding and observer patterns. By exposing properties of your model objects as observable, it is often not necessary to have custom events. Often, your view controllers can observe any changes to associated model objects and modify the internal state of model objects based upon UI interactions. This is especially the case if you introduce a dependency injection based system for injecting models into your controllers, such as Guice, Spring, afterburner.fx or Gluon Ignite.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
1

Maybe you could use some kind of registry, which takes care of the synchronisation. Here is a quick and dirty example:

    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;
            }
        }

In your Controller you can add the node, whose event you like to get synchronized, to the synchronizer, and call sync() in the eventListener.

 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);
        }
 }

EDIT:

This should make for a cleaner version:

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
        }
    }
}
jns
  • 6,017
  • 2
  • 23
  • 28
  • "...By exposing properties of your model objects as observable, it is often not necessary to have custom events. Often, your view controllers can observe any changes to associated model objects and modify the internal state of model objects based upon UI interactions." I like this option. I'm not really a GUI person, and this fits much better into the development style I'm comfortable with. Maybe if I do more GUIs then I'll get deeper into JavaFX, but I like the notion of driving all controller behavior from the underlying model. – SoCal Apr 15 '16 at 22:26