Проблема в том, что поведение TextArea
(который является внутренним классом) добавляет число EventHandler
с, которые обрабатывают различные сгенерированные пользователем события (например, нажатия клавиш). Это включает в себя общие ярлыки, используемые для вырезания, копирования, вставки, отмены, повтора, выбора всех и так далее. Эти обработчики затем потребляют событие, которое останавливает распространение указанного события. Поскольку ускорители пунктов меню работают только тогда, когда событие возвращается к значению Scene
, событие, потребляемое поведением TextArea
, означает, что ваши пункты меню не срабатывают.
Один из способов - использовать пользовательский EventDispatcher
на TextArea
, чтобы отфильтровать любые ключевые события, которые соответствуют любой из ряда комбинаций клавиш. Все остальные события могут проходить в обычном режиме, оставляя остальное поведение без изменений. Это работает, препятствуя достижению события TextArea
и позволяя событию войти в пузырчатую фазу распространения события, в конечном счете позволяя событию всплыть обратно в граф сцены. Этот последний бит объясняет необходимость использования EventDispatcher
вместо фильтра событий; использование события в фильтре событий остановит достижение события TextArea
, но не позволит ему всплыть обратно на график сцены.
Вот пример:
import java.util.Set;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
MenuItem cutItem = new MenuItem("Cut");
cutItem.setAccelerator(KeyCombination.valueOf("shortcut+x"));
cutItem.setOnAction(e -> System.out.println("CUT"));
MenuItem copyItem = new MenuItem("Copy");
copyItem.setAccelerator(KeyCombination.valueOf("shortcut+c"));
copyItem.setOnAction(e -> System.out.println("COPY"));
MenuItem pasteItem = new MenuItem("Paste");
pasteItem.setAccelerator(KeyCombination.valueOf("shortcut+v"));
pasteItem.setOnAction(e -> System.out.println("PASTE"));
TextArea area = new TextArea();
area.setEventDispatcher(
new FilteringEventDispatcher(
area.getEventDispatcher(),
cutItem.getAccelerator(),
copyItem.getAccelerator(),
pasteItem.getAccelerator()));
VBox root = new VBox(new MenuBar(new Menu("Edit", null, cutItem, copyItem, pasteItem)), area);
VBox.setVgrow(area, Priority.ALWAYS);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
private static class FilteringEventDispatcher implements EventDispatcher {
private final EventDispatcher delegate;
private final Set<KeyCombination> blacklistedCombos;
public FilteringEventDispatcher(EventDispatcher delegate, KeyCombination... blacklistedCombos) {
this.delegate = delegate;
// Set.of was added in Java 9
this.blacklistedCombos = Set.of(blacklistedCombos);
}
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (!(event instanceof KeyEvent) || isPermitted((KeyEvent) event)) {
return delegate.dispatchEvent(event, tail); // forward event to TextArea
}
return event; // skip TextArea and enter the bubbling phase
}
private boolean isPermitted(KeyEvent event) {
return blacklistedCombos.stream().noneMatch(combo -> combo.match(event));
}
}
}
Если При необходимости вы можете отфильтровать более конкретно, протестировав EventType
события (например, только фильтр KEY_PRESSED
событий). Вы также можете вносить белые списки вместо черных в зависимости от ваших потребностей.