combobox跳转到键入的char

时间:2012-11-13 14:35:32

标签: combobox javafx-2

我偶然发现(在我看来)一个愚蠢的问题。但是我没有找到解决方案(可能是因为没有使用正确的搜索关键字,或者当它变得容易时使其变得太难......) 情形:

我有500个客户的组合框。我必须选择一个客户。

在Swing中,当列表关闭并且您开始键入时,它会自动跳转到键入的字母。 E.g:

产品:

  • 亚当
  • 德克
  • 弗雷迪
  • ...
  • 罗杰
  • 史蒂芬
  • Z人

当组合框列表打开时,我只需键入“R”,然后在跳转时跳转到以“R”开头的第一个客户。在javafx 2中它似乎没有那种行为......是否有一些选项我必须启用或者我应该做一些事情,比如使用可编辑组合框代替并生成filter()方法在每个按键上被触发?

编辑:根据Bhupendra的答案解决:

public class FilterComboBox<T> extends ComboBox<T> {
private final FilterComboBox<T> fcbo = this;

//private FilterComboBox fcbo = this;
private ObservableList<T> items;
private ObservableList<T> filter;
private String s;
private Object selection;

private class KeyHandler implements EventHandler< KeyEvent> {

    private SingleSelectionModel<T> sm;

    public KeyHandler() {
        sm = getSelectionModel();
        s = "";
    }

    @Override
    public void handle(KeyEvent event) {
        filter.clear();
        // handle non alphanumeric keys like backspace, delete etc
        if (event.getCode() == KeyCode.BACK_SPACE && s.length() > 0) {
            s = s.substring(0, s.length() - 1);
        } else {
            s += event.getText();
        }

        if (s.length() == 0) {
            fcbo.setItems(items);
            sm.selectFirst();
            return;
        }
        //System.out.println(s);
        if (event.getCode().isLetterKey()) {
            for (T item : items) {
                if (item.toString().toUpperCase().startsWith(s.toUpperCase())) {

                    filter.add(item);
                    //System.out.println(item);

                    fcbo.setItems(filter);

                    //sm.clearSelection();
                    //sm.select(item);

                }
            }
            sm.select(0);
        }

    }
}

public FilterComboBox(final ObservableList<T> items) {
    super(items);
    this.items = items;
    this.filter = FXCollections.observableArrayList();

    setOnKeyReleased(new KeyHandler());

    this.focusedProperty().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            if (newValue == false) {
                s = "";
                fcbo.setItems(items);
                fcbo.getSelectionModel().select((T)selection);
            }

        }
    });

    this.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            if (newValue != null) {
                selection = (Object) newValue;
            }

        }
    });
}

}

4 个答案:

答案 0 :(得分:5)

最简单的过滤器组合框形式如下面的代码所示。但是需要更多的工作来改进它。 此外,如果列表很大,就像你的情况一样,可能会出现性能问题,因为我们在每次按键时循环“整个集合。”

public class FilterComboBox extends ComboBox< String > {
    private ObservableList< String >    items;

    private class KeyHandler implements EventHandler< KeyEvent > {

        private SingleSelectionModel< String >  sm;
        private String                          s;

        public KeyHandler() {
            sm = getSelectionModel();
            s = "";
        }

        @Override
        public void handle( KeyEvent event ) {
            // handle non alphanumeric keys like backspace, delete etc
            if( event.getCode() == KeyCode.BACK_SPACE && s.length()>0)
                s = s.substring( 0, s.length() - 1 );
            else s += event.getText();

            if( s.length() == 0 ) {
                sm.selectFirst();
                return;
            }
            System.out.println( s );
            for( String item: items ) {
                if( item.startsWith( s ) ) sm.select( item );
            }
        }

    }

    public FilterComboBox( ObservableList< String > items ) {
        super( items );
        this.items = items;

        setOnKeyReleased( new KeyHandler() );
    }
}

答案 1 :(得分:1)

我无法真正获得Perneel的解决方案以满足我的需求。 Bhupendra很不错,但有一个细节:它选择最后一个匹配的项目。如果你有0到20之间的数字(如String),如果键入“1”,它将返回19而不是1 ...

下面的代码添加了要求解决此问题的行。

import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

// TODO: Auto-generated Javadoc
/**
 * The Class FilterComboBox.
 */
public class FilterComboBox extends ComboBox< String > 
{

    /** The items. */
    private ObservableList< String >    items;

    /**
     * The Class KeyHandler.
     */
    private class KeyHandler implements EventHandler< KeyEvent > 
    {

        /** The sm. */
        private SingleSelectionModel< String >  sm;

        /** The s. */
        private String                          s;

        /**
         * Instantiates a new key handler.
         */
        public KeyHandler() 
        {
            sm = getSelectionModel();
            s = "";
        }

        /* (non-Javadoc)
         * @see javafx.event.EventHandler#handle(javafx.event.Event)
         */
        @Override
        public void handle( KeyEvent event ) 
        {
            // handle non alphanumeric keys like backspace, delete etc
            if( event.getCode() == KeyCode.BACK_SPACE && s.length()>0)
            {
                s = s.substring( 0, s.length() - 1 );
            }
            else if(event.getCode() != KeyCode.TAB )
            {
                s += event.getText();
            }

            if( s.length() == 0 ) 
            {
                sm.selectFirst();
                return;
            }
            System.out.println( s );
            for( String item: items ) 
            {
                if( item.startsWith( s ) ) 
                {
                    sm.select( item );
                    return;
                }
            }
        }

    }

    /**
     * Instantiates a new filter combo box.
     *
     * @param items the items
     */
    public FilterComboBox( ObservableList< String > items ) 
    {
        super( items );
        this.items = items;

        setOnKeyReleased( new KeyHandler() );
    }
}

此组件是一个ComboBox,它只接受String作为输入,可以通过键入一些字符进行过滤。所有功劳都归功于Bhupendra,我只发布了这段代码,以防止其他人不必过多考虑这个常见问题。 上次编辑:添加了一个测试,以防止将TAB视为一个字符(允许在不破坏组件的情况下在表单中导航)

答案 2 :(得分:1)

这样的代码不够吗?

    comboBox.setOnKeyReleased(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent event) {
            String s = jumpTo(event.getText(), comboBox.getValue(), comboBox.getItems());
            if (s != null) {
                comboBox.setValue(s);
            }
        }
    });

...

static String jumpTo(String keyPressed, String currentlySelected, List<String> items) {
    String key = keyPressed.toUpperCase();
    if (key.matches("^[A-Z]$")) {
        // Only act on letters so that navigating with cursor keys does not
        // try to jump somewhere.
        boolean letterFound = false;
        boolean foundCurrent = currentlySelected == null;
        for (String s : items) {
            if (s.toUpperCase().startsWith(key)) {
                letterFound = true;
                if (foundCurrent) {
                    return s;
                }
                foundCurrent = s.equals(currentlySelected);
            }
        }
        if (letterFound) {
            return jumpTo(keyPressed, null, items);
        }
    }
    return null;
}

当您按一个字母时,这将跳转到第一个项目。如果你再次按下该字母,它会跳转到以该字母开头的下一个项目,如果没有更多项目以该字母开头,则回到第一个项目。

答案 3 :(得分:0)

这是另一种方法。
以网站https://tech.chitgoks.com的示例为基础。

它具有优雅的解决方案,如果需要,也可以在前面的示例中使用。
键入时,列表会自动滚动,非常方便。

    import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.scene.control.ComboBox;
    import javafx.scene.control.ListView;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;

    import java.time.Duration;
    import java.time.Instant;
    import java.util.Collection;

    class SearchComboBox<T> extends ComboBox<T> {

        private static final int IDLE_INTERVAL_MILLIS = 1000;

        private Instant instant = Instant.now();
        private StringBuilder sb = new StringBuilder();

        public SearchComboBox(Collection<T> choices) {
            this(FXCollections.observableArrayList(choices));
        }

        public SearchComboBox(final ObservableList<T> items) {
            this();
            setItems(items);
            getSelectionModel().selectFirst();
        }

        public SearchComboBox() {
            super();

            this.addEventFilter(KeyEvent.KEY_RELEASED, event -> {
                if (event.getCode() == KeyCode.ESCAPE && sb.length() > 0) {
                    resetSearch();
                }
            });

            this.setOnKeyReleased(event -> {

                        if (Duration.between(instant, Instant.now()).toMillis() > IDLE_INTERVAL_MILLIS) {
                            resetSearch();
                        }

                        instant = Instant.now();

                        if (event.getCode() == KeyCode.DOWN || event.getCode() == KeyCode.UP || event.getCode() == KeyCode.TAB) {
                            return;
                        } else if (event.getCode() == KeyCode.BACK_SPACE && sb.length() > 0) {
                            sb.deleteCharAt(sb.length() - 1);
                        } else {
                            sb.append(event.getText().toLowerCase());
                        }

                        if (sb.length() == 0) {
                            return;
                        }

                        boolean found = false;
                        for (int i = 0; i < getItems().size(); i++) {
                            if (event.getCode() != KeyCode.BACK_SPACE && getItems().get(i).toString().toLowerCase().startsWith(sb.toString())) {
                                ListView listView = getListView();
                                listView.getSelectionModel().clearAndSelect(i);
                                scroll();
                                found = true;
                                break;
                            }
                        }

                        if (!found && sb.length() > 0)
                            sb.deleteCharAt(sb.length() - 1);
                    }
            );

            // add a focus listener such that if not in focus, reset the search process
            this.focusedProperty().addListener((observable, oldValue, newValue) -> {
                if (!newValue) {
                    resetSearch();
                } else {
                    scroll();
                }
            });
        }

        private void resetSearch() {
            sb.setLength(0);
            instant = Instant.now();
        }

        private void scroll() {
            ListView listView = getListView();
            int selectedIndex = listView.getSelectionModel().getSelectedIndex();
            listView.scrollTo(selectedIndex == 0 ? selectedIndex : selectedIndex - 1);
        }

        private ListView getListView() {
            return ((ComboBoxListViewSkin) this.getSkin()).getListView();
        }
    }

我通过两种方式改进了这个示例。

  1. 所有代码都封装在一个类中-不需要从外部进行任何连接。
  2. 如果用户一段时间未显示活动,则将重置搜索字符串。另一种解决方案是如何重置搜索:按Backspace键,或使ComboBox失去焦点。