当前位置: 首页 > 知识库问答 >
问题:

如何在默认按钮操作之前使用按键事件?

罗河
2023-03-14

我很难使用< code>onKeyPressed事件。我的应用程序中有一个< code>TextField,允许用户按下[ENTER]键来实现某项功能;然而,我也为场景指定了一个默认按钮。

虽然我可以在<code>文本字段</code>中成功触发按键所需的操作,但默认按钮的操作始终首先执行。当用户位于文本字段中时,我需要完全为按键使用事件。

请参阅以下MCVE:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple UI
        VBox root = new VBox(10);
        root.setPadding(new Insets(10));
        root.setAlignment(Pos.CENTER);

        // TextField
        TextField textField = new TextField();

        // Capture the [ENTER] key
        textField.setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.ENTER) {
                System.out.println("-> Enter");
                event.consume();
            }
        });

        // Buttons
        Button btnCancel = new Button("Cancel");
        btnCancel.setCancelButton(true);
        btnCancel.setOnAction(e -> {
            System.out.println("-> Cancel");
            primaryStage.close();
        });

        Button btnSave = new Button("Save");
        btnSave.setDefaultButton(true);
        btnSave.setOnAction(e -> {
            System.out.println("-> Save");
            primaryStage.close();
        });

        ButtonBar buttonBar = new ButtonBar();
        buttonBar.getButtons().addAll(btnCancel, btnSave);

        root.getChildren().addAll(textField, buttonBar);

        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Consume Event");
        primaryStage.show();
    }
}

所需的行为是能够在文本字段中键入并按 Enter 键。输出应仅显示 -

但是,当前发生的情况是,该阶段以以下输出结束:

-> Save
-> Enter

我是否将< code>event.consume()调用放在了错误的位置?我想保持默认按钮不变。

编辑:

这似乎只是JDK 10中的一个问题。我再次尝试使用JDK 1.8.161,它的行为符合预期。Java 10可能的bug?

已提交错误报告:查看错误报告

共有2个答案

程俊健
2023-03-14

问题得到了回答(这是OP报告的一个错误,修复程序已获得批准,并将使其进入openjfx14):

  • 在“特殊”中消费事件(因为它保证是为相同类型/阶段/事件注册的最后一个in处理程序)事件处理程序必须工作,即停止将事件分派给其他相关方。
  • 此时,我们正处于事件调度的冒泡阶段的开始。
  • 加速器由场景/舞台处理,即在冒泡阶段结束时:如果一切正常,则在开始消耗时不应到达加速器。(注意:找不到处理加速器的正式规范,只是场景内部事件调度程序KeyboardShortCutsHandler类型中的一条代码注释,所以对此持保留态度)

但为什么会这样呢?

下面是一个例子:对于像F5这样的键,调度完全按照指定的方式发生:沿着场景图向下直到文本字段,然后向上直到加速器。输出是:

-> filter on parent:  source: VBox target: TextField
-> filter on field  source: TextField target: TextField
-> handler on field  source: TextField target: TextField
-> onKeyPressed on field  source: TextField target: TextField
-> handler on parent:  source: VBox target: TextField
-> onKeyPressed on parent  source: VBox target: TextField
in accelerator

此外,链中的任何处理程序都可以使用并停止进一步调度。

现在切换到 ENTER,看看调度链是如何变得严重混乱的,以至于在加速器之后,轮到特殊压制的处理程序作为最后一个。输出:

-> filter on parent:  source: VBox target: TextField
-> filter on field  source: TextField target: TextField
-> handler on field  source: TextField target: TextField
action added: javafx.event.ActionEvent[source=TextField@53c9244[styleClass=text-input text-field]]
-> filter on parent:  source: VBox target: VBox
-> handler on parent:  source: VBox target: VBox
-> onKeyPressed on parent  source: VBox target: VBox
in accelerator
-> onKeyPressed on field  source: TextField target: TextField

消费可以在所有处理程序中完成(并且有效),除了字段上的特殊处理程序。

如果没有actionHandler使用keyEvent,问题的根源似乎是手动转发keyEvent(我怀疑转发代码来自InputMap引入之前,但...没有深入研究那个方向)

这个例子有点脏(*咳嗽-内部api,私有字段...)并修补了text Field的inputMap。这个想法是摆脱手动转发,让正常事件调度完成它的工作。控制正常调度的钩子是事件的消耗状态。补丁代码

  • 将ENTER键映射替换为自定义实现。
  • 禁用映射的自动消耗标志,这会将控件完全移动到自定义处理程序中。
  • 通过字段创建并触发ActionEvent(源和目标都设置为字段,这是修复JDK-8207774)
  • 设置ENTER事件的已使用状态(如果操作已处理),否则让它冒泡

似乎可以工作,正如调度日志记录的输出所看到的那样,现在与F5等普通键相同-但请注意:没有进行正式测试!

最后示例代码:

public class TextFieldActionHandler extends Application {

    private TextField textField;

    private KeyCode actor = KeyCode.ENTER;
//    private KeyCode actor = KeyCode.F5;
    private Parent createContent() {
        textField = new TextField("just some text");
        textField.skinProperty().addListener((src, ov, nv) -> {
            replaceEnter(textField);

        });
        // only this here is in the bug report, with consume
        // https://bugs.openjdk.java.net/browse/JDK-8207774
        textField.addEventHandler(ActionEvent.ACTION, e -> {
            System.out.println("action added: " + e);
//            e.consume();
        });

        //everything else is digging around
        textField.setOnKeyPressed(event -> {
            logEvent("-> onKeyPressed on field ",  event);
        });

        textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            logEvent("-> filter on field ", event);
        });

        textField.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
            logEvent("-> handler on field ", event);
        });

        VBox pane = new VBox(10, textField);

        pane.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
            logEvent("-> handler on parent: ", e);
        });

        pane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
            logEvent("-> filter on parent: ", e);
        });

        //everything else is digging around
        pane.setOnKeyPressed(event -> {
            logEvent("-> onKeyPressed on parent ",  event);
        });

        return pane;
    }

    private void logEvent(String message, KeyEvent event) {
        logEvent(message, event, false);
    }

    private void logEvent(String message, KeyEvent event, boolean consume) {
        if (event.getCode() == actor) {
            System.out.println(message + " source: " + event.getSource().getClass().getSimpleName() 
                    + " target: " + event.getTarget().getClass().getSimpleName());
            if (consume)
                event.consume();    
        }

    }
    @Override
    public void start(Stage stage) throws Exception {
        Scene scene = new Scene(createContent());
        scene.getAccelerators().put(KeyCombination.keyCombination(actor.getName()),
                () -> System.out.println("in accelerator"));
        stage.setScene(scene);
        stage.setTitle(FXUtils.version());
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    /** 
     * fishy code snippet from TextFieldBehaviour: 
     * 
     * https://bugs.openjdk.java.net/browse/JDK-8207774
     * during fire, the actionEvent without target is copied - such that
     * the check for being consumed of the original has no effect
     */
//    @Override protected void fire(KeyEvent event) {
//        TextField textField = getNode();
//        EventHandler<ActionEvent> onAction = textField.getOnAction();
//        ActionEvent actionEvent = new ActionEvent(textField, null);
//
//        textField.commitValue();
//        textField.fireEvent(actionEvent);
//
//        if (onAction == null && !actionEvent.isConsumed()) {
//            forwardToParent(event);
//        }
//    }


    // dirty patching
    protected void replaceEnter(TextField field) {
        TextFieldBehavior behavior = (TextFieldBehavior) FXUtils.invokeGetFieldValue(
                TextFieldSkin.class, field.getSkin(), "behavior");
        InputMap<TextField> inputMap = behavior.getInputMap();
        KeyBinding binding = new KeyBinding(KeyCode.ENTER);

        KeyMapping keyMapping = new KeyMapping(binding, this::fire);
        keyMapping.setAutoConsume(false);
        // note: this fails prior to 9-ea-108
        // due to https://bugs.openjdk.java.net/browse/JDK-8150636
        inputMap.getMappings().remove(keyMapping); 
        inputMap.getMappings().add(keyMapping);
    }

    /**
     * Copy from TextFieldBehaviour, changed to set the field as
     * both source and target of the created ActionEvent.
     * 
     * @param event
     */
    protected void fire(KeyEvent event) {
        EventHandler<ActionEvent> onAction = textField.getOnAction();
        ActionEvent actionEvent = new ActionEvent(textField, textField);

        textField.commitValue();
        textField.fireEvent(actionEvent);
        // remove the manual forwarding, instead consume the keyEvent if
        // the action handler has consumed the actionEvent
        // this way, the normal event dispatch can jump in with the normal
        // sequence
        if (onAction != null || actionEvent.isConsumed()) {
            event.consume();
        }
        // original code
//        if (onAction == null && !actionEvent.isConsumed()) {
////            forwardToParent(event);
//        }
        logEvent("in fire: " + event.isConsumed(), event);
    }

    protected void forwardToParent(KeyEvent event) {
        if (textField.getParent() !=  null) {
            textField.getParent().fireEvent(event);
        }
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TextFieldActionHandler.class.getName());

}
乐正烨熠
2023-03-14

正如文件所述:

Windows / Linux:当有焦点时,默认按钮接收回车键。当默认按钮没有焦点,并且焦点在另一个按钮控件上时,ENTER键的按下将被另一个非默认按钮接收。当焦点在用户界面的其他地方,而不是在任何按钮上时,如果指定了默认按钮,并且场景中没有其他节点首先使用它,则默认按钮将接收ENTER键的按下。

所以我相信这是一个错误。正如我在评论中所说,一种解决方法是检查TextField是否在befault按钮的setOnAction内具有焦点,并在那里消费事件,直到他们修复它。

 类似资料:
  • 我有一个按钮,可以设置一个动作,比如 如何从该按钮中删除此EventFilter?我尝试了方法,但应该是什么参数?

  • 我正在使用Microsoft Visual C#2010 Express编写一个窗口窗体应用程序。我在写一个数独程序。我以前写过一次这个程序,并让它工作。由于硬盘故障,我丢失了源代码。我在表格上画了一个网格。我让鼠标事件工作了。我有重要的新闻活动要办。然后我在表单中添加了几个按钮,并让它们工作。但随后出现了一个问题。在我添加按钮并使其工作后,按键事件停止工作。为什么按钮事件和按键事件之间会发生冲突

  • 浮动操作按钮 运行方式 过渡 大屏幕 浮动操作按钮 浮动操作按钮 浮动操作按钮适用于进阶的操作。它是漂浮在 UI 上的一个圆形图标,具有一些动态的效果,比如变形、弹出、位移等等。 浮动操作按钮有两种尺寸: 默认尺寸:适用于多数应用情况。 迷你尺寸:仅用于创建与其他屏幕元素视觉的连续性。 浮动操作按钮 迷你浮动操作按钮 浮动操作按钮应至少放在距手机边缘 16dp 或电脑/台式机边缘 24dp 的地方

  • 我想在下面的文本框中捕捉enter键按下事件。为了更清楚地说明这一点,我使用了一个ng repeat来填充tbody。以下是HTML: 这是我的模块: 我正在使用资源填充表格,我的控制器代码是:

  • 问题内容: 我想在下面的文本框中捕获Enter键按下事件。为了更清楚地说明,我使用a 来填充tbody。这是HTML: 这是我的模块: 我正在使用一种资源来填充表,而我的控制器代码是: 问题答案: 您需要添加,如下所示: Javascript : HTML :

  • 问题内容: 问题: 在哪里可以找到带有十六进制颜色代码的默认样式xml? 我在寻找’buttonStyle’样式,其他默认样式会影响诸如TextViews,Buttons等方面(如果您不更改方面的样式) 我抬头望去,但实际上没有找到想要的东西。 希望我的问题清楚。 由于信誉低,我无法回答这个问题。这是答案 回答 稍作谷歌搜索,我发现’buttonStyle’实际上是’Widget.Button’-