1

I try register eventHandler in my custom class. I don't know what interface or methods I have to implement for having addEventHandler method in my custom class. For this reason my Model class extends Rectangle (Rectangle class has addEventHandler mechanism).

Also I don't know why assigned source object not working (please see comment in Controller class).

Creating custom events I make by this tutorial: https://stackoverflow.com/a/27423430/3102393.


Project Structure

Controller

package sample;

import javafx.event.Event;

public class Controller {
    private Model model;

    public Controller() {
        model = new Model();

        model.addEventHandler(MyEvent.ROOT_EVENT, this::handler);
    }

    private void handler(MyEvent event) {
        if(event.getEventType().equals(MyEvent.INSTANCE_CREATED)) {
            // Why is event.getSource() instence of Rectangle and not instance of assigned MyObject?
            Object obj = event.getSource();
            System.out.println(event.getMyObject().getText());
        }
    }

    public void clickedCreate(Event event) {
        model.makeEvent();
    }
}

Model

package sample;

import javafx.scene.shape.Rectangle;
import java.util.ArrayList;

public class Model extends Rectangle {

    private ArrayList<MyObject> objects = new ArrayList<>();
    private Integer counter = 0;

    public void makeEvent() {
        MyObject object = new MyObject((++counter).toString() + "!");

        objects.add(object);
        fireEvent(new MyEvent(object, null, MyEvent.INSTANCE_CREATED));
    }
}

Custom event MyEvent

package sample;

import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.event.EventType;

public class MyEvent extends Event {

    public static final EventType<MyEvent> ROOT_EVENT = new EventType<>(Event.ANY, "ROOT_EVENT");
    public static final EventType<MyEvent> INSTANCE_CREATED = new EventType<>(ROOT_EVENT, "INSTANCE_CREATED ");
    public static final EventType<MyEvent> INSTANCE_DELETED = new EventType<>(ROOT_EVENT, "INSTANCE_DELETED");

    private MyObject object;

    public MyEvent(MyObject source, EventTarget target, EventType<MyEvent> eventType) {
        super(source, target, eventType);

        object = source;
    }

    public MyObject getMyObject() {
        return object;
    }
}

And finally MyObject

package sample;

public class MyObject {
    private String text;

    MyObject(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

Note (and question): I also tried using a ObservableList of instances of MyObjects, but I think that there is no notify for updating instance attribute.

Community
  • 1
  • 1
user3102393
  • 87
  • 2
  • 9
  • `new Controller().clickedCreate(null);` prints `1!`. Why is this unexpected??? – fabian Apr 01 '16 at 15:16
  • I mean the line before: `Object obj = event.getSource();`. I created **MyEvent** with parent super-constructor and specified source object (first constructor parameter) as **MyObject** instance. But if I inspect **obj** from `Object obj = event.getSource();`, the obj is instance of **Rectangle**. – user3102393 Apr 01 '16 at 16:12
  • but you already have `getMyObject()` ? why not use it? but rather worry about something else?, yes you set the source, but the i guess its because eventtarget is null, its resets back to the node who the handler is registered to. this is some wild guess, please do not ask me questions about whether is true, :) afaik, getSource() should always be a Node who the event came from, and just do not try to break that. – Elltz Apr 01 '16 at 16:37
  • OK, thanks, `getMyObject()` is sufficient :-). – user3102393 Apr 01 '16 at 16:48

1 Answers1

6

Basics of Events

Events are fired using Event.fireEvent which works in 2 steps:

  1. Build the EventDispatchChain using EventTarget.buildEventDispatchChain.
  2. Pass the Event to the first EventDispatcher in the resulting EventDispatchChain.

This code snippet demonstrates the behaviour:

EventTarget target = new EventTarget() {

    @Override
    public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
        return tail.append(new EventDispatcher() {

            @Override
            public Event dispatchEvent(Event event, EventDispatchChain tail) {
                System.out.println("Dispatch 1");
                tail.dispatchEvent(event);
                return event;
            }
        }).append(new EventDispatcher() {

            @Override
            public Event dispatchEvent(Event event, EventDispatchChain tail) {
                System.out.println("Dispatch 2");
                tail.dispatchEvent(event);
                return event;
            }
        });
    }
};

Event.fireEvent(target, new Event(EventType.ROOT));

It prints

Dispatch 1
Dispatch 2

As you can see, the way the EventTarget constructs the EventDispatchChain is totally up to the EventTarget.

This explains why you have to implement addEventHandler ect. yourself.

How it's done for Nodes

This is described in detail in the article JavaFX: Handling Events - 1 Processing Events on the Oracle website.

The important details are:

  • Different source objects are used during the event handling.
  • EventHandlers / EventFilters are used during the event dispatching (2.).

This explains why the source value is unexpected.

How to implement addEventHandler

It's not that hard to do this, if you leave out the event capturing and bubbling. You just need to store the EventHandlers by type in a Map<EventType, Collection>> and call the EventHandlers for each type in the EventType hierarchy:

public class EventHandlerTarget implements EventTarget {

    private final Map<EventType, Collection<EventHandler>> handlers = new HashMap<>();
    
    public final <T extends Event> void addEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler) {
        handlers.computeIfAbsent(eventType, (k) -> new ArrayList<>())
                .add(eventHandler);
    }

    public final <T extends Event> void removeEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler) {
        handlers.computeIfPresent(eventType, (k, v) -> {
            v.remove(eventHandler);
            return v.isEmpty() ? null : v;
        });
    }
    
    @Override
    public final EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
        return tail.prepend(this::dispatchEvent);
    }

    private void handleEvent(Event event, Collection<EventHandler> handlers) {
        if (handlers != null) {
            handlers.forEach(handler -> handler.handle(event));
        }
    }

    private Event dispatchEvent(Event event, EventDispatchChain tail) {
        // go through type hierarchy and trigger all handlers
        EventType type = event.getEventType();
        while (type != Event.ANY) {
            handleEvent(event, handlers.get(type));
            type = type.getSuperType();
        }
        handleEvent(event, handlers.get(Event.ANY));
        return event;
    }

    public void fireEvent(Event event) {
        Event.fireEvent(this, event);
    }

}
Community
  • 1
  • 1
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Thank you very much for your comprehensive answer :-). – user3102393 Apr 01 '16 at 18:25
  • @fabian why do we have to prepend `EventDispatcher` of this node to the chain instead of appending it. – harsh Jul 23 '17 at 14:32
  • @calvin: Take a look at [the javadoc for `EventTarget.buildEventDispatchChain`](https://docs.oracle.com/javase/8/javafx/api/javafx/event/EventTarget.html#buildEventDispatchChain-javafx.event.EventDispatchChain-) – fabian Jul 23 '17 at 14:59