使用箭头键控制连接到 TextField 的 JavaFX ContextMenu

Posted

技术标签:

【中文标题】使用箭头键控制连接到 TextField 的 JavaFX ContextMenu【英文标题】:Controlling a JavaFX ContextMenu connected to a TextField with the arrow keys 【发布时间】:2019-03-09 11:59:03 【问题描述】:

我正在尝试制作一个自动完成样式 TextField,它使用 ContextMenu 在 TextField 下方显示建议。我希望在焦点位于 TextField 上时用户按下向下键时显示 ContextMenu。这是我目前的解决方案:

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

来源:ContextMenu and programmatically selecting an item

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

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

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

Main.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();
    

AutoCompleteTextField.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;
            
        );


    

【问题讨论】:

当然,我加了一个例子。 ooookay .. 无法重现未选择,但看过它,所以似乎很随机 ;) 至于您的要求的细节:有点难以理解 - 穿着你的鞋子我会看一下 ContextMenuSkin 和 ContextMenuContent,后者负责所有的键绑定、选择等 - 你可能能够连接到它们(如果你的上下文不太复杂,比如有多个级别的子菜单)。 【参考方案1】:

我这周碰巧遇到了同样的问题。经过一段时间的挖掘,我创建了一个基于 this 和 ContextMenuContent.class 本身的解决方案。

正如 kleopatra 所建议的,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

编译和运行时,可能会出现IllegalAccessError

【讨论】:

【参考方案2】:

我通过在 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()
    

【讨论】:

以上是关于使用箭头键控制连接到 TextField 的 JavaFX ContextMenu的主要内容,如果未能解决你的问题,请参考以下文章

无法从 tableView 将数据设置为 textField - 在 Mouseclick 和上下箭头键上(H2 数据库)

Table View Controller 每行连接到不同的视图控制器

Node JS REPL、Sockets 和 Telnet - Tab 补全、箭头键等

Popover箭头与viewcontroller背景颜色不同

在 mfc 的 cedit 控制中处理箭头键

QSpinBox信号用于箭头按钮