JavaFx 11 ListView 即使不处于编辑状态也会消耗 ESCAPE 键按下事件

JavaFx 11 ListView consumes ESCAPE key pressed event even if is not in editing state

JavaFx ListView 组件有问题。我在 VBox 中使用带有 TextField 和 ListView 的弹出窗口。当 TextField 处于焦点时,我通常可以关闭此弹出窗口 window 按键盘上的 Esc 键,但是当 ListView 项目处于焦点时弹出窗口保持打开状态,没有任何反应。

最小可重现示例:

package sample;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

   @Override
   public void start(Stage primaryStage) throws Exception {
      MenuItem rightClickItem = new MenuItem("CLICK!");
      rightClickItem.setOnAction(a -> showdialog());
      ContextMenu menu = new ContextMenu(rightClickItem);

      Label text = new Label("Right Click on me");

      text.setContextMenu(menu);

      StackPane root = new StackPane(text);
      Scene scene = new Scene(root, 300, 250);

      primaryStage.setTitle("RightClick MenuItem And Dialog");
      primaryStage.setScene(scene);
      primaryStage.show();
   }

   private void showdialog() {
      Dialog<ButtonType> dialog = new Dialog<>();
      dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
      VBox vBox = new VBox();
      ListView listView = new ListView();
      listView.getItems().add("Item 1");
      listView.getItems().add("Item 2");
      vBox.getChildren().add(new TextField());
      vBox.getChildren().add(listView);

      vBox.addEventHandler(KeyEvent.KEY_PRESSED, keyEvent -> System.err.println("Key pressed: " + keyEvent.getCode()));

      dialog.getDialogPane().setContent(vBox);

      dialog.showAndWait();
   }


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

在我看来,ListView 中使用了 Esc 键,这导致关闭弹出窗口出现问题。

顺便提一下,我使用的是 zulu-11.0.8 JDKFx 版本。

It seems to me that Esc key is consumed in ListView, and this cause a problem with closing a popup.

这确实是问题所在 - 所有控件都会发生这种情况,这些控件具有通过其各自的行为添加到 ESCAPE 的消耗性 KeyMapping(f.i。也适用于带有 TextFormatter 的 TextField)。

没有干净的方法来干扰它(Behavior 和 InputMap 尚未进入 public api)。破解的方法是从行为的 inputMap 中删除 KeyMapping。当心:必须允许你变脏,即使用内部 api 并使用反射!

步骤:

  • 抓取控件的皮肤(将控件添加到场景图后可用)
  • 反射性地访问皮肤的行为
  • 从行为的 inputMap 中删除 keyMapping

示例代码片段:

private void tweakInputMap(ListView listView) {
    ListViewSkin<?> skin = (ListViewSkin<?>) listView.getSkin();
    // use your favorite utility method to reflectively access the private field
    ListViewBehavior<?> listBehavior = (ListViewBehavior<?>) FXUtils.invokeGetFieldValue(
            ListViewSkin.class, skin, "behavior");
    InputMap<?> map = listBehavior.getInputMap();
    Optional<Mapping<?>> mapping = map.lookupMapping(new KeyBinding(KeyCode.ESCAPE));
    map.getMappings().remove(mapping.get());
}

它的用法:

listView.skinProperty().addListener(ov -> {
    tweakInputMap(listView);
});

为避免使用私有 API,您可以使用事件过滤器,如果 ListView 未在编辑,则复制 Escape 键事件并在父级上触发它。从那里,复制的事件可以传播以在其他处理程序中有用,例如关闭弹出窗口。

此外,如果您在应用程序中的所有 ListView 上都需要这个,您可以在 ListViewSkin 的派生 class 中执行此操作并将其设置为 -fx-skin CSS 文件中的 .list-view

      listView.addEventFilter( KeyEvent.KEY_PRESSED, keyEvent -> {
         if( keyEvent.getCode() == KeyCode.ESCAPE && !keyEvent.isAltDown() && !keyEvent.isControlDown()
               && !keyEvent.isMetaDown() && !keyEvent.isShiftDown()
         ) {
            if( listView.getEditingIndex() == -1 ) {
               // Not editing.
               final Parent parent = listView.getParent();
               parent.fireEvent( keyEvent.copyFor( parent, parent ) );
            }
            keyEvent.consume();
         }
      } );