【问题标题】:Make javafx choicebox select item based on keyboard基于键盘使javafx选择框选择项目
【发布时间】:2021-10-18 21:31:00
【问题描述】:

根据https://wiki.openjdk.java.net/display/OpenJFX/ChoiceBox+User+Experience+Documentation

Any Alpha-numeric Keys 应该在Jumps to the first item in the list with that letter or lettersSelected state

但是,情况似乎根本不是这样,在选择小部件时键入似乎没有任何作用。

这是我的最小可重现示例:

package com.techniqab.testchoicebox;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

import java.net.URL;
import java.nio.file.Paths;


public class TestchoiceboxApplication extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        URL url = Paths.get("./src/main/resources/sample.fxml").toUri().toURL();

        Parent root = FXMLLoader.load(url);
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();

        var cb = new ChoiceBox();
        cb.getItems().add("a");
        cb.getItems().add("b");

        ((Pane) root).getChildren().add(cb);
    }


    public static void main(String[] args) {
        launch(args);
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="com.techniqab.testchoicebox.TestchoiceboxApplication"
            prefHeight="400.0" prefWidth="600.0">

</AnchorPane>
module testchoicebox {
    requires javafx.controls;
    requires javafx.graphics;
    requires javafx.fxml;
    exports com.techniqab.testchoicebox;
    opens com.techniqab.testchoicebox to javafx.graphics;
}

【问题讨论】:

  • 您能否创建一个minimal reproducible example:这依赖于您尚未发布的 FXML 文件。 (而且,题外话,但你为什么要从源文件夹加载 FXML 文件?)
  • 该功能标记为Application Dependant [sic],这可能意味着它取决于您的应用程序。
  • @trashgod 我没有看到它在哪里被标记为应用程序依赖...我的应用程序依赖于此,我该如何实现它
  • @James_D 添加了其他文件...至 FXML.. 位置,因为我是 java/spring/和 javafx 的新手,这就是我想出运行最小示例的方法。
  • 两个用例被标记为Dependant——注意拼写错误。对于很长的列表,也许是某种Pagination 控件?

标签: java javafx


【解决方案1】:

虽然ChoiceBox User Experience Documentation 是规范的,但这个更简单的变体会跳转到列表中输入字母的第一项。它还响应 EnterReturn 键。

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;

/**
 * https://stackoverflow.com/a/69591397/230513
 */
public class KeyTest extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("Key Test");
        ObservableList<String> items = FXCollections.observableArrayList(
            "a10", "b42", "c33", "a11", "b22", "c333", "d0");
        FXCollections.sort(items);
        ChoiceBox cb = new ChoiceBox(items);
        EventHandler<KeyEvent> keyEventHandler = (var e) -> {
            if (e.getCode() == KeyCode.ENTER) {
                return;
            }
            e.consume();
            for (var item : cb.getItems()) {
                if (item.toString().startsWith(e.getCharacter())) {
                    cb.getSelectionModel().select(item);
                    cb.show();
                    break;
                }
            }
        };
        cb.setOnKeyTyped(keyEventHandler);
        StackPane root = new StackPane();
        StackPane.setMargin(cb, new Insets(16, 16, 16, 16));
        root.getChildren().add(cb);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

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

对于多键前缀选择,如here 所述,还可以考虑ControlsFX 中包含的PrefixSelectionChoiceBoxPrefixSelectionComboBox

【讨论】:

    【解决方案2】:

    这似乎重新发明了一些应该起作用的东西......显然,人们希望将 ChoiceBox 子类化并添加这种行为,而不是我在这里作为 POC 所做的黑客......但这很有效

    import javafx.application.Application;
    import javafx.event.EventHandler;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.scene.control.ChoiceBox;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.Pane;
    import javafx.stage.Stage;
    
    import java.net.URL;
    import java.nio.file.Paths;
    import java.time.Duration;
    import java.time.Instant;
    import java.time.LocalTime;
    
    
    public class TestchoiceboxApplication extends Application {
        private String typed="";
        private Boolean gotIt=false;
        private Instant lastKeyTime = Instant.now();
    
        @Override
        public void start(Stage primaryStage) throws Exception{
            URL url = Paths.get("./src/main/resources/sample.fxml").toUri().toURL();
    
            Parent root = FXMLLoader.load(url);
            primaryStage.setTitle("Hello World");
            primaryStage.setScene(new Scene(root, 300, 275));
            primaryStage.show();
    
            var cb = new ChoiceBox();
            cb.getItems().add("a");
            cb.getItems().add("b");
            cb.getItems().add("ab");
            cb.getItems().add("aa");
            cb.getItems().add("c");
            cb.getItems().add("cba");
    
    
            EventHandler<KeyEvent> keyEventHandler =
                    new EventHandler<KeyEvent>() {
                        public void handle(final KeyEvent keyEvent) {
                            if (keyEvent.getCode() == KeyCode.UNDEFINED) {
                                return;
                            }
                            keyEvent.consume();
    
                            var  now = Instant.now();
                            if (Duration.between(lastKeyTime, now).getSeconds()>3) {
                                //reset search after timeout
                                typed="";
                            }
                            lastKeyTime = now;
    
                            typed = typed + keyEvent.getCharacter();
                            var items = cb.getItems();
                            int nMatch = 0;
                            int i=0;
                            for (var item: items) {
                                if (item.toString().startsWith(typed)) {
                                    if (nMatch==0) {
                                        cb.getSelectionModel().select(item);
                                    }
                                    nMatch++;
                                    i++;
                                }
                            }
                            if (nMatch>1) {
                                cb.show();
                                return;
                            } else {
                                if (nMatch == 1) {
                                    cb.hide();
                                    return;
                                }
                            }
    
                            // key didn't match
                            typed =  keyEvent.getCharacter();
                            cb.show();
                            nMatch = 0;
                            for (var item: items) {
                                if (item.toString().startsWith(typed)) {
                                    if (nMatch==0) {
                                        cb.getSelectionModel().select(item);
                                    }
                                    nMatch++;
                                }
                            }
                            if (nMatch>1) {
                                cb.show();
                                return;
                            } else {
                                if (nMatch == 1) {
                                    cb.hide();
                                    return;
                                }
                            }
                            typed = "";
                        };
                    };
    
            cb.setOnKeyTyped(keyEventHandler);
            ((Pane) root).getChildren().add(cb);
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

    • 我无法获得输入或返回键来接受选择;我必须点击鼠标。你的 sample.fxml 会影响这个吗?
    • @trashgod 看起来您只能在 KeyEvent.KEY_PRESSED / KEY_RELEASED 处理程序docs.oracle.com/javafx/2/api/javafx/scene/input/… 中获取 KeyCode,我们需要检查 '\r' || '\n' 或单独处理按下/释放
    • 啊,我明白KeyCode.UNDEFINED的意思了,现在。您正在使用更高级别的 key typed 事件,而我的 approachkey typedlower-level 事件混合在一起。我仍在尝试理解引用 hereControlsFX
    • @trashgod 我注意到另一个问题,选择将按我的预期移动并在所选项目上显示一个复选框,但如果我的鼠标在列表中,向上箭头将相对于鼠标悬停的最后一个项目,而不是最后一个选择。
    • 如果你走那条路,ControlsFX 实现是here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 1970-01-01
    • 2013-03-26
    相关资源
    最近更新 更多