EventBusis a versatile tool with a multitude of use cases. Depending on your coding style and preferences, you might want to reduce coupling between controllers and views by passing messages instead of having hard references to each other. The TornadoFX event bus can make sure that the messages are received on the appropriate thread, without having to do that concurrency house-keeping manually.
FXEventclass. In some cases, an event can be just a signal to some other component to trigger something to happen. In other cases, the event can contain data which will be broadcast to the subscribers of this event. Let us look at a couple of event definitions and how to use them.
Buttonto refresh a list of customers. The
Viewknows nothing of where the data is coming from or how it is produced, but it subscribes to the data events and uses the data once it arrives. Let us create two event classes for this use case.
object. Because it will never need to contain data, it will simply be broadcast to say that we want the customer list. The
RunOnproperty is set to
BackgroundThread, to signal that the receiver of this event should operate off of the JavaFX Application Thread. That means it will be given a background thread by default, so that it can do heavy work without blocking the UI. In the example above, we have added a static import for the
RunOnenum, so that we just write
EventBus.RunOn.BackgroundThread. Your IDE will help you to make this import so your code looks cleaner.
CustomerControllermight listen for this event, and load the customer list on demand before it fires an event with the actual customer data. First we need to define an event that can contain the customer list:
classrather than an
object, as it will contain actual data and vary. Also, it did not specify another value for the
RunOnproperty, so this event will be emitted on the JavaFX Application Thread.
CustomerListEvents, and once we have such an event we extract the customers from the event and set them into the
itemsproperty of the
Customer, but we now need to be more careful with the data we get back. If our UI allows for multiple customers to be edited at once, we need to make sure that we only apply data for the customer we asked for. This is quite easily accounted for though:
initfunction. First, we subscribe to
CustomerEvents, but we make sure to only act once we retrieve the customer we were asking for. If the
customerIdmatches, we assign the customer to the
itemproperty of our
ItemViewModel, and the UI is updated.
FXEvent, you dictate the value of the
runOnproperty. It is
ApplicationThreadby default, meaning that the subscriber will receive the event on the JavaFX Application Thread. This is useful for events coming from and going to other UI components, as well as backend services sending data to the UI. If you want to signal something to a backend service, one which is likely to perform heavy, long-running work, you should set
BackgroundThread, making sure the subscriber will operate off of the UI thread. The subscriber now no longer needs to make sure that it is off of the UI thread, so you remove a lot of thread-related house keeping calls. Used correctly this is convenient and powerful. Used incorrectly, you will have a non responsive UI. Make sure you understand this completely before playing with events, or always wrap long running tasks in
FXEventconstructor. This will make sure that only subscribers from the given scope will receive your event.
CustomerListRequestis not an object anymore since it needs to contain the scope parameter. You would now fire this event from any UI Component like this:
UIComponentis passed into the
CustomerListRequest. When customer data comes back, the framework takes care of discriminating on scope and only apply the results if they are meant for you. You do not need to mention the scope to the subscribe function call, as the framework will associate your subscription with the scope your are in at the time you create the subscription.
Fragmentare only active when that component is docked. That means that even if you have a
Viewthat has been previously initialized and used, event subscriptions will not reach it unless the
Viewis docked inside a window or some other component. Once the view is docked, the events will reach it. Once it is undocked, the events will no longer be delivered to your component. This takes care of the need for you to manually deregister subscribers when you discard of a view.
Controllershowever, subscriptions are always active until you call
unsubscribe. You need to keep in mind that controllers are lazily loaded, so if nothing references your controller, the subscriptions will never be registered in the first place. If you have such a controller with no other references, but you want it to subscribe to events right away, a good place to eagerly load it would be the
initblock of your
onDock()or any other callback function that might be invoked more than once for the duration of the component lifecycle. The safest place to create event subscriptions is in the
initblock of the component.
ItemViewModelwith injection is often a more streamlined solution to passing data and keeping UI state. This example was provided to explain how the event system works, not to convince you to write your UIs this way all the time.
times = nparameter to subscribe to control how many times the event is triggered before it is unsubscribed: