© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
K. Sharan, P. SpäthLearn JavaFX 17https://doi.org/10.1007/978-1-4842-7848-2_9

9. Event Handling

Kishori Sharan1   and Peter Späth2
(1)
Montgomery, AL, USA
(2)
Leipzig, Sachsen, Germany
 
In this chapter, you will learn:
  • What an event is

  • What an event source, an event target, and event type are

  • About the event processing mechanism

  • How to handle events using event filters and event handlers

  • How to handle mouse events, key events, and window events

The examples of this chapter lie in the com.jdojo.event package. In order for them to work, you must add a corresponding line to the module-info.java file:
...
opens com.jdojo.event to javafx.graphics, javafx.base;
...

What Is an Event?

In general, the term event is used to describe an occurrence of interest. In a GUI application, an event is an occurrence of a user interaction with the application. Clicking the mouse and pressing a key on the keyboard are examples of events in a JavaFX application.

An event in JavaFX is represented by an object of the javafx.event.Event class or any of its subclasses. Every event in JavaFX has three properties:
  • An event source

  • An event target

  • An event type

When an event occurs in an application, you typically perform some processing by executing a piece of code. The piece of code that is executed in response to an event is known as an event handler or an event filter . I will clarify the difference between these shortly. For now, think of both as a piece of code, and I will refer to both of them as event handlers. When you want to handle an event for a UI element, you need to add event handlers to the UI element, for example, a Window, a Scene, or a Node. When the UI element detects the event, it executes your event handlers.

The UI element that calls event handlers is the source of the event for those event handlers. When an event occurs, it passes through a chain of event dispatchers. The source of an event is the current element in the event dispatcher chain. The event source changes as the event passes through one dispatcher to another in the event dispatcher chain.

The event target is the destination of an event. The event target determines the route through which the event travels during its processing. Suppose a mouse click occurs over a Circle node. In this case, the Circle node is the event target of the mouse-clicked event.

The event type describes the type of the event that occurs. Event types are defined in a hierarchical fashion. Each event type has a name and a supertype.

The three properties that are common to all events in JavaFX are represented by objects of three different classes. Specific events define additional event properties; for example, the event class to represent a mouse event adds properties to describe the location of the mouse cursor and the state of the mouse buttons, among others. Table 9-1 lists the classes and interfaces involved in event processing. JavaFX has an event delivery mechanism that defines the details of the occurrence and processing of events. I will discuss all of these in detail in subsequent sections.
Table 9-1

Classes Involved in Event Processing

Name

Class/Interface

Description

Event

Class

An instance of this class represents an event. Several subclasses of the Event class exist to represent specific types of events.

EventTarget

Interface

An instance of this interface represents an event target.

EventType

Class

An instance of this class represents an event type, for example, mouse pressed, mouse released, mouse moved.

EventHandler

Interface

An instance of this interface represents an event handler or an event filter. Its handle() method is called when the event for which it has been registered occurs.

Event Class Hierarchy

Classes representing events in JavaFX are arranged in hierarchical fashion through class inheritance. Figure 9-1 shows a partial class diagram for the Event class. The Event class is at the top of the class hierarchy, and it inherits from java.util.EventObject class, which is not shown in the diagram.
Figure 9-1

A partial class hierarchy for the javafx.event.Event class

Subclasses of the Event class represent specific types of events. Sometimes, a subclass of the Event class is used to represent a generic event of some kind. For example, the InputEvent class represents a generic event to indicate a user input event, whereas the KeyEvent and MouseEvent classes represent specific input events such as the user input from the keyboard and mouse, respectively. An object of the WindowEvent class represents an event of a window, for example, showing and hiding of the window. An object of the ActionEvent is used to represent several kinds of events denoting some type of action, for example, firing a button or a menu item. Firing of a button may happen if the user clicks it with the mouse, presses some keys, or touches it on the touch screen.

The Event class provides properties and methods that are common to all events. The getSource() method returns an Object, which is the source of the event. The Event class inherits this method from the EventObject class. The getTarget() method returns an instance of the EventTarget interface, which is the target of the event. The getEventType() method returns an object of the EventType class, which indicates the type of the event.

The Event class contains consume() and isConsumed() methods . As noted before, an event travels from one element to another in an event-dispatching chain. Calling the consume() method on an Event object indicates that the event has been consumed and no further processing is required. After the consume() method is called, the event does not travel to the next element in the event processing chain. The isConsumed() method returns true if the consume() method has been called; otherwise, it returns false.

Specific Event subclasses define more properties and methods. For example, the MouseEvent class defines getX() and getY() methods that return the x and y coordinates of the mouse cursor relative to the source of the event. I’ll explain the details of the methods in event-specific classes when I discuss them later in this chapter or subsequent chapters.

Event Targets

An event target is a UI element (not necessarily just Nodes) that can respond to events. Technically, a UI element that wants to respond to events must implement the EventTarget interface. That is, in JavaFX, implementing the EventTarget interface makes a UI element eligible to be an event target.

The Window, Scene, and Node classes implement the EventTarget interface. This means that all nodes, including windows and scenes, can respond to events. The classes for some UI elements, for example, Tab, TreeItem, and MenuItem, do not inherit from the Node class. They can still respond to events because they implement the EventTarget interface. If you develop a custom UI element, you will need to implement this interface if you want your UI element to respond to events.

The responsibility of an event target is to build a chain of event dispatchers, which is also called the event route. An event dispatcher is an instance of the EventDispatcher interface. Each dispatcher in the chain can affect the event by handling and consuming. An event dispatcher in the chain can also modify the event properties, substitute the event with a new event, or chain the event route. Typically, an event target route consists of dispatchers associated with all UI elements in the container-child hierarchy. Suppose you have a Circle node placed in an HBox, which is placed in a Scene. The Scene is added to a Stage. If the mouse is clicked on the Circle, the Circle becomes the event target. The Circle builds an event dispatcher chain whose route will be, from head to tail, the Stage, Scene, HBox, and Circle.

Event Types

An instance of the EventType class defines an event type. Why do you need a separate class to define event types? Aren’t separate event classes, for example, KeyEvent, MouseEvent, for each event sufficient to define event types? Can’t you distinguish one event from another based on the event class? The EventType class is used to further classify the events within an event class. For example, the MouseEvent class only tells us that the user has used the mouse. It does not tell us the details of the mouse use, for example, whether the mouse was pressed, released, dragged, or clicked. The EventType class is used to classify these subevent types of an event. The EventType class is a generic class whose type parameter is defined as follows:
EventType<T extends Event>
Event types are hierarchical. They are hierarchical by implementation, not by class inheritance. Each event type has a name and a supertype. The getName() and getSuperType() methods in the EventType class return the name and supertype of an event type. The constant Event.ANY, which is the same as the constant EventType.ROOT, is the supertype of all events in JavaFX. Figure 9-2 shows a partial list of some event types that have been predefined in some event classes.
Figure 9-2

A partial list of predefined event types for some event classes

Note that the arrows in the diagram do not denote class inheritance. They denote dependencies. For example, the InputEvent.ANY event type depends on the Event.ANY event type, as the latter is the supertype of the former.

An event class, which has subevent types, defines an ANY event type. For example, the MouseEvent class defines an ANY event type that represents a mouse event of any type, for example, mouse released, mouse clicked, mouse moved. MOUSE_PRESSED and MOUSE_RELEASED are other event types defined in the MouseEvent class. The ANY event type in an event class is the supertype of all other event types in the same event class. For example, the MouseEvent.ANY event type is the supertype of MOUSE_RELEASED and MOUSE_PRESSED mouse events.

Event Processing Mechanism

When an event occurs, several steps are performed as part of the event processing:
  • Event target selection

  • Event route construction

  • Event route traversal

Event Target Selection

The first step in the event processing is the selection of the event target. Recall that an event target is the destination node of an event. The event target is selected based on the event type.

For mouse events, the event target is the node at the mouse cursor. Multiple nodes can be available at the mouse cursor. For example, you can have a circle placed over a rectangle. The topmost node at the mouse cursor is selected as the event target.

The event target for key events is the node that has focus. How a node gets the focus depends on the type of the node. For example, a TextField may gain focus by clicking the mouse inside it or using the focus traversal keys such as Tab or Shift + Tab on the Windows format. Shapes such as Circles or Rectangles do not get focus by default. If you want them to receive key events, you can give them focus by calling the requestFocus() method of the Node class.

JavaFX supports touch and gesture events on touch-enabled devices. A touch event is generated by touching a touch screen. Each touch action has a point of contact called a touch point. It is possible to touch a touch screen with multiple fingers, resulting in multiple touch points. Each state of a touch point, for example, pressed, released, and so forth, generates a touch event. The location of the touch point determines the target of the touch event. For example, if the location of the touch event is a point within a circle, the circle becomes the target of the touch event. In case of multiple nodes at the touch point, the topmost node is selected as the target.

Users can interact with a JavaFX application using gestures. Typically, a gesture on a touch screen and a track pad consists of multiple touch points with touch actions. Examples of gesture events are rotating, scrolling, swiping, and zooming. A rotating gesture is performed by rotating two fingers around each other. A scrolling gesture is performed by dragging a finger on the touch screen. A swiping gesture is performed by dragging a finger (or multiple fingers) on the touch screen in one direction. A zooming gesture is performed to scale a node by dragging two fingers apart or closer.

The target for gesture events is selected depending on the type of gesture. For direct gestures, for example, gestures performed on touch screens, the topmost node at the center point of all touch points at the start of the gesture is selected as the event target. For indirect gestures, for example, gestures performed on a track pad, the topmost node at the mouse cursor is selected as the event target.

Event Route Construction

An event travels through event dispatchers in an event dispatch chain. The event dispatch chain is the event route. The initial and default routes for an event are determined by the event target. The default event route consists of the container-children path starting at the stage to the event target node.

Suppose you have placed a Circle and a Rectangle in an HBox and the HBox is the root node of the Scene of a Stage. When you click the Circle, the Circle becomes the event target. The Circle constructs the default event route, which is the path starting at the stage to the event target (the Circle).

In fact, an event route consists of event dispatchers that are associated with nodes. However, for all practical and understanding purposes, you can think of the event route as the path comprising the nodes. Typically, you do not deal with event dispatchers directly.

Figure 9-3 shows the event route for the mouse-clicked event. The nodes on the event route have been shown in gray background fills. The nodes on the event route are connected by solid lines. Note that the Rectangle that is part of the scene graph is not part of the event path when the Circle is clicked.
Figure 9-3

Construction of the default event route for an event

An event dispatch chain (or event route) has a head and a tail. In Figure 9-3, the Stage and the Circle are the head and the tail of the event dispatch chain, respectively. The initial event route may be modified as the event processing progresses. Typically, but not necessarily, the event passes through all nodes in its route twice during the event traversal step, as described in the next section.

Event Route Traversal

An event route traversal consists of two phases:
  • Capture phase

  • Bubbling phase

An event travels through each node in its route twice: once during the capture phase and once during the bubbling phase. You can register event filters and event handlers to a node for specific event types. The event filters and event handlers registered to a node are executed as the event passes through the node during the capture phase and the bubbling phase, respectively. The event filters and handlers are passed in the reference of the current node as the source of the event. As the event travels from one node to another, the event source keeps changing. However, the event target remains the same from the start to the finish of the event route traversal.

During the route traversal, a node can consume the event in event filters or handlers, thus completing the processing of the event. Consuming an event is simply calling the consume() method on the event object. When an event is consumed, the event processing is stopped, even though some of the nodes in the route were not traversed at all.

Event Capture Phase

During the capture phase, an event travels from the head to the tail of its event dispatch chain. Figure 9-4 shows the traveling of a mouse-clicked event for the Circle in our example in the capture phase. The down arrows in the figure denote the direction of the event travel. As the event passes through a node, the registered event filters for the node are executed. Note that the event capture phase executes only event filters, not event handlers, for the current node.
Figure 9-4

The event capture phase

In Figure 9-4, the event filters for the Stage, Scene, HBox, and Circle are executed in order, assuming none of the event filters consumes the event.

You can register multiple event filters for a node. If the node consumes the event in one of its event filters, its other event filters, which have not been executed yet, are executed before the event processing stops. Suppose you have registered five event filters for the Scene in our example, and the first event filter that is executed consumes the event. In this case, the other four event filters for the Scene will still be executed. After executing the fifth event filter for the Scene, the event processing will stop, without the event traveling to the remaining nodes (HBox and Circle).

In the event capture phase, you can intercept events (and provide a generic response) that are targeted at the children of a node. For example, you can add event filters for the mouse-clicked event to the Stage in our example to intercept all mouse-clicked events for all its children. You can block events from reaching their targets by consuming the event in event filters for a parent node. For example, if you consume the mouse-clicked event in a filter for the Stage, the event will not reach its target, in our example, the Circle.

Event Bubbling Phase

During the bubbling phase, an event travels from the tail to the head of its event dispatch chain. Figure 9-5 shows the traveling of a mouse-clicked event for the Circle in the bubbling phase.
Figure 9-5

The event bubbling phase

The up arrows in Figure 9-5 denote the direction of the event travel. As the event passes through a node, the registered event handlers for the node are executed. Note that the event bubbling phase executes event handlers for the current node, whereas the event capture phase executes the event filters.

In our example, the event handlers for the Circle, HBox, Scene, and Stage are executed in order, assuming none of the event filters consumes the event. Note that the event bubbling phase starts at the target of the event and travels up to the topmost parent in the parent-children hierarchy.

You can register multiple event handlers for a node. If the node consumes the event in one of its event handlers, its other event handlers, which have not been executed yet, are executed before the event processing stops. Suppose you have registered five event handlers for the Circle in our example, and the first event handler that is executed consumes the event. In this case, the other four event handlers for the Circle will still be executed. After executing the fifth event handler for the Circle, the event processing will stop, without the event traveling to the remaining nodes (HBox, Scene, and Stage).

Typically, event handlers are registered to target nodes to provide a specific response to events. Sometimes, event handlers are installed on parent nodes to provide a default event response for all its children. If an event target decides to provide a specific response to the event, it can do so by adding event handlers and consuming the event, thus blocking the event from reaching the parent nodes in the event bubbling phase.

Let’s look at a trivial example. Suppose you want to display a message box to the user when they click anywhere inside a window. You can register an event handler to the window to display the message box. When the user clicks inside a circle in the window, you want to display a specific message. You can register an event handler to the circle to provide the specific message and consume the event. This will provide a specific event response when the circle is clicked, whereas for other nodes, the window provides a default event response.

Handling Events

Handling an event means executing the application logic in response to the occurrence of the event. The application logic is contained in the event filters and handlers, which are objects of the EventHandler interface, as shown in the following code:
public interface EventHandler<T extends Event> extends EventListener
        void handle(T event);
}

The EventHandler class is a generic class in the javafx.event package. It extends the EventListener marker interface, which is in the java.util package. The handle() method receives the reference of the event object, for example, the reference of the KeyEvent and MouseEvent, among others.

Both event filters and handlers are objects of the same EventHandler interface. You cannot tell whether an EventHandler object is an event filter or an event handler by just looking at it. In fact, you can register the same EventHandler object as event filters as well as handlers at the same time. The distinction between the two is made when they are registered to a node. Nodes provide different methods to register them. Internally, nodes know whether an EventHandler object was registered as an event filter or a handler. Another distinction between them is made based on the event traversal phase in which they are called. During the event capture phase, the handle() method of registered filters is called, whereas the handle() method of registered handlers is called in the event bubbling phase.

Tip

In essence, handling an event means writing the application logic for EventHandler objects and registering them to nodes as event filters, handlers, or both.

Creating Event Filters and Handlers

Creating event filters and handlers is as simple as creating objects of the class that implement the EventHandler interface. Using lambda expressions is the best choice for creating the event filters and handlers, as in the following code:
EventHandler<MouseEvent> aHandler = e -> /* Event handling code goes here */;

I use lambda expressions in this book to create event filters and handlers. If you are not familiar with lambda expressions, I suggest you learn at least the basics so you can understand the event handling code.

The following snippet of code creates a MouseEvent handler. It prints the type of the mouse event that occurs:
EventHandler<MouseEvent> mouseEventHandler =
        e -> System.out.println("Mouse event type: " + e.getEventType());

Registering Event Filters and Handlers

If you want a node to process events of specific types, you need to register event filters and handlers for those event types to the node. When the event occurs, the handle() method of the registered event filters and handlers for the node is called following the rules discussed in the previous sections. If the node is no longer interested in processing the events, you need to unregister the event filters and handlers from the node. Registering and unregistering event filters and handlers are also known as adding and removing event filters and handlers, respectively.

JavaFX provides two ways to register and unregister event filters and handlers to nodes:
  • Using the addEventFilter(), addEventHandler(), removeEventFilter(), and removeEventHandler() methods

  • Using the onXXX convenience properties

Using addXXX( ) and removeXXX( ) Methods

You can use the addEventFilter() and addEventHandler() methods to register event filters and handlers to nodes, respectively. These methods are defined in the Node class, Scene class, and Window class. Some classes (e.g., MenuItem and TreeItem) can be event targets; however, they are not inherited from the Node class. The classes provide only the addEventHandler() method for event handlers registration, such as
  • <T extends Event> void addEventFilter(EventType<T> eventType, EventHandler<? super T> eventFilter)

  • <T extends Event> void addEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler)

These methods have two parameters. The first parameter is the event type, and the second is an object of the EventHandler interface.

You can handle mouse-clicked events for a Circle using the following snippet of code:
import javafx.scene.shape.Circle;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
...
Circle circle = new Circle (100, 100, 50);
// Create a MouseEvent filter
EventHandler<MouseEvent> mouseEventFilter =
         e -> System.out.println("Mouse event filter has been called.");
// Create a MouseEvent handler
EventHandler<MouseEvent> mouseEventHandler =
         e -> System.out.println("Mouse event handler has been called.");
// Register the MouseEvent filter and handler to the Circle
// for mouse-clicked events
circle.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseEventFilter);
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEventHandler);

This code creates two EventHandler objects, which prints a message on the console. At this stage, they are not event filters or handlers. They are just two EventHandler objects. Note that giving the reference variables names and printing messages that use the words filter and handler does not make any difference in their status as filters and handlers. The last two statements register one of the EventHandler objects as an event filter and another as an event handler; both are registered for the mouse-clicked event.

Registering the same EventHandler object as event filters as well as handlers is allowed. The following snippet of code uses one EventHandler object as the filter and handler for the Circle to handle the mouse-clicked event:
// Create a MouseEvent EventHandler object
EventHandler<MouseEvent> handler = e ->
    System.out.println("Mouse event filter or handler has been called.");
// Register the same EventHandler object as the MouseEvent filter and handler
// to the Circle for mouse-clicked events
circle.addEventFilter(MouseEvent.MOUSE_CLICKED, handler);
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
Tip

You can add multiple event filters and handlers for a node using the addEventFilter() and addEventHandler() methods. You need to call these methods once for every instance of the event filters and handlers that you want to add.

Listing 9-1 has the complete program to demonstrate the handling of the mouse-clicked events of a Circle object. It uses an event filter and an event handler. Run the program and click inside the circle. When the circle is clicked, the event filter is called first, followed by the event handler. This is evident from the output. The mouse-clicked event occurs every time you click any point inside the circle. If you click outside the circle, the mouse-clicked event still occurs; however, you do not see any output because you have not registered event filters or handlers on the HBox, Scene, and Stage.
// EventRegistration.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class EventRegistration extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle (100, 100, 50);
                circle.setFill(Color.CORAL);
                // Create a MouseEvent filter
                EventHandler<MouseEvent> mouseEventFilter = e ->
                         System.out.println(
                             "Mouse event filter has been called.");
                // Create a MouseEvent handler
                EventHandler<MouseEvent> mouseEventHandler = e ->
                         System.out.println(
                             "Mouse event handler has been called.");
                // Register the MouseEvent filter and handler to
                     // the Circle for mouse-clicked events
                circle.addEventFilter(MouseEvent.MOUSE_CLICKED,
                         mouseEventFilter);
                circle.addEventHandler(MouseEvent.MOUSE_CLICKED,
                         mouseEventHandler);
                HBox root = new HBox();
                root.getChildren().add(circle);
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Registering Event Filters and Handlers");
                stage.show();
                stage.sizeToScene();
        }
}
Mouse event filter has been called.
Mouse event handler has been called.
...
Listing 9-1

Registering Event Filters and Handlers

To unregister an event filter and an event handler, you need to call the removeEventFilter() and removeEventHandler() methods, respectively:
  • <T extends Event> void removeEventFilter(EventType<T> eventType, EventHandler<? super T> eventFilter)

  • <T extends Event> void removeEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler)

The following snippet of code adds and removes an event filter to a Circle, and, later, it removes them. Note that once an EventHandler is removed from a node, its handle() method is not called when the event occurs:
// Create a MouseEvent EventHandler object
EventHandler<MouseEvent> handler = e ->
    System.out.println("Mouse event filter or handler has been called.");
// Register the same EventHandler object as the MouseEvent filter and handler
// to the Circle for mouse-clicked events
circle.addEventFilter(MouseEvent.MOUSE_CLICKED, handler);
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
...
// At a later stage, when you are no longer interested in handling the mouse
// clicked event for the Circle, unregister the event filter and handler
circle.removeEventFilter(MouseEvent.MOUSE_CLICKED, handler);
circle.removeEventHandler(MouseEvent.MOUSE_CLICKED, handler);

Using onXXX Convenience Properties

The Node, Scene, and Window classes contain event properties to store event handlers of some selected event types. The property names use the event type pattern. They are named as onXXX. For example, the onMouseClicked property stores the event handler for the mouse-clicked event type; the onKeyTyped property stores the event handler for the key-typed event; and so on. You can use the setOnXXX() methods of these properties to register event handlers for a node. For example, use the setOnMouseClicked() method to register an event handler for the mouse-clicked event and use the setOnKeyTyped() method to register an event handler for the key-typed event, and so on. The setOnXXX() methods in various classes are known as convenience methods for registering event handlers.

You need to remember some points about the onXXX convenience properties:
  • They only support the registration of event handlers, not event filters. If you need to register event filters, use the addEventFilter() method.

  • They only support the registration of one event handler for a node. Multiple event handlers for a node may be registered using the addEventHandler() method .

  • These properties exist only for the commonly used events for a node type. For example, the onMouseClicked property exists in the Node and Scene classes, but not in the Window class; the onShowing property exists in the Window class, but not in the Node and Scene classes.

The program in Listing 9-2 works the same as the program in Listing 9-1. This time, you have used the onMouseClicked property of the Node class to register the mouse-clicked event handler for the circle. Notice that to register the event filter, you have to use the addEventFilter() method as before. Run the program and click inside the circle. You will get the same output you got when running the code in Listing 9-1.
// EventHandlerProperties.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class EventHandlerProperties extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle (100, 100, 50);
                circle.setFill(Color.CORAL);
                HBox root = new HBox();
                root.getChildren().add(circle);
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle(
                         "Using convenience event handler properties");
                stage.show();
                stage.sizeToScene();
                // Create a MouseEvent filter
                EventHandler<MouseEvent> eventFilter = e ->
                         System.out.println(
                             "Mouse event filter has been called.");
                // Create a MouseEvent handler
                EventHandler<MouseEvent> eventHandler = e ->
                         System.out.println(
                             "Mouse event handler has been called.");
                // Register the filter using the addEventFilter() method
                circle.addEventFilter(MouseEvent.MOUSE_CLICKED,
                         eventFilter);
                // Register the handler using the setter method for
                // the onMouseCicked convenience event property
                circle.setOnMouseClicked(eventHandler);
        }
}
Listing 9-2

Using the Convenience Event Handler Properties

The convenience event properties do not provide a separate method to unregister the event handler. Setting the property to null unregisters the event handler that has already been registered:
// Register an event handler for the mouse-clicked event
circle.setOnMouseClicked(eventHandler);
...
// Later, when you are no longer interested in processing the mouse-clicked
// event, unregister it.
circle.setOnMouseClicked(null);

Classes that define the onXXX event properties also define getOnXXX() getter methods that return the reference of the registered event handler. If no event handler is set, the getter method returns null.

Execution Order of Event Filters and Handlers

There are some execution order rules for event filters and handlers for both similar and different nodes:
  • Event filters are called before event handlers. Event filters are executed from the topmost parent to the event target in the parent-child order. Event handlers are executed in the reverse order of the event filters. That is, the execution of the event handlers starts at the event target and moves up in the child-parent order.

  • For the same node, event filters and handlers for a specific event type are called before the event filters and handlers for generic types. Suppose you have registered event handlers to a node for MouseEvent.ANY and MouseEvent.MOUSE_CLICKED. Event handlers for both event types are capable of handling mouse-clicked events. When the mouse is clicked on the node, the event handler for the MouseEvent.MOUSE_CLICKED event type is called before the event handler for the MouseEvent.ANY event type. Note that a mouse-pressed event and a mouse-released event occur before a mouse-clicked event occurs. In our example, these events will be handled by the event handler for the MouseEvent.ANY event type.

  • The order in which the event filters and handlers for the same event type for a node are executed is not specified. There is one exception to this rule. Event handlers registered to a node using the addEventHandler() method are executed before the event handlers registered using the setOnXXX() convenience methods.

Listing 9-3 demonstrates the execution order of the event filters and handlers for different nodes. The program adds a Circle and a Rectangle to an HBox. The HBox is added to the Scene. An event filter and an event handler are added to the Stage, Scene, HBox, and Circle for the mouse-clicked event. Run the program and click anywhere inside the circle. The output shows the order in which filters and handlers are called. The output contains the event phase, type, target, source, and location. Notice that the source of the event changes as the event travels from one node to another. The location is relative to the event source. Because every node uses its own local coordinate system, the same point, where the mouse is clicked, has different values for (x, y) coordinates relative to different nodes.
// CaptureBubblingOrder.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
Listing 9-3

Execution Order for Event Filters and Handlers

If you click the rectangle, you will notice that the output shows the same path for the event through its parents as it did for the circle. The event still passes through the rectangle, which is the event target. However, you do not see any output, because you have not registered any event filters or handlers for the rectangle to output any message. You can click at any point outside the circle and rectangle to see the event target and the event path.
public class CaptureBubblingOrder extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle (50, 50, 50);
                circle.setFill(Color.CORAL);
                Rectangle rect = new Rectangle(100, 100);
                rect.setFill(Color.TAN);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(circle, rect);
                Scene scene = new Scene(root);
                // Create two EventHandlders
                EventHandler<MouseEvent> filter = e ->
                         handleEvent("Capture", e);
                EventHandler<MouseEvent> handler = e ->
                         handleEvent("Bubbling", e);
                // Register filters
                stage.addEventFilter(MOUSE_CLICKED, filter);
                scene.addEventFilter(MOUSE_CLICKED, filter);
                root.addEventFilter(MOUSE_CLICKED, filter);
                circle.addEventFilter(MOUSE_CLICKED, filter);
                // Register handlers
                stage.addEventHandler(MOUSE_CLICKED, handler);
                scene.addEventHandler(MOUSE_CLICKED, handler);
                root.addEventHandler(MOUSE_CLICKED, handler);
                circle.addEventHandler(MOUSE_CLICKED, handler);
                stage.setScene(scene);
                stage.setTitle(
                         "Event Capture and Bubbling Execution Order");
                stage.show();
        }
        public void handleEvent(String phase, MouseEvent e) {
                String type = e.getEventType().getName();
                String source = e.getSource().getClass().getSimpleName();
                String target = e.getTarget().getClass().getSimpleName();
                // Get coordinates of the mouse cursor relative to the
                // event source
                double x = e.getX();
                double y = e.getY();
                System.out.println(phase + ": Type=" + type +
                    ", Target=" + target +
                    ", Source=" +  source +
                    ", location(" + x + ", " + y + ")");
        }
}
Listing 9-4 demonstrates the execution order of event handlers for a node. It displays a circle. It registers three event handlers for the circle:
  • One for the MouseEvent.ANY event type

  • One for the MouseEvent.MOUSE_CLICKED event type using the addEventHandler() method

  • One for the MouseEvent.MOUSE_CLICKED event type using the setOnMouseClicked() method

Run the program and click inside the circle. The output shows the order in which three event handlers are called. The order will be similar to that presented in the discussion at the beginning of the section.
// HandlersOrder.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class HandlersOrder extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle(50, 50, 50);
                circle.setFill(Color.CORAL);
                HBox root = new HBox();
                root.getChildren().addAll(circle);
                Scene scene = new Scene(root);
                /* Register three handlers for the circle that can handle
                          mouse-clicked events */
                // This will be called last
                circle.addEventHandler(MouseEvent.ANY, e ->
                         handleAnyMouseEvent(e));
                // This will be called first
                circle.addEventHandler(MouseEvent.MOUSE_CLICKED, e ->
                         handleMouseClicked("addEventHandler()", e));
                // This will be called second
                circle.setOnMouseClicked(e ->
                         handleMouseClicked("setOnMouseClicked()", e));
                stage.setScene(scene);
                stage.setTitle(
                         "Execution Order of Event Handlers of a Node");
                stage.show();
        }
        public void handleMouseClicked(String registrationMethod,
                         MouseEvent e) {
                System.out.println(registrationMethod +
                    ": MOUSE_CLICKED handler detected a mouse click.");
        }
        public void handleAnyMouseEvent(MouseEvent e) {
                // Print a message only for mouse-clicked events,
                // ignoring other mouse events such as mouse-pressed,
                // mouse-released, etc.
                if (e.getEventType() == MouseEvent.MOUSE_CLICKED) {
                    System.out.println(
                               "MouseEvent.ANY handler detected a mouse click.");
                }
        }
}
addEventHandler(): MOUSE_CLICKED handler detected a mouse click.
setOnMouseClicked(): MOUSE_CLICKED handler detected a mouse click.
MouseEvent.ANY handler detected a mouse click.
Listing 9-4

Order of Execution of Event Handlers for a Node

Consuming Events

An event is consumed by calling its consume() method . The event class contains the method, and it is inherited by all event classes. Typically, the consume() method is called inside the handle() method of the event filters and handlers.

Consuming an event indicates to the event dispatcher that the event processing is complete and that the event should not travel any farther in the event dispatch chain. If an event is consumed in an event filter of a node, the event does not travel to any child node. If an event is consumed in an event handler of a node, the event does not travel to any parent node.

All event filters or handlers for the consuming node are called, irrespective of which filter or handler consumes the event. Suppose you have registered three event handlers for a node and the event handler, which is called first, consumes the event. In this case, the other two event handlers for the node are still called.

If a parent node does not want its child nodes to respond to an event, it can consume the event in its event filter. If a parent node provides a default response to an event in an event handler, a child node can provide a specific response and consume the event, thus suppressing the default response of the parent.

Typically, nodes consume most input events after providing a default response. The rule is that all event filters and handlers of a node are called, even if one of them consumes the event. This makes it possible for developers to execute their event filters and handlers for a node even if the node consumes the event.

The code in Listing 9-5 shows how to consume an event. Figure 9-6 shows the screen when you run the program.
// ConsumingEvents.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.input.MouseEvent;
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
Listing 9-5

Consuming Events

Figure 9-6

Consuming events

The program adds a Circle, a Rectangle, and a CheckBox to an HBox. The HBox is added to the scene as the root node. An event handler is added to the Stage, Scene, HBox, and Circle. Notice that you have a different event handler for the Circle, just to keep the program logic simple. When the check box is selected, the event handler for the circle consumes the mouse-clicked event, thus preventing the event from traveling up to the HBox, Scene, and Stage. If the check box is not selected, the mouse-clicked event on the circle travels from the Circle to the HBox, Scene, and Stage. Run the program and, using the mouse, click the different areas of the scene to see the effect. Notice that the mouse-clicked event handlers for the HBox, Scene, and Stage are executed, even if you click a point outside the circle, because they are in the event dispatch chain of the clicked nodes.
public class ConsumingEvents extends Application {
        private CheckBox consumeEventCbx =
              new CheckBox("Consume Mouse Click at Circle");
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle (50, 50, 50);
                circle.setFill(Color.CORAL);
                Rectangle rect = new Rectangle(100, 100);
                rect.setFill(Color.TAN);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(circle, rect, consumeEventCbx);
                Scene scene = new Scene(root);
                // Register mouse-clicked event handlers to all nodes ,
                // except the rectangle and checkbox
                EventHandler<MouseEvent> handler = e ->
                         handleEvent(e);
                EventHandler<MouseEvent> circleMeHandler = e ->
                         handleEventforCircle(e);
                stage.addEventHandler(MOUSE_CLICKED, handler);
                scene.addEventHandler(MOUSE_CLICKED, handler);
                root.addEventHandler(MOUSE_CLICKED, handler);
                circle.addEventHandler(MOUSE_CLICKED, circleMeHandler);
                stage.setScene(scene);
                stage.setTitle("Consuming Events");
                stage.show();
        }
        public void handleEvent(MouseEvent e) {
                print(e);
        }
        public void handleEventforCircle(MouseEvent e) {
                print(e);
                if (consumeEventCbx.isSelected()) {
                        e.consume();
                }
        }
        public void print(MouseEvent e) {
                String type = e.getEventType().getName();
                String source = e.getSource().getClass().getSimpleName();
                String target = e.getTarget().getClass().getSimpleName();
                // Get coordinates of the mouse cursor relative to the
                     // event source
                double x = e.getX();
                double y = e.getY();
                System.out.println("Type=" + type + ", Target=" + target
                    ", Source=" +  source +
                   ", location(" + x + ", " + y + ")");
        }
}

Clicking the check box does not execute the mouse-clicked event handlers for the HBox, Scene, and Stage, whereas clicking the rectangle does. Can you think of a reason for this behavior? The reason is simple. The check box has a default event handler that takes a default action and consumes the event, preventing it from traveling up the event dispatch chain. The rectangle does not consume the event, allowing it to travel up the event dispatch chain.

Tip

Consuming an event by the event target in an event filter has no effect on the execution of any other event filters. However, it prevents the event bubbling phase from happening. Consuming an event in the event handlers of the topmost node, which is the head of the event dispatch chain, has no effect on the event processing at all.

Handling Input Events

An input event indicates a user input (or a user action), for example, clicking the mouse, pressing a key, touching a touch screen, and so forth. JavaFX supports many types of input events. Figure 9-7 shows the class diagram for some of the classes that represent input event. All input event–related classes are in the javafx.scene.input package. The InputEvent class is the superclass of all input event classes. Typically, nodes execute the user-registered input event handlers before taking the default action. If the user event handlers consume the event, nodes do not take the default action. Suppose you register key-typed event handlers for a TextField, which consume the event. When you type a character, the TextField will not add and display it as its content. Therefore, consuming input events for nodes gives you a chance to disable the default behavior of the node. In the next sections, I will discuss mouse and key input events.
Figure 9-7

Class hierarchy for some input events

Handling Mouse Events

An object of the MouseEvent class represents a mouse event. The MouseEvent class defines the following mouse-related event type constants. All constants are of the type EventType<MouseEvent>. The Node class contains the convenience onXXX properties for most of the mouse event types that can be used to add one event handler of a specific mouse event type for a node:
  • ANY: It is the supertype of all mouse event types. If a node wants to receive all types of mouse events, you would register handlers for this type. The InputEvent.ANY is the supertype of this event type.

  • MOUSE_PRESSED: Pressing a mouse button generates this event. The getButton() method of the MouseEvent class returns the mouse button that is responsible for the event. A mouse button is represented by the NONE, PRIMARY, MIDDLE, and SECONDARY constants defined in the MouseButton enum.

  • MOUSE_RELEASED: Releasing a mouse button generates this event. This event is delivered to the same node on which the mouse was pressed. For example, you can press a mouse button on a circle, drag the mouse outside the circle, and release the mouse button. The MOUSE_RELEASED event will be delivered to the circle, not the node on which the mouse button was released.

  • MOUSE_CLICKED: This event is generated when a mouse button is clicked on a node. The button should be pressed and released on the same node for this event to occur.

  • MOUSE_MOVED: Moving the mouse without pressing any mouse buttons generates this event.

  • MOUSE_ENTERED: This event is generated when the mouse enters a node. The event capture and bubbling phases do not take place for this event. That is, event filters and handlers of the parent nodes of the event target of this event are not called.

  • MOUSE_ENTERED_TARGET: This event is generated when the mouse enters a node. It is a variant of the MOUSE_ENTERED event type. Unlike the MOUSE_ENTERED event, the event capture and bubbling phases take place for this event.

  • MOUSE_EXITED: This event is generated when the mouse leaves a node. The event capture and bubbling phases do not take place for this event, that is, it is delivered only to the target node.

  • MOUSE_EXITED_TARGET: This event is generated when the mouse leaves a node. It is a variant of the MOUSE_EXITED event type. Unlike the MOUSE_EXITED event, the event capture and bubbling phases take place for this event.

  • DRAG_DETECTED: This event is generated when the mouse is pressed and dragged over a node over a platform-specific distance threshold.

  • MOUSE_DRAGGED: Moving the mouse with a pressed mouse button generates this event. This event is delivered to the same node on which the mouse button was pressed, irrespective of the location of the mouse pointer during the drag.

Getting Mouse Location

The MouseEvent class contains methods to give you the location of the mouse when a mouse event occurs. You can obtain the mouse location relative to the coordinate systems of the event source node, the scene, and the screen. The getX() and getY() methods give the (x, y) coordinates of the mouse relative to the event source node. The getSceneX() and getSceneY() methods give the (x, y) coordinates of the mouse relative to the scene to which the node is added. The getScreenX() and getScreenY() methods give the (x, y) coordinates of the mouse relative to the screen to which the node is added.

Listing 9-6 contains the program to show how to use the methods in the MouseEvent class to know the mouse location. It adds a MOUSE_CLICKED event handler to the stage, and the stage can receive the notification when the mouse is clicked anywhere in its area. Run the program and click anywhere in the stage, excluding its title bar if you are running it on the desktop. Each mouse click prints a message describing the source, target, and location of the mouse relative to the source, scene, and screen.
// MouseLocation.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MouseLocation extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle (50, 50, 50);
                circle.setFill(Color.CORAL);
                Rectangle rect = new Rectangle(100, 100);
                rect.setFill(Color.TAN);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(circle, rect);
                // Add a MOUSE_CLICKED event handler to the stage
                stage.addEventHandler(MouseEvent.MOUSE_CLICKED, e ->
                         handleMouseMove(e));
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Mouse Location");
                stage.show();
        }
        public void handleMouseMove(MouseEvent e) {
                String source = e.getSource().getClass().getSimpleName();
                String target = e.getTarget().getClass().getSimpleName();
                // Mouse location relative to the event source
                double sourceX = e.getX();
                double sourceY = e.getY();
                // Mouse location relative to the scene
                double sceneX = e.getSceneX();
                double sceneY = e.getSceneY();
                // Mouse location relative to the screen
                double screenX = e.getScreenX();
                double screenY = e.getScreenY();
                System.out.println("Source=" +  source +
                   ", Target=" + target +
                   ", Location:" +
                   " source(" + sourceX + ", " + sourceY + ")" +
                   ", scene(" + sceneX + ", " + sceneY + ")" +
                   ", screen(" + screenX + ", " + screenY + ")");
        }
}
Listing 9-6

Determining the Mouse Location During Mouse Events

Representing Mouse Buttons

Typically, a mouse has three buttons. You will also find some that have only one or two buttons. Some platforms provide ways to simulate the missing mouse buttons. The MouseButton enum in the javafx.scene.input package contains constants to represent mouse buttons. Table 9-2 contains the list of constants defined in the MouseButton enum.
Table 9-2

Constants for the MouseButton Enum

MouseButton Enum Constant

Description

NONE

It represents no button.

PRIMARY

It represents the primary button. Usually, it is the left button in the mouse.

MIDDLE

It represents the middle button.

SECONDARY

It represents the secondary button. Usually, it is the right button in the mouse.

The location of the primary and second mouse buttons depends on the mouse configuration. Typically, for right-handed users, the left and right buttons are configured as the primary and secondary buttons, respectively. For the left-handed users, the buttons are configured in the reverse order. If you have a two-button mouse, you do not have a middle button.

State of Mouse Buttons

The MouseEvent object that represents a mouse event contains the state of the mouse buttons at the time the event occurs. The MouseEvent class contains many methods to report the state of mouse buttons. Table 9-3 contains a list of such methods with their descriptions.
Table 9-3

Methods Related to the State of Mouse Buttons in the MouseEvent Class

Method

Description

MouseButton getButton()

It returns the mouse button responsible for the mouse event.

int getClickCount()

It returns the number of mouse clicks associated with the mouse event.

boolean isPrimaryButtonDown()

It returns true if the primary button is currently pressed. Otherwise, it returns false.

boolean isMiddleButtonDown()

It returns true if the middle button is currently pressed. Otherwise, it returns false.

boolean isSecondaryButtonDown()

It returns true if the secondary button is currently pressed. Otherwise, it returns false.

boolean isPopupTrigger()

It returns true if the mouse event is the pop-up menu trigger event for the platform. Otherwise, it returns false.

boolean isStillSincePress()

It returns true if the mouse cursor stays within a small area, which is known as the system-provided hysteresis area, between the last mouse-pressed event and the current mouse event.

In many circumstances, the getButton() method may return MouseButton.NONE, for example, when a mouse event is triggered on a touch screen by using the fingers instead of a mouse or when a mouse event, such as a mouse-moved event, is not triggered by a mouse button.

It is important to understand the difference between the getButton() method and other methods, for example, isPrimaryButtonDown(), which returns the pressed state of buttons. The getButton() method returns the button that triggers the event. Not all mouse events are triggered by buttons. For example, a mouse-moved event is triggered when the mouse moves, not by pressing or releasing a button. If a button is not responsible for a mouse event, the getButton() method returns MouseButton.NONE. The isPrimaryButtonDown() method returns true if the primary button is currently pressed, whether or not it triggered the event. For example, when you press the primary button, the mouse-pressed event occurs. The getButton() method will return MouseButton.PRIMARY because this is the button that triggered the mouse-pressed event. The isPrimaryButtonDown() method returns true because this button is pressed when the mouse-pressed event occurs. Suppose you keep the primary button pressed and you press the secondary button. Another mouse-pressed event occurs. However, this time, the getButton() returns MouseButton.SECONDARY, and both isPrimaryButtonDown() and isSecondaryButtonDown() methods return true, because both of these buttons are in the pressed state at the time of the second mouse-pressed event.

A pop-up menu, also known as a context, contextual, or shortcut menu, is a menu that gives a user a set of choices that are available in a specific context in an application. For example, when you click the right mouse button in a browser on the Windows platform, a pop-up menu is displayed. Different platforms trigger pop-up menu events differently upon use of a mouse or keyboard. On the Windows platform, typically it is a right mouse click or Shift + F10 key press.

The isPopupTrigger() method returns true if the mouse event is the pop-up menu trigger event for the platform. Otherwise, it returns false. If you perform an action based on the returned value of this method, you need to use it in both mouse-pressed and mouse-released events. Typically, when this method returns true, you let the system display the default pop-up menu.

Tip

JavaFX provides a context menu event that is a specific type of input event. It is represented by the ContextMenuEvent class in the javafx.scene.input package. If you want to handle context menu events, use ContextMenuEvent.

Hysteresis in GUI Applications

Hysteresis is a feature that allows user inputs to be within a range of time or location. The time range within which user inputs are accepted is known as the hysteresis time. The area in which user inputs are accepted is known as the hysteresis area. Hysteresis time and area are system dependent. For example, modern GUI applications provide features that are invoked by double-clicking a mouse button. A time gap exists between two clicks. If the time gap is within the hysteresis time of the system, two clicks are considered a double-click. Otherwise, they are considered two separate single clicks.

Typically, during a mouse-click event, the mouse is moved by a very tiny distance between the mouse-pressed and mouse-released events. Sometimes, it is important to take into account the distance the mouse is moved during a mouse click. The isStillSincePress() method returns true if the mouse stays in the system-provided hysteresis area since the last mouse-pressed event and the current event. This method is important when you want to consider a mouse-drag action. If this method returns true, you may ignore mouse drags as the mouse movement is still within the hysteresis distance from the point where the mouse was last pressed.

State of Modifier Keys

A modifier key is used to change the normal behavior of other keys. Some examples of modifier keys are Alt, Shift, Ctrl, Meta, Caps Lock, and Num Lock. Not all platforms support all modifier keys. The Meta key is present on Mac, not on Windows. Some systems let you simulate the functionality of a modifier key even if the modifier key is physically not present, for example, you can use the Windows key on Windows to work as the Meta key. The MouseEvent method contains methods to report the pressed state of some of the modifier keys when the mouse event occurs. Table 9-4 lists the methods related to the modifier keys in the MouseEvent class.
Table 9-4

Methods, Related to the State of Modifier Keys, in the MouseEvent Class

Method

Description

boolean isAltDown()

It returns true if the Alt key is down for this mouse event. Otherwise, it returns false.

boolean isControlDown()

It returns true if the Ctrl key is down for this mouse event. Otherwise, it returns false.

boolean isMetaDown()

It returns true if the Meta key is down for this mouse event. Otherwise, it returns false.

boolean isShiftDown()

It returns true if the Shift key is down for this mouse event. Otherwise, it returns false.

boolean isShortcutDown()

It returns true if the platform-specific shortcut key is down for this mouse event. Otherwise, it returns false. The shortcut modifier key is the Ctrl key on Windows and Meta key on Mac.

Picking Mouse Events on Bounds

The Node class has a pickOnBounds property to control the way mouse events are picked (or generated) for a node. A node can have any geometric shape, whereas its bounds always define a rectangular area. If the property is set to true, the mouse events are generated for the node if the mouse is on the perimeter or inside of its bounds. If the property is set to false, which is the default value, mouse events are generated for the node if the mouse is on the perimeter or inside of its geometric shape. Some nodes, such as the Text node, have the default value for the pickOnBounds property set to true.

Figure 9-8 shows the perimeter for the geometric shape and bounds of a circle. If the pickOnBounds property for the circle is false, the mouse event will not be generated for the circle if the mouse is one of the four areas in the corners that lie between the perimeter of the geometric shape and bounds.
Figure 9-8

Difference between the geometric shape and bounds of a circle

Listing 9-7 contains the program to show the effects of the pickOnBounds property of a Circle node. It displays a window as shown in Figure 9-9. The program adds a Rectangle and a Circle to a Group. Note that the Rectangle is added to the Group before the Circle to keep the former below the latter in Z-order.
Figure 9-9

Demonstrating the effects of the pickOnBounds property of a Circle node

The Rectangle uses red as the fill color, whereas light gray is used as the fill color for the Circle. The area in red is the area between the perimeters of the geometric shape and bounds of the Circle.

You have a check box that controls the pickOnBounds property of the circle. If it is selected, the property is set to true. Otherwise, it is set to false.

When you click the gray area, the Circle always picks up the mouse-clicked event. When you click the red area with the check box unselected, the Rectangle picks up the event. When you click the red area with the check box selected, the Circle picks up the event. The output shows who picks up the mouse-clicked event.
// PickOnBounds.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class PickOnBounds extends Application {
        private CheckBox pickonBoundsCbx = new CheckBox("Pick on Bounds");
        Circle circle = new Circle(50, 50, 50, Color.LIGHTGRAY);
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Rectangle rect = new Rectangle(100, 100);
                rect.setFill(Color.RED);
                Group group = new Group();
                group.getChildren().addAll(rect, circle);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(group, pickonBoundsCbx);
                // Add MOUSE_CLICKED event handlers to the circle and
                // rectangle
                circle.setOnMouseClicked(e -> handleMouseClicked(e));
                rect.setOnMouseClicked(e -> handleMouseClicked(e));
                // Add an Action handler to the checkbox
                pickonBoundsCbx.setOnAction(e -> handleActionEvent(e));
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Pick on Bounds");
                stage.show();
        }
        public void handleMouseClicked(MouseEvent e) {
                String target = e.getTarget().getClass().getSimpleName();
                String type = e.getEventType().getName();
                System.out.println(type + " on " + target);
        }
        public void handleActionEvent(ActionEvent e) {
                if (pickonBoundsCbx.isSelected()) {
                        circle.setPickOnBounds(true);
                } else {
                        circle.setPickOnBounds(false);
                }
        }
}
Listing 9-7

Testing the Effects of the pickOnBounds Property for a Circle Node

Mouse Transparency

The Node class has a mouseTransparent property to control whether or not a node and its children receive mouse events. Contrast the pickOnBounds and mouseTransparent properties: the former determines the area of a node that generates mouse events, and the latter determines whether or not a node and its children generate mouse events, irrespective of the value of the former. The former affects only the node on which it is set; the latter affects the node on which it is set and all its children.

The code in Listing 9-8 shows the effects of the mouseTransparent property of a Circle. This is a variant of the program in Listing 9-7. It displays a window that is very similar to the one shown in Figure 9-9. When the check box MouseTransparency is selected, it sets the mouseTransparent property of the circle to true. When the check box is unselected, it sets the mouseTransparent property of the circle to false.

Click the circle, in the gray area, when the check box is selected, and all mouse-clicked events will be delivered to the rectangle. This is because the circle is mouse transparent, and it lets the mouse events pass through. Unselect the check box, and all mouse clicks in the gray area are delivered to the circle. Note that clicking the red area always delivers the event to the rectangle, because the pickOnBounds property for the circle is set to false by default. The output shows the node that receives the mouse-clicked events.
// MouseTransparency.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MouseTransparency extends Application {
        private CheckBox mouseTransparentCbx =
              new CheckBox("Mouse Transparent");
        Circle circle = new Circle(50, 50, 50, Color.LIGHTGRAY);
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Rectangle rect = new Rectangle(100, 100);
                rect.setFill(Color.RED);
                Group group = new Group();
                group.getChildren().addAll(rect, circle);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(group, mouseTransparentCbx);
                // Add MOUSE_CLICKED event handlers to the circle
                // and rectangle
                circle.setOnMouseClicked(e -> handleMouseClicked(e));
                rect.setOnMouseClicked(e -> handleMouseClicked(e));
                // Add an Action handler to the checkbox
                mouseTransparentCbx.setOnAction(e ->
                         handleActionEvent(e));
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Mouse Transparency");
                stage.show();
        }
        public void handleMouseClicked(MouseEvent e) {
                String target = e.getTarget().getClass().getSimpleName();
                String type = e.getEventType().getName();
                System.out.println(type + " on " + target);
        }
        public void handleActionEvent(ActionEvent e) {
                if (mouseTransparentCbx.isSelected()) {
                        circle.setMouseTransparent(true);
                } else {
                        circle.setMouseTransparent(false);
                }
        }
}
Listing 9-8

Testing the Effects of the mouseTransparent Property for a Circle Node

Synthesized Mouse Events

A mouse event can be generated using several types of devices, such as a mouse, track pad, or touch screen. Some actions on a touch screen generate mouse events, which are considered synthesized mouse events. The isSynthesized() method of the MouseEvent class returns true if the event is synthesized from using a touch screen. Otherwise, it returns false.

When a finger is dragged on a touch screen, it generates both a scrolling gesture event and a mouse-dragged event. The return value of the isSynthesized() method can be used inside the mouse-dragged event handlers to detect if the event is generated by dragging a finger on a touch screen or by dragging a mouse.

Handling Mouse-Entered and Mouse-Exited Events

Four mouse event types deal with events when the mouse enters or exits a node:
  • MOUSE_ENTERED

  • MOUSE_EXITED

  • MOUSE_ENTERED_TARGET

  • MOUSE_EXITED_TARGET

You have two sets of event types for mouse-entered and mouse-exited events. One set contains two types called MOUSE_ENTERED and MOUSE_EXITED, and another set contains MOUSE_ENTERED_TARGET and MOUSE_EXITED_TARGET. They both have something in common, such as when they are triggered. They differ in their delivery mechanisms. I will discuss all of them in this section.

When the mouse enters a node, a MOUSE_ENTERED event is generated. When the mouse leaves a node, a MOUSE_EXITED event is generated. These events do not go through the capture and bubbling phases. That is, they are delivered directly to the target node, not to any of its parent nodes.

Tip

The MOUSE_ENTERED and MOUSE_EXITED events do not participate in the capture and bubbling phases. However, all event filters and handlers are executed for the target following the rules for event handling.

The program in Listing 9-9 shows how mouse-entered and mouse-exited events are delivered. The program displays a window as shown in Figure 9-10. It shows a circle with gray fill inside an HBox. Event handlers for mouse-entered and mouse-exited events are added to the HBox and the Circle. Run the program and move the mouse in and out of the circle. When the mouse enters the white area in the window, its MOUSE_ENTERED event is delivered to the HBox. When you move the mouse in and out of the circle, the output shows that the MOUSE_ENTERED and MOUSE_EXITED events are delivered only to the Circle, not to the HBox. Notice that in the output the source and target of these events are always the same, proving that the capture and bubbling phases do not occur for these events. When you move the mouse in and out of the circle, keeping it in the white area, the MOUSE_EXITED event for the HBox does not fire, as the mouse stays on the HBox. To fire the MOUSE_EXITED event on the HBox, you will need to move the mouse outside the scene area, for example, outside the window or over the title bar of the window.
// MouseEnteredExited.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import static javafx.scene.input.MouseEvent.MOUSE_ENTERED;
import static javafx.scene.input.MouseEvent.MOUSE_EXITED;
public class MouseEnteredExited  extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle (50, 50, 50);
                circle.setFill(Color.GRAY);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(circle);
                // Create a mouse event handler
                EventHandler<MouseEvent> handler = e -> handle(e);
                // Add mouse-entered and mouse-exited event handlers to
                // the HBox
                root.addEventHandler(MOUSE_ENTERED, handler);
                root.addEventHandler(MOUSE_EXITED, handler);
                // Add mouse-entered and mouse-exited event handlers to
                // the Circle
                circle.addEventHandler(MOUSE_ENTERED, handler);
                circle.addEventHandler(MOUSE_EXITED, handler);
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Mouse Entered and Exited Events");
                stage.show();
        }
        public void handle(MouseEvent e) {
                String type = e.getEventType().getName();
                String source = e.getSource().getClass().getSimpleName();
                String target = e.getTarget().getClass().getSimpleName();
                System.out.println("Type=" + type +
                         ", Target=" + target + ", Source=" +  source);
        }
}
Type=MOUSE_ENTERED, Target=HBox, Source=HBox
Type=MOUSE_ENTERED, Target=Circle, Source=Circle
Type=MOUSE_EXITED, Target=Circle, Source=Circle
Type=MOUSE_ENTERED, Target=Circle, Source=Circle
Type=MOUSE_EXITED, Target=Circle, Source=Circle
Type=MOUSE_EXITED, Target=HBox, Source=HBox
...
Listing 9-9

Testing Mouse-Entered and Mouse-Exited Events

Figure 9-10

Demonstrating mouse-entered and mouse-exited events

The MOUSE_ENTERED and MOUSE_EXITED event types provide the functionality needed in most cases. Sometimes, you need these events to go through the normal capture and bubbling phases, so parent nodes can apply filters and provide default responses. The MOUSE_ENTERED_TARGET and MOUSE_EXITED_TARGET event types provide these features. They participate in the event capture and bubbling phases.

The MOUSE_ENTERED and MOUSE_EXITED event types are subtypes of the MOUSE_ENTERED_TARGET and MOUSE_EXITED_TARGET event types. A node interested in the mouse-entered event of its children should add event filters and handlers for the MOUSE_ENTERED_TARGET type. The child node can add MOUSE_ENTERED, MOUSE_ENTERED_TARGET, or both event filters and handlers. When the mouse enters the child node, parent nodes receive the MOUSE_ENTERED_TARGET event. Before the event is delivered to the child node, which is the target node of the event, the event type is changed to the MOUSE_ENTERED type. Therefore, in the same event processing, the target node receives the MOUSE_ENTERED event, whereas all its parent nodes receive the MOUSE_ENTERED_TARGET event. Because the MOUSE_ENTERED event type is a subtype of the MOUSE_ENTERED_TARGET type, either type of event handler on the target can handle this event. The same would apply to the mouse-exited event and its corresponding event types.

Sometimes, inside the parent event handler, it is necessary to distinguish the node that fires the MOUSE_ENTERED_TARGET event. A parent node receives this event when the mouse enters the parent node itself or any of its child nodes. You can check the target node reference, using the getTarget() method of the Event class, for equality with the reference of the parent node, inside the event filters and handlers, to know whether or not the event was fired by the parent.

The program in Listing 9-10 shows how to use the mouse-entered-target and mouse-exited-target events. It adds a Circle and a CheckBox to an HBox. The HBox is added to the Scene. It adds the mouse-entered-target and mouse-exited-target event filters to the HBox and event handlers to the Circle. It also adds mouse-entered and mouse-exited event handlers to the Circle. When the check box is selected, events are consumed by the HBox, so they do not reach the Circle. The following are a few observations when you run the program:
  • With the check box unselected, when the mouse enters or leaves the Circle, the HBox receives the MOUSE_ENTERED_TARGET and MOUSE_EXITED_TARGET events. The Circle receives the MOUSE_ENTERED and MOUSE_EXITED events.

  • With the check box selected, the HBox receives the MOUSE_ENTERED_TARGET and MOUSE_EXITED_TARGET events and consumes them. The Circle does not receive any events.

  • When the mouse enters or leaves the HBox, the white area in the window, the HBox receives the MOUSE_ENTERED and MOUSE_EXITED events, because the HBox is the target of the event.

Play with the application by moving the mouse around, selecting and unselecting the check box. Look at the output to get a feel for how these events are processed.
// MouseEnteredExitedTarget.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.input.MouseEvent;
import static javafx.scene.input.MouseEvent.MOUSE_ENTERED;
import static javafx.scene.input.MouseEvent.MOUSE_EXITED;
import static javafx.scene.input.MouseEvent.MOUSE_ENTERED_TARGET;
import static javafx.scene.input.MouseEvent.MOUSE_EXITED_TARGET;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class MouseEnteredExitedTarget extends Application {
        private CheckBox consumeCbx = new CheckBox("Consume Events");
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Circle circle = new Circle(50, 50, 50);
                circle.setFill(Color.GRAY);
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(circle, consumeCbx);
                // Create mouse event handlers
                EventHandler<MouseEvent> circleHandler = e ->
                         handleCircle(e);
                EventHandler<MouseEvent> circleTargetHandler = e ->
                         handleCircleTarget(e);
                EventHandler<MouseEvent> hBoxTargetHandler = e ->
                         handleHBoxTarget(e);
                // Add mouse-entered-target and mouse-exited-target event
                // handlers to HBox
                root.addEventFilter(MOUSE_ENTERED_TARGET,
                         hBoxTargetHandler);
                root.addEventFilter(MOUSE_EXITED_TARGET,
                         hBoxTargetHandler);
                // Add mouse-entered-target and mouse-exited-target event
                // handlers to the Circle
                circle.addEventHandler(MOUSE_ENTERED_TARGET,
                         circleTargetHandler);
                circle.addEventHandler(MOUSE_EXITED_TARGET,
                         circleTargetHandler);
                // Add mouse-entered and mouse-exited event handlers to
                // the Circle
                circle.addEventHandler(MOUSE_ENTERED, circleHandler);
                circle.addEventHandler(MOUSE_EXITED, circleHandler);
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle(
                         "Mouse Entered Target and Exited Target Events");
                stage.show();
        }
        public void handleCircle(MouseEvent e) {
                print(e, "Circle Handler");
        }
        public void handleCircleTarget(MouseEvent e) {
                print(e, "Circle Target Handler");
        }
        public void handleHBoxTarget(MouseEvent e) {
                print(e, "HBox Target Filter");
                if (consumeCbx.isSelected()) {
                    e.consume();
                    System.out.println(
                               "HBox consumed the " + e.getEventType() + " event");
                }
        }
        public void print(MouseEvent e, String msg) {
                String type = e.getEventType().getName();
                String source = e.getSource().getClass().getSimpleName();
                String target = e.getTarget().getClass().getSimpleName();
                System.out.println(msg + ": Type=" + type +
                                         ", Target=" + target +
                                         ", Source=" + source);
        }
}
Listing 9-10

Using the Mouse-Entered-Target and Mouse-Exited-Target Events

Handling Key Events

A key event is a type of input event that denotes the occurrence of a keystroke. It is delivered to the node that has focus. An instance of the KeyEvent class, which is declared in the javafx.scene.input package, represents a key event. Key pressed, key released, and key typed are three types of key events. Table 9-5 lists all of the constants in the KeyEvent class, which represent key event types.
Table 9-5

Constants in the KeyEvent Class to Represent Key Event Types

Constant

Description

ANY

It is the supertype of other key event types.

KEY_PRESSED

It occurs when a key is pressed.

KEY_RELEASED

It occurs when a key is released.

KEY_TYPED

It occurs when a Unicode character is entered.

Tip

It may not be obvious that shapes, for example, circles or rectangles, can also receive key events. The criterion for a node to receive key events is that the node should have focus. By default, shapes are not part of the focus traversal chain, and mouse clicks do not bring focus to them. Shape nodes can get focus by calling the requestFocus() method.

The key-pressed and key-released events are lower-level events compared to the key-typed event; they occur with a key press and release, respectively, and depend on the platform and keyboard layout.

The key-typed event is a higher-level event. Generally, it does not depend on the platform and keyboard layout. It occurs when a Unicode character is typed. Typically, a key press generates a key-typed event. However, a key release may also generate a key-typed event. For example, when using the Alt key and number pad on Windows, a key-typed event is generated by the release of the Alt key, irrespective of the number of keystrokes entered on the number pad. A key-typed event can also be generated by a series of key presses and releases. For example, the character A is entered by pressing Shift + A, which includes two key presses (Shift and A). In this case, two key presses generate one key-typed event. Not all key presses or releases generate key-typed events. For example, when you press a function key (F1, F2, etc.) or modifier keys (Shift, Ctrl, etc.), no Unicode character is entered, and, hence, no key-typed event is generated.

The KeyEvent class maintains three variables to describe the keys associated with the event: code, text, and character. These variables can be accessed using the getter methods in the KeyEvent class as listed in Table 9-6.
Table 9-6

Methods in the KeyEvent Class Returning Key Details

Method

Valid for

Description

KeyCode getCode()

KEY_PRESSED

KEY_RELEASED

The KeyCode enum contains a constant to represent all keys on the keyboard. This method returns the KeyCode enum constant that is associated with the key being pressed or released. For the key-typed events, it always returns KeyCode.UNDEFINED, because the key-typed event may not necessarily be triggered by a single keystroke.

String getText()

KEY_PRESSED

KEY_RELEASED

It returns a String description of the KeyCode associated with the key-pressed and key-released events. It always returns an empty string for the key-typed events.

String getCharacter()

KEY_TYPED

It returns a character or a sequence of characters associated with a key-typed event as a String. For the key-pressed and key-released events, it always returns KeyEvent.CHAR_UNDEFINED.

It is interesting to note that the return type of the getCharacter() method is String, not char. The design is intentional. Unicode characters outside the basic multilingual plane cannot be represented in one character. Some devices may produce multiple characters using a single keystroke. The return type of String for the getCharacter() method covers these odd cases.

The KeyEvent class contains isAltDown(), isControlDown(), isMetaDown(), isShiftDown(), and isShortcutDown() methods that let you check whether modifier keys are down when a key event occurs.

Handling Key-Pressed and Key-Released Events

Key-pressed and key-released events are handled simply by adding the event filters and handlers to nodes for the KEY_PRESSED and KEY_RELEASED event types. Typically, you use these events to know which keys were pressed or released and to perform an action. For example, you can detect the F1 function key press and display a custom Help window for the node in focus.

The program in Listing 9-11 shows how to handle key-pressed and key-released events. It displays a Label and a TextField. When you run the program, the TextField has focus. Notice the following points when you use keystrokes while running this program:
  • Press and release some keys. Output will show the details of events as they occur. A key-released event does not occur for every key-pressed event.

  • The mapping between key-pressed and key-released events is not one to one. There may be no key-released event for a key-pressed event (refer to the next item). There may be one key-released event for several key-pressed events. This can happen when you keep a key pressed for a longer period. Sometimes, you do it to type the same character multiple times. Press the A key and hold it for some time and then release it. This will generate several key-pressed events and only one key-released event.

  • Press the F1 key. It will display the Help window. Notice that pressing the F1 key does not generate an output for a key-released event, even after you release the key. Can you think of the reason for this? On the key-pressed event, the Help window is displayed, which grabs the focus. The TextField on the main window no longer has focus. Recall that the key events are delivered to the node that has focus, and only one node can have focus in a JavaFX application. Therefore, the key-released event is delivered to the Help window, not the TextField.

// KeyPressedReleased.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class KeyPressedReleased extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Label nameLbl = new Label("Name:");
                TextField nameTfl = new TextField();
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(nameLbl, nameTfl);
                // Add key pressed and released events to the TextField
                nameTfl.setOnKeyPressed(e -> handle(e));
                nameTfl.setOnKeyReleased(e -> handle(e));
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Key Pressed and Released Events");
                stage.show();
        }
        public void handle(KeyEvent e) {
                String type = e.getEventType().getName();
                KeyCode keyCode = e.getCode();
                System.out.println(type + ": Key Code=" +
                   keyCode.getName() +
                   ", Text=" + e.getText());
                // Show the help window when the F1 key is pressed
                if (e.getEventType() == KEY_PRESSED &&
                               e.getCode() == KeyCode.F1) {
                    displayHelp();
                    e.consume();
                }
        }
        public void displayHelp() {
                Text helpText = new Text("Please enter a name.");
                HBox root = new HBox();
                root.setStyle("-fx-background-color: yellow;");
                root.getChildren().add(helpText);
                Scene scene = new Scene(root, 200, 100);
                Stage helpStage = new Stage();
                helpStage.setScene(scene);
                helpStage.setTitle("Help");
                helpStage.show();
        }
}
Listing 9-11

Handling Key-Pressed and Key-Released Events

Handling the Key-Typed Event

The key-typed event is used to detect specific keystrokes. You cannot use it to prevent the user from entering certain characters—for this aim, you can use a formatter. We do not explain using formatters at this place, but the setTextFormatter() method description in the API documentation of, for example, the TextField control gives you a starting point if you need to use this kind of functionality.

The program in Listing 9-12 shows a Label and a TextField. It adds a key-typed event handler to the TextField, which prints some information on pressed keys.
// KeyTyped.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class KeyTyped extends Application {
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Label nameLbl = new Label("Name:");
                TextField nameTfl = new TextField();
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(nameLbl, nameTfl);
                // Add key-typed event to the TextField
                nameTfl.setOnKeyTyped(e -> handle(e));
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Key Typed Event");
                stage.show();
        }
        public void handle(KeyEvent e) {
                String type = e.getEventType().getName();
                System.out.println(type + ": Character=" +
                                e.getCharacter());
        }
}
Listing 9-12

Using the Key-Typed Event

Handling Window Events

A window event occurs when a window is shown, hidden, or closed. An instance of the WindowEvent class in the javafx.stage package represents a window event. Table 9-7 lists the constants in the WindowEvent class.
Table 9-7

Constants in the WindowEvent Class to Represent Window Event Types

Constant

Description

ANY

It is the supertype of all other window event types.

WINDOW_SHOWING

It occurs just before the window is shown.

WINDOW_SHOWN

It occurs just after the window is shown.

WINDOW_HIDING

It occurs just before the window is hidden.

WINDOW_HIDDEN

It occurs just after the window is hidden.

WINDOW_CLOSE_REQUEST

It occurs when there is an external request to close this window.

The window-showing and window-shown events are straightforward. They occur just before and after the window is shown. Event handlers for the window-showing event should have time-consuming logic, as it will delay showing the window to the user and, hence, degrading the user experience. Initializing some window-level variables is a good example of the kind of code you need to write in this event. Typically, the window-shown event sets the starting direction for the user, for example, setting focus to the first editable field on the window and showing alerts to the user about the tasks that need their attention, among others.

The window-hiding and window-hidden events are counterparts of the window-showing and window-shown events. They occur just before and after the window is hidden.

The window-close-request event occurs when there is an external request to close the window. Using the Close menu from the context menu or the Close icon in the window title bar or pressing the Alt + F4 key combination on Windows is considered an external request to close the window. Note that closing a window programmatically, for example, using the close() method of the Stage class or Platform.exit() method, is not considered an external request. If the window-close-request event is consumed, the window is not closed.

The program in Listing 9-13 shows how to use all window events. You may get a different output than that shown below the code. It adds a check box and two buttons to the primary stage. If the check box is unselected, external requests to close the window are consumed, thus preventing the window from closing. The Close button closes the window. The Hide button hides the primary window and opens a new window, so the user can show the primary window again.

The program adds event handlers to the primary stage for window event types. When the show() method on the stage is called, the window-showing and window-shown events are generated. When you click the Hide button, the window-hiding and window-hidden events are generated. When you click the button on the pop-up window to show the primary window, the window-showing and window-shown events are generated again. Try clicking the Close icon on the title bar to generate the window-close-request event. If the Can Close Window check box is not selected, the window is not closed. When you use the Close button to close the window, the window-hiding and window-hidden events are generated, but not the window-close-request event, as it is not an external request to close the window.
// WindowEventApp.java
package com.jdojo.event;
import javafx.application.Application;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
public class WindowEventApp  extends Application {
        private CheckBox canCloseCbx = new CheckBox("Can Close Window");
        public static void main(String[] args) {
                Application.launch(args);
        }
        @Override
        public void start(Stage stage) {
                Button closeBtn = new Button("Close");
                closeBtn.setOnAction(e -> stage.close());
                Button hideBtn = new Button("Hide");
                hideBtn.setOnAction(e -> {
                         showDialog(stage); stage.hide(); });
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(
                         canCloseCbx, closeBtn, hideBtn);
                // Add window event handlers to the stage
                stage.setOnShowing(e -> handle(e));
                stage.setOnShown(e -> handle(e));
                stage.setOnHiding(e -> handle(e));
                stage.setOnHidden(e -> handle(e));
                stage.setOnCloseRequest(e -> handle(e));
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Window Events");
                stage.show();
        }
        public void handle(WindowEvent e) {
                // Consume the event if the CheckBox is not selected
                // thus preventing the user from closing the window
                EventType<WindowEvent> type = e.getEventType();
                if (type == WINDOW_CLOSE_REQUEST &&
                             !canCloseCbx.isSelected()) {
                         e.consume();
                }
                System.out.println(type + ": Consumed=" +
                         e.isConsumed());
        }
        public void showDialog(Stage mainWindow) {
                Stage popup = new Stage();
                Button closeBtn =
                         new Button("Click to Show Main Window");
                closeBtn.setOnAction(e -> {
                         popup.close(); mainWindow.show();});
                HBox root = new HBox();
                root.setPadding(new Insets(20));
                root.setSpacing(20);
                root.getChildren().addAll(closeBtn);
                Scene scene = new Scene(root);
                popup.setScene(scene);
                popup.setTitle("Popup");
                popup.show();
        }
}
WINDOW_SHOWING: Consumed=false
WINDOW_SHOWN: Consumed=false
WINDOW_HIDING: Consumed=false
WINDOW_HIDDEN: Consumed=false
WINDOW_SHOWING: Consumed=false
WINDOW_SHOWN: Consumed=false
WINDOW_CLOSE_REQUEST: Consumed=true
Listing 9-13

Using Window Events

Summary

In general, the term event is used to describe an occurrence of interest. In a GUI application, an event is an occurrence of a user interaction with the application such as clicking the mouse, pressing a key on the keyboard, and so forth. An event in JavaFX is represented by an object of the javafx.event.Event class or any of its subclasses. Every event in JavaFX has three properties: an event source, an event target, and an event type.

When an event occurs in an application, you typically perform some processing by executing a piece of code. The piece of code that is executed in response to an event is known as an event handler or an event filter. When you want to handle an event for a UI element, you need to add event handlers to the UI element, for example, a Window, a Scene, or a Node. When the UI element detects the event, it executes your event handlers.

The UI element that calls event handlers is the source of the event for those event handlers. When an event occurs, it passes through a chain of event dispatchers. The source of an event is the current element in the event dispatcher chain. The event source changes as the event passes through one dispatcher to another in the event dispatcher chain. The event target is the destination of an event, which determines the route the event travels through during its processing. The event type describes the type of the event that occurs. They are defined in a hierarchical fashion. Each event type has a name and a supertype.

When an event occurs, the following three steps are performed in order: event target selection, event route construction, and event route traversal. An event target is the destination node of the event that is selected based on the event type. An event travels through event dispatchers in an event dispatch chain. The event dispatch chain is the event route. The initial and default route for an event is determined by the event target. The default event route consists of the container-children path starting at the stage to the event target node.

An event route traversal consists of two phases: capture and bubbling. An event travels through each node in its route twice: once during the capture phase and once during the bubbling phase. You can register event filters and event handlers to a node for specific event types. The event filters and event handlers registered to a node are executed as the event passes through the node during the capture and the bubbling phases, respectively.

During the route traversal, a node can consume the event in event filters or handlers, thus completing the processing of the event. Consuming an event is simply calling the consume() method on the event object. When an event is consumed, the event processing is stopped, even though some of the nodes in the route were not traversed at all.

The interaction of the user with the UI elements using the mouse, such as clicking, moving, or pressing the mouse, triggers a mouse event. An object of the MouseEvent class represents a mouse event.

A key event denotes the occurrence of a keystroke. It is delivered to the node that has focus. An instance of the KeyEvent class represents a key event. Key pressed, key released, and key typed are three types of key events.

A window event occurs when a window is shown, hidden, or closed. An instance of the WindowEvent class in the javafx.stage package represents a window event.

The next chapter discusses layout panes that are used as containers for other controls and nodes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.202.103