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();
}
} );
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();
}
} );