OK - I've fought and fought with this and read a million posts, tutorials, etc. None of them seem to address directly what I'm trying to do here. I've got this little code example to illustrate.
Basically, I want to be able to raise/fire a custom event from the button and have the label respond to that event.
(Please note that I AM NOT INTERESTED in finding other ways to do it than what I'm trying to do here with the actual Event objects. I know perfectly well how to do it with change listeners and such, but I want to learn how to do it this way.)
Here is the code, which you can see doesn't get me the result I'm looking for. Much of it comes from some examples (that didn't really work for me) and I admit there's parts here that are going "whoosh" over my head.
If someone can help me get it working, I can dissect it later to make sure I understand what is going on exactly. Here's the code:
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.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
//-------------------------------------------------------------------
public class MyDemo extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// label that should receive the event and react to it
MyLabel lblReceiver = new MyLabel("And I Should Receive & React");
// button to firs the event
Button btnSender = new Button("Initate Event");
btnSender.setPrefWidth(200);
btnSender.setOnAction(e -> {
MyEvent.fireEvent(lblReceiver, e); // really confused what the first parameter here is supposed to be.
});
// set up stage and show it
Stage stage = new Stage();
VBox root = new VBox(btnSender, lblReceiver);
root.setSpacing(10);
root.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
//-------------------------------------------------------------------
// Interface for objects that want to listen to my event
//-------------------------------------------------------------------
interface MyEventListener {
void onMyEvent();
}
//-------------------------------------------------------------------
// My event definition itself
//-------------------------------------------------------------------
class MyEvent extends Event {
public static final EventType<MyEvent> MY_EVENT = new EventType<>(ANY, "MY_EVENT");
public MyEvent(EventType<? extends MyEvent> eventType) {
super(eventType);
}
}
//-------------------------------------------------------------------
//base/parent class for my label - this is what should receive/respond to event and
// where I'm sure i have problems - just don't know what.
//-------------------------------------------------------------------
class MyLabel extends Label implements MyEventListener {
public MyLabel(String name) {
this.setAlignment(Pos.CENTER);
this.setText(name);
this.setPrefWidth(200);
}
@Override // this is what I'm expecting to happen when i click the button
public void onMyEvent() {
this.setText("YAY! i got the event!");
}
private final ObjectProperty<EventHandler<? super MyEvent>> onMyEventProp
= new SimpleObjectProperty<EventHandler<? super MyEvent>>(this, "onMyEvent") {
@Override
protected void invalidated() {
setEventHandler(MyEvent.MY_EVENT, get());
}
};
public final void setOnMyEvent(EventHandler<? super MyEvent> handler) {
onMyEventProp.set(handler);
}
public final EventHandler<? super MyEvent> getOnMyEvent() {
return onMyEventProp.get();
}
public final ObjectProperty<EventHandler<? super MyEvent>> onMyEventProperty() {
return onMyEventProp;
}
}
//-------------------------------------------------------------------
You seem have everything set up correctly except for two problems.
You never add an EventHandler
that listens for your event.
Implementing an arbitrary interface will not make your object react to your custom event. The event processing system has no knowledge of your interface and doesn't even know you've implemented it. If you want your onMyEvent()
method to be invoked when your event reaches the label, you would need to do something like:
public MyLabel() {
//...
addEventHandler(MyEvent.MY_EVENT, event -> onMyEvent());
}
Note I used addEventHandler
so that the class itself doesn't rely on the onMyEvent
property. If you relied on the property then outside code could accidentally break your code by replacing the EventHandler
.
This makes me wonder if the MyEventListener
interface is really necessary. Couldn't you just do what you needed inside the EventHandler
?
You never actually fire an instance of your MyEvent
.
You currently have:
btnSender.setOnAction(e -> MyEvent.fireEvent(lblReceiver, e));
This simply refires e
(an ActionEvent
) towards lblReceiver
. The fireEvent
method is a static
method declared by javafx.event.Event
. Prefixing the method call with MyEvent
doesn't change what method is actually called here. Change that to the following:
btnSender.setOnAction(e -> Event.fireEvent(lblReceiver, new MyEvent()));
// or...
btnSender.setOnAction(e -> lblReceiver.fireEvent(new MyEvent()));
// The second option is a convenience method and simply forwards to Event.fireEvent
In order to actually fire an instance of your own event class at your label.
There are two phases to event processing in JavaFX:
Capturing phase
Node.addEventFilter(EventType,EventHandler)
.Bubbling phase
Node.addEventHandler(EventType,EventHandler)
and via properties such as Node.onKeyPressed
and ButtonBase.onAction
.And event processing consists of the following components:
javafx.event.Event
(and subclasses)
The actual object passed around. Carries information related to the event (e.g. the cursor's location for MouseEvent
s).
An Event
also carries the source. However, the source is dynamic; it will always be the object the EventHandler
currently handling the event was added to. So an EventHandler
added to a Button
will have the Button
as the event's source, but the same event will have the parent as it's source for an EventHandler
added to the parent. This behavior doesn't change even if you used the same EventHandler
instance both times.
Refines the meaning of a class of Event
. For example, a MouseEvent
with the MOUSE_PRESSED
type means, unsurprisingly, the mouse was pressed. An EventType
has a supertype; this forms a hierarchy across all types for every kind of event. A handler registered for a supertype will also be notified of subtypes.
You cannot have two or more EventType
s with the same supertype and name. This is why instances are typically public static final
fields of the corresponding event class.
Responsible for dispatching the event to the appropriate handlers. Each object capable of being the target of an event typically has its own dispatcher. For example, each Window
, Scene
, and Node
instance has its own EventDispatcher
instance (held in a property).
Note: Outside highly specialized situations, you will never have to deal with this interface directly.
javafx.event.EventDispatchChain
Represents the path the event will take when it's fired at a target. Instances of this interface function as a stack of EventDispatcher
s. When an event is fired, a new chain is created and configured based on the event's target. In the case of the scene graph and the target is a Node
, the stack consists of the EventDispatcher
s belonging to the Window
, the Scene
, and then each Node
down to (parent to child) and including the target. The event travels down this chain (the capturing phase) and then back up this chain (the bubbling phase).
Note: You will likely never need to use this interface directly.
The actual target of the event. The EventTarget
interface has one method that is used to build the EventDispatchChain
. In other words, the target determines the path of the event. This is the first argument in Event.fireEvent(EventTarget,Event)
.
Note: You only need to use this interface if you're creating an object that can (obviously) be the target of events (and your object doesn't extend from an implementation of EventTarget
).