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
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 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.
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
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
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
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.
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
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
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
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
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.
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
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.
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.
Using the addEventFilter(), addEventHandler(), removeEventFilter(), and removeEventHandler() methods
Using the onXXX convenience properties
Using addXXX( ) and removeXXX( ) Methods
<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.
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.
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.
Registering Event Filters and Handlers
<T extends Event> void removeEventFilter(EventType<T> eventType, EventHandler<? super T> eventFilter)
<T extends Event> void removeEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler)
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.
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.
Using the Convenience Event Handler Properties
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
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.
Execution Order for Event Filters and Handlers
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
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.
Consuming Events
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.
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
Handling Mouse Events
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.
Determining the Mouse Location During Mouse Events
Representing Mouse Buttons
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
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.
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
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.
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.
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.
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
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.
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.
Testing 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.
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.
Using the Mouse-Entered-Target and Mouse-Exited-Target Events
Handling Key Events
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. |
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.
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.
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.
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.
Using the Key-Typed Event
Handling Window Events
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.
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.