Thursday, October 20, 2011

JavaFX 2.0 EventHandler & EventFilter

Some clarification about addEventFilter and addEventHandler from my observation.

Looking from the API they both take the exact same parameters. So what's the difference?

Event Filters will be called before Event Handlers.
Call sequence:

1. call all EventHandlers added with addEventFilter (most likely in order which they are added, but no guarantee). All EventHandlers will be called, even if any handlers called event.consume().

2. call all EventHandlers added with addEventHandler, except those that with the same EventType which is handled in EventHandlers in step 1 and had called event.consume().

Example:

final TextField text = new TextField("");

text.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("handler 1");
      event.consume();
    }
});
text.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("handler 2");
    }
});
    
text.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("filter 1");
    }
});
text.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("filter 2");
    }
});

Output is like this:

>> filter 1
>> filter 2
>> handler 1
>> handler 2

As you can see there is no use calling event.consume() in the Handler phase.

Now using event.consume() in the Filter phase:
final TextField text = new TextField("");

text.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("handler 1");
      event.consume();
    }
});
text.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("handler 2");
    }
});
    
text.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("filter 1");
      event.consume();
    }
});
text.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
      
    @Override
    public void handle(KeyEvent event) {
      System.out.println("filter 2");
    }
});

The output is:

>> filter 1
>> filter 2


There, the events of the same type in the Handler phase are no longer being called.

One more thing, if the Filter event is listened on KeyEvent.KEY_TYPED and event.consume() is called, the default behavior of key will be consumed as well, which means no character will be printed out.

To make the example more complete (Thanks to Peter Moufarrej), let's add parent-child idea into the scene. Suppose the TextField is inside a GridPane and have all the same EventHandlers and EventFilters as the TextField.
grid.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {

    @Override
    public void handle(KeyEvent event) {
        System.out.println("grid handler 1");
        event.consume();
    }
});
grid.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {

    @Override
    public void handle(KeyEvent event) {
        System.out.println("grid handler 2");
    }
});

grid.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {

    @Override
    public void handle(KeyEvent event) {
        System.out.println("grid filter 1");
        event.consume();
    }
});
grid.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {

    @Override
    public void handle(KeyEvent event) {
        System.out.println("grid filter 2");
    }
});

The output is:

>> grid filter 1
>> grid filter 2

Suppose we comment out the event.consume() inside gride's EventFilter.
The output is:

>> grid filter 1
>> grid filter 2
>> filter 1
>> filter 2

Let's comment out the event.consume() inside text's EventFilter.
The output is:

>> grid filter 1
>> grid filter 2
>> filter 1
>> filter 2
>> handler 1
>> handler 2

If we take out all the event.consume() The output is:

>> grid filter 1
>> grid filter 2
>> filter 1
>> filter 2
>> handler 1
>> handler 2
>> grid handler 1
>> grid handler 2

As you can see the execution order is from parent.eventFilter -> child.eventFilter -> child.eventHandler -> parent.evenHandler. The EventFilter phase is like the Event Capture phase and EventHandler is like the Event Bubble phase of JavaScript.