Create a Custom Event in Java

Create a custom event in Java

You probably want to look into the observer pattern.

Here's some sample code to get yourself started:

import java.util.*;

// An interface to be implemented by everyone interested in "Hello" events
interface HelloListener {
void someoneSaidHello();
}

// Someone who says "Hello"
class Initiater {
private List<HelloListener> listeners = new ArrayList<HelloListener>();

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

public void sayHello() {
System.out.println("Hello!!");

// Notify everybody that may be interested.
for (HelloListener hl : listeners)
hl.someoneSaidHello();
}
}

// Someone interested in "Hello" events
class Responder implements HelloListener {
@Override
public void someoneSaidHello() {
System.out.println("Hello there...");
}
}

class Test {
public static void main(String[] args) {
Initiater initiater = new Initiater();
Responder responder = new Responder();

initiater.addListener(responder);

initiater.sayHello(); // Prints "Hello!!!" and "Hello there..."
}
}

Related article: Java: Creating a custom event

Java - Create a Custom event and listener

So I made a minimal example maybe that will help you:

You need an interface for your listener:

public interface MyEventListener
{
public void onMyEvent();
}

Then for your String you need some wrapper class that also handles your events

public class EventString
{
private String myString;

private List<MyEventListener> eventListeners;

public EventString(String myString)
{
this.myString = myString;
this.eventListeners = new ArrayList<MyEventListener>();
}

public void addMyEventListener(MyEventListener evtListener)
{
this.eventListeners.add(evtListener);
}

public void setValue(String val)
{
myString = val;

if (val.equals("hello world"))
{
eventListeners.forEach((el) -> el.onMyEvent());
}
}
}

You see that the myString field is private and only accessible using the setValue method. This is so we can see when our event condition triggers.

And then you only need some implementation of this, such as:

EventString temp = new EventString("test");

temp.addMyEventListener(() -> {
System.out.println("hello world detected");
});

temp.setValue("hello world");

How to create custom events in Java

I don't have an equivalent for the 'JsonObject' type, but other than that I think the following may work for you, using your own custom EventHandler functional interface, custom EventArgs class, and generic 'Event' helper class:

import java.util.*;

public class HighlightsObjectHandler
{
// Constants
private static final String JsonKeysHighlightsHolder = "Items",
JsonKeysHighlightUrl = "Url",
JsonKeysHighlightTranslationsHolder = "Traducoes",
JsonKeysHighlightTranslationLanguage = "Idioma",
JsonKeysHighlightTranslationText = "Titulo",
JsonKeysHighlightTranslationImage = "Imagem";

// Handlers
public Event<CustomEventHandler> HighlightsJsonChanged = new Event<CustomEventHandler>();

public Event<CustomEventHandler> HighlightsContentChanging = new Event<CustomEventHandler>();
public Event<CustomEventHandler> HighlightsContentChanged = new Event<CustomEventHandler>();

// Variables
private String _json;

// Properties
public final String getHighlightsJson()
{
return _json;
}
public final void setHighlightsJson(String value)
{
if (!_json.equals(value) && value != null)
{
_json = value;

OnHighlightsJsonChanged(CustomEventArgs.Empty);

ParseJson();
}
}

private boolean HighlightsUpdating;
public final boolean getHighlightsUpdating()
{
return HighlightsUpdating;
}
private void setHighlightsUpdating(boolean value)
{
HighlightsUpdating = value;
}
private ArrayList<HighlightObject> Highlights;
public final ArrayList<HighlightObject> getHighlights()
{
return Highlights;
}
private void setHighlights(ArrayList<HighlightObject> value)
{
Highlights = value;
}

// Methods
private void ParseJson()
{
//todo: no equivalent to 'JsonObject':
JsonObject jsonObject = null;

//todo: no equivalent to 'out' parameter:
if (JsonObject.TryParse(HighlightsJson, jsonObject))
{
OnHighlightsContentChanging(CustomEventArgs.Empty);

// Json parsing and other stuff...
// ... it shouldn't matter for this question.

OnHighlightsContentChanged(CustomEventArgs.Empty);
}
}

// Events
public final void OnHighlightsJsonChanged(CustomEventArgs eventArgs)
{
if (HighlightsJsonChanged != null)
{
for (CustomEventHandler listener : HighlightsJsonChanged.listeners())
{
listener.invoke(this, eventArgs);
}
}
}

public final void OnHighlightsContentChanging(CustomEventArgs eventArgs)
{
setHighlightsUpdating(true);

if (HighlightsContentChanging != null)
{
for (CustomEventHandler listener : HighlightsContentChanging.listeners())
{
listener.invoke(this, eventArgs);
}
}
}
public final void OnHighlightsContentChanged(CustomEventArgs eventArgs)
{
setHighlightsUpdating(false);

if (HighlightsContentChanged != null)
{
for (CustomEventHandler listener : HighlightsContentChanged.listeners())
{
listener.invoke(this, eventArgs);
}
}
}

// Constructors
public HighlightsObjectHandler()
{
setHighlights(new ArrayList<HighlightObject>());
}
}

@FunctionalInterface
public interface CustomEventHandler
{
void invoke(object sender, CustomEventArgs e);
}
public class CustomEventArgs
{
public static readonly CustomEventArgs Empty;

public CustomEventArgs()
{
}
}

//this is produced as a helper class by C# to Java Converter:
public final class Event<T>
{
private java.util.Map<String, T> namedListeners = new java.util.HashMap<String, T>();
public void addListener(String methodName, T namedEventHandlerMethod)
{
if (!namedListeners.containsKey(methodName))
namedListeners.put(methodName, namedEventHandlerMethod);
}
public void removeListener(String methodName)
{
if (namedListeners.containsKey(methodName))
namedListeners.remove(methodName);
}

private java.util.List<T> anonymousListeners = new java.util.ArrayList<T>();
public void addListener(T unnamedEventHandlerMethod)
{
anonymousListeners.add(unnamedEventHandlerMethod);
}

public java.util.List<T> listeners()
{
java.util.List<T> allListeners = new java.util.ArrayList<T>();
allListeners.addAll(namedListeners.values());
allListeners.addAll(anonymousListeners);
return allListeners;
}
}

Java custom event handler and listeners

Let's say your class that raises the event is called A. And the class that needs to listen for the event is called B. And the event is called SomeEvent.

First, create an interface called SomeEventListener:

public interface SomeEventListener {
void onSomeEvent ();
}

If there are arguments that you want to pass when the event occurs (typically something about the event), you can add it to the method.

Then in A, you add a field:

private SomeEventListener listener;

and a method:

public void setSomeEventListener (SomeEventListener listener) {
this.listener = listener;
}

This way, B can call setSomeEventListener to set the listener.

When the event occurs, A should call

if (listener != null) listener.onSomeEvent ();

And that's all there is to A!

In B, you need to implement the interface:

public class B implements SomeEventListener {
public void onSomeEvent () {
//do whatever you like when SomeEvent happens.
}
}

And you can listen for SomeEvent like this:

someInstanceOfA.setSomeEventListener (this);

And after this call, all the SomeEvent raised by A can be listened by B!

Using the Observable and Observer pattern, we can see that A is an Observable and B is an Observer.

That's easy!

How to Properly Set Up custom Event

Answer

You seem have everything set up correctly except for two problems.

  1. 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?

  2. 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.


Fundamentals of Event Dispatching

There are two phases to event processing in JavaFX:

  • Capturing phase

    • The first phase. Here the event travels from the start of its path down to the target. At each step along the way the appropriate event filters are invoked. Filters are added via methods such as Node.addEventFilter(EventType,EventHandler).
  • Bubbling phase

    • The second phase. Here the event travels from the target back up to the start of the path. At each step along the way the appropriate event handlers are invoked. Handlers are added via methods such as 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 MouseEvents).

      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.

  • javafx.event.EventType

    • 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 EventTypes with the same supertype and name. This is why instances are typically public static final fields of the corresponding event class.

  • javafx.event.EventHandler

    • Handles any events its registered for. This is a functional interface (can be the target of a lambda expression or method reference). It's implementations of this interface that do the application-level work (i.e. do something when a button is fired).
  • javafx.event.EventDispatcher

    • 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 EventDispatchers. 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 EventDispatchers 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.

  • javafx.event.EventTarget

    • 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).

Custom JavaFX events

You cannot. JavaFX uses javafx.event.Events or subtypes, so String or even primitive int can't be passed.

You could however create a custom subtype of Event and add the parameters to this class.

Similarly only classes implementing javafx.event.EventHandler can be registered as event handlers.

You could create a event handler class that delegates to your methods though:

public abstract class CustomEvent extends Event {

public static final EventType<CustomEvent> CUSTOM_EVENT_TYPE = new EventType(ANY);

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

public abstract void invokeHandler(MyCustomEventHandler handler);

}
public class CustomEvent1 extends CustomEvent {

public static final EventType<CustomEvent> CUSTOM_EVENT_TYPE_1 = new EventType(CUSTOM_EVENT_TYPE, "CustomEvent1");

private final int param;

public CustomEvent1(int param) {
super(CUSTOM_EVENT_TYPE_1);
this.param = param;
}

@Override
public void invokeHandler(MyCustomEventHandler handler) {
handler.onEvent1(param);
}

}
public class CustomEvent2 extends CustomEvent {

public static final EventType<CustomEvent> CUSTOM_EVENT_TYPE_2 = new EventType(CUSTOM_EVENT_TYPE, "CustomEvent2");

private final String param;

public CustomEvent2(String param) {
super(CUSTOM_EVENT_TYPE_2);
this.param = param;
}

@Override
public void invokeHandler(MyCustomEventHandler handler) {
handler.onEvent2(param);
}

}
public abstract class MyCustomEventHandler implements EventHandler<CustomEvent> {

public abstract void onEvent1(int param0);

public abstract void onEvent2(String param0);

@Override
public void handle(CustomEvent event) {
event.invokeHandler(this);
}
}

Usage Example

Button btn = new Button("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
btn.fireEvent(new CustomEvent1(42));
btn.fireEvent(new CustomEvent2("Hello World"));
});

btn.addEventHandler(CustomEvent.CUSTOM_EVENT_TYPE, new MyCustomEventHandler() {

@Override
public void onEvent1(int param0) {
System.out.println("integer parameter: " + param0);
}

@Override
public void onEvent2(String param0) {
System.out.println("string parameter: "+param0);
}
});

How can i make custom event with property?

For Kotlin:

val MY_CUSTOM_EVENT =
EventType<Event>(Event.ANY, "MY_CUSTOM_EVENT" + UUID.randomUUID().toString())
var onOnlineClick = object : SimpleObjectProperty<EventHandler<Event>>() {
override fun getBean(): Any {
return this
}

override fun getName(): String {
return "onMyCustomEvent"
}

override fun invalidated() {
setEventHandler(MY_CUSTOM_EVENT, get())
}
}

fun onMyCustomEvent(): ObjectPropertyBase<EventHandler<Event>> {
return onMyCustomEvent
}

fun setOnMyCustomEvent(value: EventHandler<Event>) {
onMyCustomEvent.set(value)
}

fun getOnMyCustomEvent(): EventHandler<Event> {
return onMyCustomEvent.get()
}

Somewhere property

fun myProperty(): BooleanProperty {
return myCustomProperty ?: run {
myCustomProperty = object : SimpleBooleanProperty(false) {
override fun invalidated() {
fireEvent(Event(MY_CUSTOM_EVENT)) // <---this
}

override fun getBean(): Any {
return this
}

override fun getName(): String {
return "myProperty"
}
}
return myCustomProperty as SimpleBooleanProperty
}
}


Related Topics



Leave a reply



Submit