java - ComboBox ChangeListener 在 AutoCompleteDropDown 中被调用 3 次

标签 java javafx

在这里使用这个例子:

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 而不是监听下拉列表的更改。然后,如果该值不为空,我会更新该属性。

最佳答案

正在通读并看到这个,非常有趣,您看到额外条目的原因如下:

  • comboBox.setEditable(true) 使组合框创建一个 TextField,因此将值设置为 null(文本字段的值)
  • 在框中输入内容以过滤列表,然后单击选择一个项目
  • comboBox.setEditable(false) 导致组合框将 TextField 设为 null,从而使值设为 null
  • comboBox.getSelectionModel().select(selectedItem[0]) 最终将其设置回非空

查看 ComboBox/ComboBoxBase 对 editableProperty 的注释是

请注意,当可编辑属性更改时,value 属性以及任何其他相关状态都会重置。

我不确定在键入并关闭组合框时关闭和打开可编辑标志是否有特定原因,但可能您并不真正需要执行此操作。

最后,就我所知,使用 HideableItem 而不重置谓词是否比仅使用字符串并在文本字段值更改时更新谓词更好?我从未见过 FX 中的自动完成功能以这种方式实现,我很好奇。

关于java - ComboBox ChangeListener 在 AutoCompleteDropDown 中被调用 3 次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55696979/

相关文章:

java - 如何将值从类传递到 Activity - Android

java - Gwt ListBox 字母顺序

java - ISSUE : java.net.InetAddress RESULTS 与 MYSQL 结果相比是错误的

java 将 HBox 对齐到左下角

java - JavaFX 中的寻路

java - 将 Elasticsearch 与 Spring MVC 集成

java - 如何解析内部数组json

JavaFX "is already set as root of another scene"

javafx - 如何使 TextFieldTableCell 以模型属性为条件?

未找到 JavaFX JDK