如何使用JavaFX的KeyCombination覆盖系统默认键盘快捷键,如Ctrl+C、Ctrl+V?

huangapple go评论74阅读模式
英文:

how to overwrite system default keyboard shortcuts like Ctrl+C, Ctrl+V by using Javafx KeyCombination?

问题

在实现撤销/重做机制时我编写了以下代码将粘贴操作添加到我的撤销栈中如果我点击菜单项它会执行事件处理代码但如果我使用快捷键Ctrl+V我的事件处理程序不会执行

有关更多信息请参见以下代码

```java
        MenuItem paste = new MenuItem("粘贴");

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

        paste.setAccelerator(pasteKeyCombination);

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

如果我使用不同的键码,如Ctrl+J,它可以正常工作,但对于Ctrl+V则不行。

注意: 当我使用Ctrl+V时,似乎直接从系统剪贴板粘贴数据,而不是执行我的代码。

请问是否有人能够提供解决此问题的方法?并解释一下为什么Ctrl+J可以工作,而Ctrl+V不行?

此问题适用于所有剪切、复制、粘贴快捷键。


<details>
<summary>英文:</summary>

While implementing undo/redo mechanism, I have written below code to add paste operation to my own undo stack. If I click on menu item it&#39;s executing event handling code, but if I use shortcut Ctrl+V my event handler is not executing.

For more information see the below code.

[![enter image description here][1]][1]

``` java
        MenuItem paste = new MenuItem(&quot;Paste&quot;);

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

        paste.setAccelerator(pasteKeyCombination);

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

If I use different KeyCode like Ctrl+J it's working fine but not for Ctrl+V.

Note: When I use Ctrl+V it seems directly pasting data from system clipboard instead of executing my code.

Can anyone please suggest the solution for this problem? and please explain why it's working with Ctrl+J and why not Ctrl+V?

The issue exists for all cut, copy, paste shortcuts.

答案1

得分: 2

问题在于TextArea的行为(它是一个内部类)会添加许多处理各种用户生成事件(例如按键)的EventHandler。这包括用于剪切、复制、粘贴、撤消、重做、全选等常见快捷方式。然后,这些处理器会消耗事件,从而停止事件的传播。由于菜单项加速器仅在事件冒泡回到Scene时起作用,被TextArea行为消耗的事件意味着菜单项不会触发。

一个解决方法是在TextArea上使用自定义的EventDispatcher,以过滤掉与多个键组合匹配的任何键事件。所有其他事件都可以正常进行,保留其余行为不变。这通过阻止事件到达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) {
    // ...(省略了菜单项的创建)

    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 {
    // ...(省略了内部类的实现)
  }
}

如果需要的话,您可以通过测试事件的EventType来进行更具体的过滤(例如只过滤KEY_PRESSED事件)。根据您的需求,您还可以将组合键加入白名单而不是黑名单。

英文:

The problem is the behavior of TextArea (which is an internal class) adds a number of EventHandlers which handle various user-generated events (e.g. key presses). This includes the common shortcuts used for cut, copy, paste, undo, redo, select all, and so on. These handlers then consume the event which stops said event's propagation. Since menu item accelerators only work when the event bubbles back up to the Scene, the event being consumed by the TextArea behavior means your menu items don't fire.

One workaround is to use a custom EventDispatcher on the TextArea to filter out any key events which match any of a number of key combinations. All other events are allowed to proceed normally, leaving the rest of the behavior intact. This works by preventing the event from reaching the TextArea and letting the event enter the bubbling phase of event propagation, ultimately letting the event bubble back up the scene graph. That last bit is why using an EventDispatcher is necessary instead of an event filter; consuming an event in an event filter will stop the event from reaching the TextArea but won't let it bubble back up the scene graph.

Here's an example:

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(&quot;Cut&quot;);
    cutItem.setAccelerator(KeyCombination.valueOf(&quot;shortcut+x&quot;));
    cutItem.setOnAction(e -&gt; System.out.println(&quot;CUT&quot;));

    MenuItem copyItem = new MenuItem(&quot;Copy&quot;);
    copyItem.setAccelerator(KeyCombination.valueOf(&quot;shortcut+c&quot;));
    copyItem.setOnAction(e -&gt; System.out.println(&quot;COPY&quot;));

    MenuItem pasteItem = new MenuItem(&quot;Paste&quot;);
    pasteItem.setAccelerator(KeyCombination.valueOf(&quot;shortcut+v&quot;));
    pasteItem.setOnAction(e -&gt; System.out.println(&quot;PASTE&quot;));

    TextArea area = new TextArea();
    area.setEventDispatcher(
        new FilteringEventDispatcher(
            area.getEventDispatcher(),
            cutItem.getAccelerator(),
            copyItem.getAccelerator(),
            pasteItem.getAccelerator()));

    VBox root = new VBox(new MenuBar(new Menu(&quot;Edit&quot;, 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&lt;KeyCombination&gt; 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 -&gt; combo.match(event));
    }
  }
}

If necessary, you can filter more specifically by testing the EventType of the event (e.g. only filter KEY_PRESSED events). You could also whitelist combos instead of blacklisting them, depending on your needs.

答案2

得分: 1

因为找不到适当的解决方案,我现在尝试了一些变通方法,通过添加 onKeyPress 事件处理程序来解决,即使是对于 Ctrl+C、Ctrl+V 等,它也会执行处理程序的实现。

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

注意: 仍然不清楚为什么不适用于 KeyCombination。如果有人知道答案,请发布出来。

英文:

As I couldn't find proper solution, I have tried some work around for now by adding onKeyPress event handler and it's executing the handler implementation even for Ctrl+C,Ctrl+V etc.

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

Note: still not clear about why it's not working with KeyCombination. if anyone has answer please post it.

huangapple
  • 本文由 发表于 2020年4月7日 11:03:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/61072150.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定