button.onActionProperty().addListener is not working in javafx


Question

I am new in Java and I am trying Java events but I am absolutely lost. I know events from C# where is no problem with it and they works perfectly, but Java is different universe . I tried to find something on internet but I can't figured it out long time so I am here.

I have one object and I need trigger some action. When this action is triggered I need to call not only one event handler, but more of them from different objects.

For example I just use Button class.

There are two ways how to do that:

One way is to use button.setOnAction method. But this is not working because when I call this method second time (from another object) I just replace one event handler by another. You can see what I mean in code in method initEventsUselessWay().

Second way is to use button.onActionProperty().addListener. But this is not working at all. You can see in method initEventsNeededWay().

So, why button.onActionProperty().addListeneris not working?

And is there any way how to do this in Javafx?

Finally I will not use Button class, but something like MyClass and I need to implement this here. But if this is not working on Button class it will not work on MyClas neither.

Thank you very much for advice.

package sample;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class JavaEventsTest1589 extends Application {
private Button btnDemo1;
private Button btnDemo2;

@Override
public void start(Stage primaryStage) {
    // panel
    Pane rootPane = new Pane();

    // scene
    Scene scene = new Scene(rootPane, 300, 250);
    primaryStage.setTitle("events demo");
    primaryStage.setScene(scene);

    // button 1
    btnDemo1 = new Button();
    rootPane.getChildren().add(btnDemo1);
    btnDemo1.setText("Execute Demo 1");
    btnDemo1.setLayoutX(50);
    btnDemo1.setLayoutY(10);

    // button 2
    btnDemo2 = new Button();
    rootPane.getChildren().add(btnDemo2);
    btnDemo2.setText("Execute Demo 2");
    btnDemo2.setLayoutX(50);
    btnDemo2.setLayoutY(50);

    initEventsUselessWay();

    initEventsNeededWay();

    primaryStage.show();
}

public static void main(String[] args) {
    launch(args);
}

private void initEventsUselessWay() {

    btnDemo1.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent actionEvent) {
            runDemoPrimaryReaction();
        }
    });

    btnDemo1.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent actionEvent) {
            runDemoSecondaryReaction();
        }
    });
}

private void initEventsNeededWay() {

    btnDemo2.onActionProperty().addListener(new ChangeListener<EventHandler<ActionEvent>>() {
        @Override
        public void changed(ObservableValue<? extends EventHandler<ActionEvent>> observableValue, EventHandler<ActionEvent> actionEventEventHandler, EventHandler<ActionEvent> actionEventEventHandler2) {
            runDemoThisINeed_No1();
        }
    });

    btnDemo2.onActionProperty().addListener(new ChangeListener<EventHandler<ActionEvent>>() {
        @Override
        public void changed(ObservableValue<? extends EventHandler<ActionEvent>> observableValue, EventHandler<ActionEvent> actionEventEventHandler, EventHandler<ActionEvent> actionEventEventHandler2) {
            runDemoThisINeed_No2();
        }
    });

}

private void runDemoPrimaryReaction() {
    System.out.println("useless way - primary reaction");
}

private void runDemoSecondaryReaction() {
    System.out.println("useless way - secondary reaction");
}

private void runDemoThisINeed_No1() {
    System.out.println("not working way - No1");
}

private void runDemoThisINeed_No2() {
    System.out.println("not working way - No2");
}

}
1
1
3/30/2014 1:30:59 PM

Accepted Answer

Use addEventHandler:

btnDemo1.addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent actionEvent) {
        runDemoPrimaryReaction();
    }
});

btnDemo1.addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent actionEvent) {
        runDemoSecondaryReaction();
    }
});

I recommend using Java 8, in which case you can write

btnDemo1.addEventHandler(ActionEvent.ACTION, event -> runDemoPrimaryReaction());
btnDemo1.addEventHandler(ActionEvent.ACTION, event -> runDemoSecondaryReaction());

The setOnAction(...) method is a "convenience" method. The way it works is that the Button maintains an ObjectProperty<EventHandler<ActionEvent>>. If you set the value of this property (to an EventHandler<ActionEvent>), that event handler will automatically be added to the event handlers for the button. If you set it a second time, since it's just a property, it will replace the existing event handler. So you can use this for a slightly quicker approach in the case you only have one handler. (It also plays nicely with FXML.)

onActionProperty().addListener(...) is a different thing entirely: it listens for changes in the property itself. So if you register a listener in this way, then call setOnAction(...), the listener you registered with the property will be invoked when you call setOnAction (not when the button is pressed).

Have a look at the tutorial, particularly the first section "Processing events" and the second "Working with convenience methods". The second section makes it clear when setOnAction is actually doing.

7
3/30/2014 1:53:45 PM

You need to think of an 'action' as a property of the Button rather than an event. A Button can only have one action at a time, hence you are setting it to one value and then changing it to another which means only the code in the second handler will execute.

The action property is implemented using one of the JavaFX property classes which provides built in property change notification. When you call button.onActionProperty().addListener(... you are adding a listener that will be invoked when the action is changed.

Try calling the addListener(... code before calling setOnAction(... and it should be clear what is happening.

If you want to think of this in terms of C# then

  • button.setOnAction(.. is like a property e.g. button.Action = ...
  • button.onActionProperty().addListener(... is like an event e.g. button.ActionChanged += ...

If you are new to JavaFX and need some help with their particular implementation of properties and events I can suggest the following resources:

Update:

After reading your comments I realise this isn't really JavaFX specific but about events in general. Events are just an implementation of the observer pattern (even in C#).

A basic implementation might look something like this although there is little point in writing this yourself as JavaFX does include a built in way registering listeners for events as demonstrated by James_D.

public interface MyListener {
    invoke();
}

class MyButton extends Button {

    List<MyListener> listeners = new ArrayList<MyListener>();

    public void addListener(MyListener toAdd) {
        listeners.add(toAdd);
    }

    private void invoke() {
        for (HelloListener l : listeners)
            l.invoke();
    }

    this.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent actionEvent) {
            this.invoke();
        }
    });
}

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon