【问题标题】:JavaFX Enter key issue on ComboBoxComboBox 上的 JavaFX Enter 键问题
【发布时间】:2021-10-30 13:33:21
【问题描述】:

我在 ComboBox 中遇到“ENTER”键事件传播问题。

我有一个表单(VBox),它在收到“ENTER”键释放事件时提交或执行验证。用户将通过在字段之间切换并点击 UP/DOWN 键在组合框中进行选择来输入详细信息(只是为了最小化鼠标交互)。到目前为止一切正常。

当用户选择 ComboBox 并按下“DOWN”键时,将显示 ComboBox 弹出窗口。用户可以使用 UP/DOWN 键选择选项。选择一个选项后,用户将按“ENTER”键关闭弹出窗口。在这里,我希望“ENTER”事件仅关闭弹出窗口而不提交表单。如果弹出窗口未打开,则 ComboBox 上的“ENTER”键应提交表单。

我尝试通过使用 ComboBox 上的事件来解决此问题。但这没有用。所以两个问题::

谁能告诉我“如何防止 'ENTER' 键事件传播到 VBox,仅当 ComboBox 显示时”?

还有谁能告诉我事件是如何传播到“VBox”处理程序的,尽管在 ComboBox 过滤器/处理程序上消耗了事件?

以下是显示问题的示例:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ComboBoxEnterKeyIssue extends Application {

    private DateFormat df = new SimpleDateFormat("hh:mm:ss");

    @Override
    public void start(Stage primaryStage) throws Exception {
        TextField textField1 = new TextField();
        textField1.setMaxWidth(150);

        ComboBox<String> comboBox = new ComboBox<>();
        comboBox.setPrefWidth(150);
        comboBox.getItems().addAll("Item 1", "Item 2", "Item 3", "Item 4", "Item 5");
        comboBox.addEventFilter(KeyEvent.KEY_RELEASED, ke -> {
            if (ke.getCode() == KeyCode.DOWN && !comboBox.isShowing()) {
                comboBox.show();
                ke.consume();
            } else if (ke.getCode() == KeyCode.ENTER) {
                ke.consume(); // Has no effect !!
            }
        });
        comboBox.addEventHandler(KeyEvent.KEY_RELEASED, ke -> {
            if (ke.getCode() == KeyCode.ENTER) {
                ke.consume(); // Has no effect !!
            }
        });

        TextField textField2 = new TextField();
        textField2.setMaxWidth(150);

        ListView<String> output = new ListView<>();
        VBox.setVgrow(output, Priority.ALWAYS);

        VBox pane = new VBox(textField1, comboBox, textField2, output);
        pane.setPadding(new Insets(10));
        pane.setSpacing(10);
        pane.setOnKeyReleased(ke -> {
            if (ke.getCode() == KeyCode.ENTER) {
                output.getItems().add(df.format(new Date()) + " :: Enter key released on pane...");
                // Form submission/validation is performed here !!
            }
        });

        Scene scene = new Scene(pane, 500, 400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("ComboBox enter key issue");
        primaryStage.show();
    }
}

【问题讨论】:

  • 我无法在 Windows 10 上使用 Java/JavaFX 16.0.2 重现该问题。虽然对我来说你的 open-dropdown-on-down-arrow-released 不起作用(它似乎只是聚焦下一个文本字段)。
  • 我在 JavaFX8 中遇到了这个问题。不幸的是,截至目前我无法升级 JavaFX 版本:(
  • @Slaw 好奇:如果组合没有显示,输入是否会到达窗格?或者我可能误解了要求:如果输入用于关闭弹出窗口,使用并验证表单,如果在组合不显示时使用输入,是否验证?
  • fx8 远早于对 keyEvent 处理的广泛清理——最糟糕的是安装在 ComboBoxPopupControl 中的任何键上的 eventFilter 会重新触发其父级上收到的键:这样做完全破坏了事件调度(即使在当前的 fx17,仍未完全解决可编辑组合的问题)。令人惊讶的是,即使在当前的 fx 中,解决这个要求(据我所知)也很困难:这里的问题是找到一个合适的条件来判断何时/不使用输入。
  • 对于 fx16+:删除 listView 的 inputMap 的输入映射,添加显示的处理程序以将标志切换为真,为组合上的任何键安装键过滤器,在按下和释放之间标志为真(并重置释放标志)..讨厌..

标签: javafx combobox javafx-8


【解决方案1】:

所以我提出了这个讨厌的解决方案来区分 enter 事件。 修复方法如下:

  • 创建了一个自定义组合皮肤来识别 listView 并在 listview 上按下“enter”键时设置一个标志。
  • 在自定义组合事件调度器中,检查是否在弹出窗口打开时按下了标志,然后在调度器中使用事件。

这个问题仍有待改进。

import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.event.EventDispatcher;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ComboBoxEnterKeyIssue extends Application {

    private DateFormat df = new SimpleDateFormat("hh:mm:ss");

    @Override
    public void start(Stage primaryStage) throws Exception {
        TextField textField1 = new TextField();
        textField1.setMaxWidth(150);

        MyComboBox<String> comboBox = new MyComboBox<>();
        comboBox.setPrefWidth(150);
        comboBox.getItems().addAll("Item 1", "Item 2", "Item 3", "Item 4", "Item 5");
        comboBox.addEventFilter(KeyEvent.KEY_RELEASED, ke -> {
            if (ke.getCode() == KeyCode.DOWN && !comboBox.isShowing()) {
                comboBox.show();
                ke.consume();
            } else if (ke.getCode() == KeyCode.ENTER) {
                ke.consume(); // Has no effect !!
            }
        });
        comboBox.addEventHandler(KeyEvent.KEY_RELEASED, ke -> {
            if (ke.getCode() == KeyCode.ENTER) {
                ke.consume(); // Has no effect !!
            }
        });

        TextField textField2 = new TextField();
        textField2.setMaxWidth(150);

        ListView<String> output = new ListView<>();
        VBox.setVgrow(output, Priority.ALWAYS);

        VBox pane = new VBox(textField1, comboBox, textField2, output);
        pane.setPadding(new Insets(10));
        pane.setSpacing(10);
        pane.setOnKeyReleased(ke -> {
            if (ke.getCode() == KeyCode.ENTER) {
                output.getItems().add(df.format(new Date()) + " :: Enter key released on pane...");
                // Form submission/validation is performed here !!
            }
        });

        Scene scene = new Scene(pane, 500, 400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("ComboBox enter key issue");
        primaryStage.show();
    }

    class MyComboBox<T> extends ComboBox<T> {
        boolean hiddenByEnter = false;

        public MyComboBox() {
            init();
        }

        public void setHiddenByEnter(boolean hiddenByEnter) {
            this.hiddenByEnter = hiddenByEnter;
        }

        private void init() {
            final EventDispatcher initial = getEventDispatcher();
            setEventDispatcher((event, tail) -> {
                // Consuming the event only if the popup is hidden by 'enter' key event.
                if (event instanceof KeyEvent && ((KeyEvent) event).getCode() == KeyCode.ENTER && hiddenByEnter) {
                    hiddenByEnter = false;
                    return null;
                }
                return initial.dispatchEvent(event, tail);
            });
        }

        @Override
        protected Skin<?> createDefaultSkin() {
            return new MyComboBoxSkin<>(this);
        }
    }

    class MyComboBoxSkin<T> extends ComboBoxListViewSkin<T> {

        private ListView<T> listView;

        public MyComboBoxSkin(MyComboBox<T> comboBox) {
            super(comboBox);
            /* Identifying the "ENTER" event on listView in popup, to differentiate the events. */
            getPopup().showingProperty().addListener((obs, old, showing) -> {
                if (showing) {
                    comboBox.setHiddenByEnter(false);
                    if (listView == null) {
                        listView = (ListView) getPopup().getScene().getRoot().lookup(".list-view");
                        listView.addEventFilter(KeyEvent.KEY_PRESSED, ke -> {
                            if (ke.getCode() == KeyCode.ENTER) {
                                comboBox.setHiddenByEnter(true);
                            }
                        });
                    }
                }
            });
        }
    }
}

【讨论】:

  • 确实很糟糕——而且很好 :) 正常事件调度的残骸发生在从 handleKeyEvent 调用的 forwardToParent(ComboBoxPopupControl)中,我认为
猜你喜欢
  • 2014-12-18
  • 1970-01-01
  • 2021-10-17
  • 2010-12-06
  • 2016-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多