即使未处于编辑状态,JavaFx 11 ListView也会使用ESCAPE键按下事件

问题描述

我对JavaFx ListView组件有问题。我正在VBox内部使用带有TextField和ListView的弹出窗口。当TextField处于焦点位置时,通常可以按键盘上的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);
   }
}

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

只需提一下,我正在使用zulu-11.0.8 JDKFx版本。

解决方法

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

这确实是问题所在-所有具有通过其各自的行为添加到ESCAPE的易用键映射的控件都会发生(f.i.同样是具有TextFormatter的TextField)。

没有干净的方法来干扰它(Behavior和InputMap尚未进入公共api)。破解的方法是从Behavior的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 的派生类中执行此操作,并将其设置为 {{1} 的 -fx-skin } 在你的 CSS 文件中。

.list-view