控制使用箭头键连接到文本字段的 JavaFX 上下文菜单



我正在尝试制作一个自动完成样式的文本字段,该样式使用上下文菜单在文本字段下方显示建议。我希望当用户按下向下键而焦点在文本字段上时显示上下文菜单。这是我当前的解决方案:

setOnKeyPressed(event -> {
System.out.println("pressed " + event.getCode());
switch (event.getCode()) {
case DOWN:
if(getText().length()>0) {
if (!suggestionMenu.isShowing()) {
suggestionMenu.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
}
suggestionMenu.getSkin().getNode().lookup(".menu-item").requestFocus();
}
break;
}
});

源:上下文菜单并以编程方式选择项

使用此代码,向下箭头始终"选择"(蓝色)列表中的第一项。问题是有时(对我来说似乎是随机的),第二个箭头按键不会在上下文菜单中产生任何响应 - 第一项将保持选中状态。按压后,它将始终正常工作。

我还希望在选择第一个元素时向上按会隐藏上下文菜单,并且该空间不会触发MenuItemonAction方法,尽管我真的不明白侦听此菜单的焦点/事件是如何工作的。似乎键盘同时有两个焦点 - 向上/向下,空格键和上下文菜单上的输入,而其他所有内容都转到文本字段。

编辑: 这是一个完整的示例。使用向下箭头键显示上下文菜单时,有时会导致有问题的行为,有时则不然。

主.java

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
AutoCompleteTextField actf = new AutoCompleteTextField();
pane.setTop(actf);
stage.setScene(new Scene(pane));
stage.show();
}
}

自动完成文本字段.java

import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class AutoCompleteTextField extends TextField {
private ContextMenu suggestionMenu;
public AutoCompleteTextField(){
super();
suggestionMenu = new ContextMenu();
for(int i = 0; i<5; i++) {
CustomMenuItem item = new CustomMenuItem(new Label("Item "+i), true);
item.setOnAction(event -> {
setText("selected");
positionCaret(getText().length());
suggestionMenu.hide();
});
suggestionMenu.getItems().add(item);
}
textProperty().addListener((observable, oldValue, newValue) -> {
if(getText().length()>0){
if (!suggestionMenu.isShowing())
suggestionMenu.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
} else {
suggestionMenu.hide();
}
});
setOnKeyPressed(event -> {
System.out.println("pressed " + event.getCode());
switch (event.getCode()) {
case DOWN:
if(getText().length()>0) {
if (!suggestionMenu.isShowing()) {
suggestionMenu.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
}
suggestionMenu.getSkin().getNode().lookup(".menu-item").requestFocus();
}
break;
}
});

}
}

我基于这个和ContextMenuContent.class创建了一个解决方案。

ContextMenuContent,正如克列奥帕特拉所建议的那样,执行大部分键绑定。在里面,有一种叫做requestFocusOnIndex()的方法,可以让我摆脱这种奇怪的行为。

因此,您的代码可以是:

textField.addEventFilter(KeyEvent.KEY_PRESSED, event->{
if(event.getCode() == KeyCode.DOWN) {
if(!suggestionMenu.isShowing())
suggestionMenu.show(textField, Side.BOTTOM, 0, 0);
suggestionMenuKeyBindings = (ContextMenuContent) suggestionMenu.getSkin().getNode();
suggestionMenuKeyBindings.requestFocusOnIndex(0);
}
});

另外,您需要将

--add-exports javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED

编译和运行时,否则可能会收到非法访问错误

我在 Kotlin 中通过打开 AutoCompleteTextField 初始化块中的"假"上下文菜单并在其他协程中一毫秒后将其关闭来解决 Kotlin 中的问题。

您也可以在 Java 中使用新线程执行此操作。希望对您有所帮助!

GlobalScope.launch {
Platform.runLater {
suggestionMenu.items.add(MenuItem("fake"))
suggestionMenu.show(this@AutoCompleteTextField, Side.BOTTOM, 0.0, 0.0)
}
delay(1)
Platform.runLater {
suggestionMenu.hide()
}
}

最新更新