我偶然发现(在我看来)一个愚蠢的问题。但是我没有找到解决方案(可能是因为没有使用正确的搜索关键字,或者当它变得容易时使其变得太难......) 情形:
我有500个客户的组合框。我必须选择一个客户。
在Swing中,当列表关闭并且您开始键入时,它会自动跳转到键入的字母。 E.g:
产品:
当组合框列表打开时,我只需键入“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;
}
}
});
}
}
答案 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();
}
}
我通过两种方式改进了这个示例。