ComboBox ChangeListener вызывается 3 раза в AutoCompleteDropDown - PullRequest
1 голос
/ 15 апреля 2019

Используя этот пример здесь:

https://stackoverflow.com/a/47933342

Мне удалось создать выпадающий поиск с автоматическим завершением, однако при добавлении прослушивателя изменений для обновления данных из базы данных он получаетвызывается 3 раза, хотя я выбрал значение только один раз.Я набираю страну и нажимаю на страну, и вывод:

ПОДКЛЮЧЕНИЕ К БАЗЕ ДАННЫХ

ПОДКЛЮЧЕНИЕ К БАЗЕ ДАННЫХ

ПОДКЛЮЧЕНИЕ К БАЗЕ ДАННЫХ

ожидаемый выводis:

ПОДКЛЮЧИТЬ К БАЗЕ ДАННЫХ

Вот мой код:

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 вместо того, чтобы слушать изменение выпадающего списка.Затем я обновил свойство, если значение не равно нулю.

Ответы [ 2 ]

1 голос
/ 24 апреля 2019

Прочитал и увидел это, было довольно интересно, причина, по которой вы видите дополнительные записи:

  • comboBox.setEditable (true) заставляет comboBox создать TextField ипоэтому установите значение NULL (значение текстового поля)
  • Введите в поле фильтра список, а затем нажмите, выберите элемент
  • comboBox.setEditable (false) заставит comboBox сделатьзначение TextField равно нулю, поэтому значение null
  • comboBox.getSelectionModel (). select (selectedItem [0]) окончательно устанавливает его в значение, не равное нулю

Просмотр ComboBox / ComboBoxBaseкомментарии к editableProperty:

Note that when the editable property changes, the value property is reset, along with any other relevant state.

Я не уверен, есть ли конкретная причина, по которой вы устанавливаете или отключаете флаг редактирования, когда ComboBox вводится и закрывается,но, вероятно, то, что вам на самом деле не нужно делать.

Наконец, насколько мне известно, использует ли HideableItem и не сбрасывает ли предикат предпочтительнее, чем просто использование String, и обновляет предикат, когда значение текстового поля изменяется лучше?Я никогда не видел автозаполнения в FX, реализованного таким образом, и мне любопытно.

0 голосов
/ 16 апреля 2019

Я только что скопировал и запустил ваш код, и он работает правильно, как вы и ожидали, он показывает только один вывод.

here is a screen of it

после вашего комментария я попытался с помощью клавиатуры, так как целью было автозаполнение, и оно печаталось 3 раза, но не сразу. и позвольте мне сказать вам, что это не проблема, это то, как работает прослушиватель при изменении, каждый раз, когда вы указываете на новое значение, список запускает блок кода, который он содержит. ЗДЕСЬ ОРАКУЛ ДОКУМЕНТАЦИЯ ЕГО

но все же, это зависит от того, что вы планируете сделать в слушателе, а пока я советую вам работать медленно и осторожно. Я доступен, если вам нужна помощь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...