Как переписать системные сочетания клавиш по умолчанию, такие как Ctrl + C, Ctrl + V, с помощью Javafx KeyCombination? - PullRequest
0 голосов
/ 07 апреля 2020

При реализации механизма отмены / возврата я написал ниже код для добавления операции вставки в мой собственный стек отмены. Если я нажимаю на пункт меню, он выполняет код обработки событий, но если я использую сочетание клавиш Ctrl + V, мой обработчик событий не выполняется.

Для получения дополнительной информации см. Код ниже.

enter image description here

        MenuItem paste = new MenuItem("Paste");

        KeyCombination pasteKeyCombination = new KeyCodeCombination(KeyCode.V,KeyCombination.CONTROL_DOWN);

        paste.setAccelerator(pasteKeyCombination);

        paste.setOnAction(event -> {
            System.out.println("Ctrl+V triggered.");
            if(clipboard.hasString()){
                String pastedText = clipboard.getString();
                InsertCommand insertCommand = new InsertCommand(textArea.getCaretPosition(),pastedText,textArea);
                insertCommand.execute();
                UndoRedo.insertIntoUndoStack(insertCommand);
            }
        });

Если я использую другой KeyCode, например, Ctrl + J, он работает нормально, но не для Ctrl + V.

Примечание: Когда я используйте Ctrl + V, кажется, непосредственно вставляя данные из системного буфера обмена вместо выполнения моего кода.

Может кто-нибудь предложить решение этой проблемы? и, пожалуйста, объясните, почему он работает с Ctrl + J и почему нет Ctrl + V?

Проблема существует для всех сочетаний клавиш вырезать, скопировать, вставить.

Ответы [ 2 ]

1 голос
/ 07 апреля 2020

Поскольку я не мог найти правильное решение, я попытался обойти его, добавив обработчик события onKeyPress, и он выполняет реализацию обработчика даже для Ctrl + C, Ctrl + V и c.

        MenuItem paste = new MenuItem("Paste");
        this.textArea.addEventHandler(KeyEvent.KEY_PRESSED,event -> {
            if(event.isControlDown()&& event.getCode()==KeyCode.V){
                System.out.println("Ctrl+V is triggered");
                if(clipboard.hasString()){
                    String pastedText = clipboard.getString();
                    InsertCommand insertCommand = new InsertCommand(textArea.getCaretPosition(),pastedText,textArea);
                    insertCommand.execute();
                    UndoRedo.insertIntoUndoStack(insertCommand);
                }
            }
        });

Примечание: до сих пор не ясно, почему он не работает с KeyCombination. если у кого-то есть ответ, пожалуйста, напишите его.

0 голосов
/ 07 апреля 2020

Проблема в том, что поведение 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 событий). Вы также можете вносить белые списки вместо черных в зависимости от ваших потребностей.

...