【发布时间】:2019-04-15 20:44:35
【问题描述】:
在这里使用这个例子:
https://stackoverflow.com/a/47933342
我能够创建一个自动完成的下拉搜索,但是当我添加一个更改侦听器以刷新数据库中的数据时,即使我只选择了一次值,它也会被调用 3 次。我输入一个国家,然后点击国家,输出是:
连接到数据库
连接到数据库
连接到数据库
预期输出是:
连接到数据库
这是我的代码:
package autocomplete;
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class Main extends Application
{
public static class HideableItem<T>
{
private final ObjectProperty<T> object = new SimpleObjectProperty<>();
private final BooleanProperty hidden = new SimpleBooleanProperty();
private HideableItem(T object)
{
setObject(object);
}
private ObjectProperty<T> objectProperty()
{
return this.object;
}
private T getObject()
{
return this.objectProperty().get();
}
private void setObject(T object)
{
this.objectProperty().set(object);
}
private BooleanProperty hiddenProperty()
{
return this.hidden;
}
private boolean isHidden()
{
return this.hiddenProperty().get();
}
private void setHidden(boolean hidden)
{
this.hiddenProperty().set(hidden);
}
@Override
public String toString()
{
return getObject() == null ? null : getObject().toString();
}
}
@Override
public void start(Stage stage)
{
List<String> countries = new ArrayList<>();
for (String countryCode : Locale.getISOCountries())
{
Locale obj = new Locale("", countryCode);
countries.add(obj.getDisplayCountry());
}
ComboBox<HideableItem<String>> comboBox = createComboBoxWithAutoCompletionSupport(countries);
comboBox.setMaxWidth(Double.MAX_VALUE);
comboBox.valueProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
System.out.println("CONNECT TO DATABASE");
}
});
HBox root = new HBox();
root.getChildren().add(comboBox);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
comboBox.setMinWidth(comboBox.getWidth());
comboBox.setPrefWidth(comboBox.getWidth());
}
public static void main(String[] args)
{
launch();
}
private static <T> ComboBox<HideableItem<T>> createComboBoxWithAutoCompletionSupport(List<T> items)
{
ObservableList<HideableItem<T>> hideableHideableItems = FXCollections.observableArrayList(hideableItem -> new Observable[]
{
hideableItem.hiddenProperty()
});
items.forEach(item ->
{
HideableItem<T> hideableItem = new HideableItem<>(item);
hideableHideableItems.add(hideableItem);
});
FilteredList<HideableItem<T>> filteredHideableItems = new FilteredList<>(hideableHideableItems, t -> !t.isHidden());
ComboBox<HideableItem<T>> comboBox = new ComboBox<>();
comboBox.setItems(filteredHideableItems);
@SuppressWarnings("unchecked")
HideableItem<T>[] selectedItem = (HideableItem<T>[]) new HideableItem[1];
comboBox.addEventHandler(KeyEvent.KEY_PRESSED, event ->
{
if (!comboBox.isShowing())
{
return;
}
comboBox.setEditable(true);
comboBox.getEditor().clear();
});
comboBox.showingProperty().addListener((observable, oldValue, newValue) ->
{
if (newValue)
{
@SuppressWarnings("unchecked")
ListView<HideableItem> lv = ((ComboBoxListViewSkin<HideableItem>) comboBox.getSkin()).getListView();
Platform.runLater(() ->
{
if (selectedItem[0] == null) // first use
{
double cellHeight = ((Control) lv.lookup(".list-cell")).getHeight();
lv.setFixedCellSize(cellHeight);
}
});
lv.scrollTo(comboBox.getValue());
} else
{
HideableItem<T> value = comboBox.getValue();
if (value != null)
{
selectedItem[0] = value;
}
comboBox.setEditable(false);
Platform.runLater(() ->
{
comboBox.getSelectionModel().select(selectedItem[0]);
comboBox.setValue(selectedItem[0]);
});
}
});
comboBox.setOnHidden(event -> hideableHideableItems.forEach(item -> item.setHidden(false)));
comboBox.getEditor().textProperty().addListener((obs, oldValue, newValue) ->
{
if (!comboBox.isShowing())
{
return;
}
Platform.runLater(() ->
{
if (comboBox.getSelectionModel().getSelectedItem() == null)
{
hideableHideableItems.forEach(item -> item.setHidden(!item.getObject().toString().toLowerCase().contains(newValue.toLowerCase())));
} else
{
boolean validText = false;
for (HideableItem hideableItem : hideableHideableItems)
{
if (hideableItem.getObject().toString().equals(newValue))
{
validText = true;
break;
}
}
if (!validText)
{
comboBox.getSelectionModel().select(null);
}
}
});
});
return comboBox;
}
}
编辑:
似乎没有真正的解决方案......所以我最终只是听了ObjectProperty,而不是听下拉菜单的变化。如果值不为空,我然后更新属性。
【问题讨论】:
-
每次调用时
oldValue和newValue的值是多少? -
@Slaw 他们像
oldValue = null,newValue = Canada--- 然后是oldValue = Canada,newValue = null, 然后回到oldValue = null,newValue = Canada -
嗯...就像Simou's answer 我无法使用您的代码重现问题。每次更改选择时,我只会得到一个“连接到数据库”。
-
@Slaw 你用鼠标点击了那个项目吗?
-
啊,我做得不对。我错过了我必须输入并选择过滤项目的事实。我可以重现该问题。