0

UPDATE 2019.05.14 4:54PM EST - OK - here is code that illustrates my issue - probably took me way too long to get this, and it's probably too lengthy, but again, I'm new to Java. Anyway - it works, brings the form up, and fails to have the second button see and react to the "Event". I think i am now "raising" the event OK - at least it is hitting the code in the event. However, it is still running the class code and not the "custom" code passed in via setOnFormStateChange ???? I'm not sure what's wrong.

All Imports

import java.util.ArrayList;
import java.util.Date;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

MyApp.java

public class MyApp extends Application {

    public static void main(String[] args) {
        launch(args); // this method will hang here until main form is closed!
    }

    @Override
    public void start(Stage stage) throws Exception {
        Form myForm = new Form();
        myForm.ShowForm();
    }

}

Form.java

class Form {

    private boolean modified;
    private ArrayList<FormStateChangeListener> registry = new ArrayList<>();

    public void setModified(boolean m) {
        modified = m;
        this.throwStateChange(m);
    }

    public void throwStateChange(boolean m) {
        for (FormStateChangeListener o : registry) {
          //  o.onFormStateChange(m);         
            FormStateChangeEvent.fireEvent(o,m);
        }
    }

    public void ShowForm() {

        Label lbl = new Label("NORMAL STATE");
        lbl.setLayoutX(50);
        lbl.setLayoutY(20);

        Btn myBtn1 = new Btn(this);
        myBtn1.setLayoutX(100);
        myBtn1.setLayoutY(100);
        myBtn1.setPrefWidth(200);

        myBtn1.setText("Press To Change State");
        myBtn1.setOnAction(e -> {
            lbl.setText("CHANGED STATE:" + new Date().toString());
            this.setModified(true);
        });

        Btn myBtn2 = new Btn(this);
        myBtn2.setLayoutX(100);
        myBtn2.setLayoutY(200);
        myBtn2.setPrefWidth(200);
        myBtn2.setText("And This Should React");

        myBtn2.setOnFormStateChange(e -> {
            myBtn2.setText("I REACTED!");
        });


        Stage stage = new Stage();
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 430, 400);
       // root.getChildren().add(lbl);
       // root.getChildren().add(myBtn1);
       // root.getChildren().add(myBtn2);
        root.getChildren().addAll(lbl,myBtn1,myBtn2);
        stage.setScene(scene);
        stage.showAndWait();
    }

    void registerForEvent(FormStateChangeListener t) {
        registry.add(t);

    }

}

FormStateChangeListener.java

interface FormStateChangeListener {
    public void onFormStateChange(boolean mod);
}

FormState.java

enum FormState {
    NORMAL, MODIFIED, NEW
}

Btn.java

class Btn extends Button implements FormStateChangeListener {

    private final ObjectProperty<EventHandler<? super FormStateChangeEvent>> onFormStateChange
            = new SimpleObjectProperty<EventHandler<? super FormStateChangeEvent>>(this, "onFormStateChange") {

        @Override
        protected void invalidated() {
            setEventHandler(FormStateChangeEvent.FORM_STATE_CHANGE, get());
        }
    };

    public Btn(Form f) { // constructor
        f.registerForEvent(this); // register for the event
    }

    public final void setOnFormStateChange(EventHandler<? super FormStateChangeEvent> handler) {
        onFormStateChange.set(handler);
    }

    public final EventHandler<? super FormStateChangeEvent> getOnFormStateChange() {
        return onFormStateChange.get();
    }

    public final ObjectProperty<EventHandler<? super FormStateChangeEvent>> onFormStateChangeProperty() {
        return onFormStateChange;
    }

    public void onFormStateChange(boolean mod) {
        //in reality nothing would be here, but is just for testing
       System.out.println("Code from class.");
    }
;

}

FormStateChangeEvent.java

class FormStateChangeEvent extends Event {

    public static final EventType<FormStateChangeEvent> ANY = new EventType<>(Event.ANY, "MY_EVENT");
    public static final EventType<FormStateChangeEvent> FORM_STATE_CHANGE = new EventType<>(ANY, "FORM_STATE_CHANGE");

    static void fireEvent(FormStateChangeListener o, boolean mod) {
        //throw new UnsupportedOperationException("Not supported yet."); 
        o.onFormStateChange(mod);
    }

    public FormStateChangeEvent(EventType<? extends FormStateChangeEvent> eventType) {
        super(eventType);
    }
}
Slaw
  • 37,820
  • 8
  • 53
  • 80
KenM
  • 31
  • 8
  • What prevents you from using a normal setter? (Or if you want to provide it, using using a property in addition to this?) If something happens that should trigger the event, you invoke the `handle` method of the event handler, if the property is non-null. – fabian May 14 '19 at 01:00
  • Related examples or function parameters may be seen [here](https://stackoverflow.com/a/32116938/230513) and [here](https://stackoverflow.com/a/45718846/230513). – trashgod May 14 '19 at 02:52

1 Answers1

1

Methods such as setOnMouseClicked accept a javafx.event.EventHandler, which is a functional interface. In other words, it has a single abstract method which means it can be used as the target of a lambda expression or method reference. If your goal is to have your own Event class with a corresponding onXXX property, then your setOnXXX method must accept an EventHandler like all the others.

It's not enough, however, to simply set the property if you want the EventHandler to be invoked during normal event dispatching. You have to register it with the event dispatcher, which can be done via the protected Node.setEventHandler(EventType,EventHandler) method.

For example, let's say the following is your event class:

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

public class MyEvent extends Event {

    public static final EventType<MyEvent> ANY = new EventType<>(Event.ANY, "MY_EVENT");
    public static final EventType<MyEvent> AWESOME_THING = new EventType<>(ANY, "AWESOME_THING");

    public MyEvent(EventType<? extends MyEvent> eventType) {
        super(eventType);
    }

}

Then your custom node would look something like:

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.scene.control.Button;

public class MyButton extends Button {

    private final ObjectProperty<EventHandler<? super MyEvent>> onAwesomeThing 
            = new SimpleObjectProperty<>(this, "onAwesomeThing") {
            @Override protected void invalidated() {
                setEventHandler(MyEvent.AWESOME_THING, get());
            }
    };

    public final void setOnAwesomeThing(EventHandler<? super MyEvent> handler) {
        onAwesomeThing.set(handler);
    }

    public final EventHandler<? super MyEvent> getOnAwesomeThing() {
        return onAwesomeThing.get();
    }

    public final ObjectProperty<EventHandler<? super MyEvent>> onAwesomeThingProperty() {
        return onAwesomeThing;
    }

}

Typically, you'd have one property for each EventType associated with your event except for the "general" event type (e.g. MyEvent.ANY).

Now you can call myBtn.fireEvent(new MyEvent(MyEvent.AWESOME_THING)) and your registered EventHandler will be invoked. You can also register handlers via addEventFilter or addEventHandler.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Thanks @Slaw ! I feel like that's a really good answer and probably "right", but being so new at this, it's going to take me a bit to get my head around it enough to transpose it to my particular situation. THANKS! – KenM May 14 '19 at 13:15
  • OK - tried to just copy it in to get it to work and use that to help me get my head around it. Ran into compile issue with: `````` = new SimpleObjectProperty<>(this, "onAwesomeThing") ````` telling me "cannot infer type arguments for SimpleObjectProperty and then something about not support in source 8 - use source 9? - Please advise – KenM May 14 '19 at 14:07
  • In Java 8, when implementing a generic anonymous class you can't use the diamond operator. Just change it to `new SimpleObjectProperty>(this, "onAwesomeThing") { ... };`. The type inference system was improved in Java 9 which makes re-declaring the generic parameter(s) in an anonymous class unnecessary (thankfully). – Slaw May 14 '19 at 14:26
  • Ok thanks - that works (compiles) and I'm able to do a btn.setOnMyEvent on my button instance to some silly test code. However, and forgive my beginnerness, when I then call "btn.onMyEvent" , it is running the class code, not the "custom" code i put into it at instance time. maybe I'm not understanding the proper way to "call" that handler? Thanks for your patience and help! – KenM May 14 '19 at 16:54
  • Could you please edit your question to provide a [mcve]? – Slaw May 14 '19 at 17:08
  • OK - i put some working code in OP. I'm sure it could have been skinnier - but it works and illustrates issue. THANKS! – KenM May 14 '19 at 19:11
  • Just updated the code again - I think I'm raising the event properly now .. maybe ... possibly ... but still not getting desired functionality. @Slaw – KenM May 14 '19 at 20:55
  • Hi @Slaw. Thanks for formatting my question/code. I'm not sure if there is an updated answer in there or not? Currently - my "event" is firing like its supposed to. The issue is that it is not running the custom handling code for the instances? It;s just running the class code for that method. I'm sure I'm not calling something correctly - just not sure what? THANKS for your help! – KenM May 15 '19 at 18:46
  • Sorry, I just haven't had the time to properly analyze your code. Near as I can tell, you never call [`Node.fireEvent`](https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/Node.html#fireEvent(javafx.event.Event)) (instance method) or [`Event.fireEvent`](https://openjfx.io/javadoc/12/javafx.base/javafx/event/Event.html#fireEvent(javafx.event.EventTarget,javafx.event.Event)) (static method); the former is essentially a convenience method for the latter. – Slaw May 15 '19 at 19:22