Issue
I have a JavaFX HTMLEditor with the following objects:
HTMLEditor htmlEditor;
WebView webView;
WebEngine webEngine;
HTMLDocumentImpl htmlDocImpl;
What I want is to over-ride the default undo function when user hits Ctrl+Z. I'm a programming newb, but my understanding is there's no undo/redo functionality with HTMLEditor I can work with (even though Ctrl+Z somehow undoes the text I type)
I have lots of functions that manipulate the HTML in the editor, so I need a custom undo function. The first step in implementing my own would be overriding what happens when you press Ctrl+Z
I can't seem to figure how to do this. For example, this code, which attempts to turn off all typing functionality has no effect:
htmlEditor.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
e.consume();
});
htmlEditor.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
e.consume();
});
htmlEditor.addEventHandler(KeyEvent.KEY_TYPED, e -> {
e.consume();
});
How could I capture a specific key press, cancel its default functionality, then call a function of my own instead?
Thanks for your help
Solution
You need to use a filter, not a handler.
Here is an example application which demonstrates this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.web.HTMLEditor;
import javafx.stage.Stage;
public class EventCapture extends Application {
@Override
public void start(final Stage stage) {
HTMLEditor editor = new HTMLEditor();
editor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.isMetaDown() && KeyCode.Z.equals(event.getCode())) {
event.consume();
System.out.println("consumed event = " + event);
}
});
stage.setScene(new Scene(editor));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The above code was tested using HTMLEditor on a Mac with Java and JavaFX 16. This intercepts the default undo key combination on a Mac (Command + Z) and ignores it, preventing it from being routed to the underlying HTMLEditor implementation (which is based on WebView). That, in effect, disables the key-based accelerator for undo functionality for the editor. For a non-Mac PC (e.g. a Windows PC), perhaps you would need to intercept a different key combination.
I found (by experimentation) that the event which triggers the undo functionality on a Mac is a KEY_PRESSED event (and not a KEY_TYPED or KEY_RELEASED event), so that is the one I chose to consume.
To understand this better, please read Oracle's (excellent) tutorial on event handling, which I requested they write and they kindly agreed to do so. Especially, review the section on event filters and the event capturing phase. The linked documentation will explain the difference between the event capturing phase (when filters are applied) and the event bubbling phase (when handlers are applied), which is crucial to understanding the solution to this issue.
The section on event filters describes them like this:
Event filters are typically used on a branch node of the event dispatch chain and are called during the event capturing phase of event handling. Use a filter to perform actions such as overriding an event response or blocking an event from reaching its destination.
Which is exactly what you want.
Slightly off-topic: Event debugging hints
To show all events sent to a node during the capturing phase write:
editor.addEventFilter(EventType.ROOT, System.out::println);
To show all events sent to a node during the bubbling phase write:
editor.addEventHandler(EventType.ROOT, System.out::println);
Answers to additional questions
how come if you remove the event.isMetaDown() condition so its just 'z', then run the program and type 'z', the 'z' isn't consumed and still appears in the editor?
Because the 'z' appears in the editor when a KEY_TYPED
event occurs, and the filter is written to only filter KEY_PRESSED
events.
If you want to filter 'z' typing then change the event type being filtered.
If you only wanted lower-case typed 'z' filtered (not upper-case), then only consume KEY_TYPED
events for the matching character.
editor.addEventFilter(KeyEvent.KEY_TYPED, event -> {
if ("z".equals(event.getCharacter())) {
event.consume();
}
});
Answered By - jewelsea
Answer Checked By - Candace Johnson (JavaFixing Volunteer)