Значение изменения JavaFX ComboBox вызывает IndexOutOfBoundsException

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

Проблема: выбор другого значения внутри списка изменений вызывает исключение IndexOutOfBoundsException, и я понятия не имею, почему.

Вот SSCCE. Он создает ComboBox со значениями Integer, и по умолчанию выбирается первый. Затем я попытался сделать это очень простым: каждое изменение значения считается «неправильным», и я возвращаю выделение к первому элементу. Но все же IndexOutOfBounds:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;

public class Tester extends Application{
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        ComboBox<Integer> box = new ComboBox<Integer>();
        ObservableList<Integer> vals= FXCollections.observableArrayList(0,1,2,3);

        box.setItems(vals);
        box.getSelectionModel().select(0);
        /*box.valueProperty().addListener((observable, oldValue, newValue) -> {
            box.getSelectionModel().select(0);
        });*/
        /*box.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue)->{
            System.out.println(oldValue+","+newValue);
            box.getSelectionModel().select(0);
        });*/

        box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
            System.out.println(oldValue+","+newValue);
            box.getSelectionModel().select(0);
        });
        Scene scene = new Scene(new Group(box),500,500);
        stage.setScene(scene);
        stage.show();
    }
}

Я протестировал его с помощью valueProperty, selectedItemProperty и selectedIndexProperty, а также всех этих:

box.getSelectionModel().select(0);

box.getSelectionModel().selectFirst();

box.getSelectionModel().selectPrevious();

box.setValue(0);

if (oldValue.intValue() < newValue.intValue())
            box.getSelectionModel().select(oldValue.intValue());

Единственное, что работает, это установка самого значения:

box.getSelectionModel().select(box.getSelectionModel().getSelectedIndex());
box.setValue(box.getValue));

Вот исключение:

Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException
    at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.subList(Unknown Source)
    at javafx.collections.ListChangeListener$Change.getAddedSubList(Unknown Source)
    at com.sun.javafx.scene.control.behavior.ListViewBehavior.lambda$new$178(Unknown Source)
    at com.sun.javafx.scene.control.behavior.ListViewBehavior$$Lambda$126/644961012.onChanged(Unknown Source)
    at javafx.collections.WeakListChangeListener.onChanged(Unknown Source)
    at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(Unknown Source)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(Unknown Source)
    at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(Unknown Source)
    at javafx.scene.control.MultipleSelectionModelBase.clearAndSelect(Unknown Source)
    at javafx.scene.control.ListView$ListViewBitSetSelectionModel.clearAndSelect(Unknown Source)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.simpleSelect(Unknown Source)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.doSelect(Unknown Source)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(Unknown Source)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(Unknown Source)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
    at javafx.event.Event.fireEvent(Unknown Source)
    at javafx.scene.Scene$MouseHandler.process(Unknown Source)
    at javafx.scene.Scene$MouseHandler.access$1500(Unknown Source)
    at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$350(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$$Lambda$172/2037973250.get(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.notifyMouse(Unknown Source)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$145(Unknown Source)
    at com.sun.glass.ui.win.WinApplication$$Lambda$36/2117255219.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

Что я делаю не так?


person leyren    schedule 03.09.2015    source источник
comment
См. stackoverflow.com /вопросы/13587134/   -  person Uluk Biy    schedule 03.09.2015


Ответы (2)


В JavaFX вы не можете изменить содержимое ObservableList, пока изменение уже выполняется. Здесь происходит то, что ваши слушатели (любые из тех, кого вы пытаетесь) увольняют как часть box.getSelctionModel().getSelectedItems() ObservableList изменения. Таким образом, вы не можете изменить выбор во время обработки изменения выбора.

В любом случае ваше решение немного громоздкое. Если бы у вас был другой слушатель для выбранного элемента (или значения поля со списком), даже если бы ваш метод работал, он временно увидел бы поле со списком с «недопустимым» выбором. Например, в приведенном выше примере, если пользователь попытается выбрать «1», другой слушатель увидит изменение выбора на запрещенное значение «1», а затем обратно на «0». Работа со значениями, которые не должны быть разрешены в этом слушателе, вероятно, сделает логику вашей программы довольно сложной.

Имхо, лучший подход - запретить пользователю выбирать запрещенные значения. Вы можете сделать это с помощью фабрики ячеек, которая устанавливает свойство disable ячейки:

    box.setCellFactory(lv -> new ListCell<Integer>() {
        @Override
        public void updateItem(Integer item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setText(null);
            } else {
                setText(item.toString());
                setDisable(item.intValue() != 0);
            }
        }
    });

Включение следующего во внешнюю таблицу стилей даст пользователю обычную визуальную подсказку о том, что элементы нельзя выбрать:

.combo-box-popup .list-cell:disabled  {
    -fx-opacity: 0.4 ;
}
person James_D    schedule 03.09.2015
comment
Я согласен с этим ответом в отношении конкретной проблемы, связанной с запретом пользователю выбирать определенные элементы. Но я не видел веских аргументов, почему нельзя допускать изменение выбора изнутри изменения выбора. Недавно я отправил отчет об ошибке. У меня мало надежды на то, что это будет исправлено, но опять же, я не понимаю, почему этого нельзя допустить. К сведению, LiveList ReactFX поддерживает рекурсивные изменения (изменения, сделанные из слушателей изменений). - person Tomas Mikula; 03.09.2015
comment
Согласованный. (Я никогда не говорил, что это хорошо: просто это так :).) Я (вроде как) вижу, учитывая ListChangeListener.Change API, что может быть не очень хорошей идеей разрешать изменения в списке во время итерации существующего Change, но кажется, что изменения в selectedItemvalue в ComboBox) в любом случае могут быть сделаны вне этого. Но я действительно не вникал в исходный код для этого... - person James_D; 03.09.2015
comment
ListChangeListener.Change — ужасный API. Запрет изменений при обработке текущего изменения — это деталь реализации с утечкой, а не фундаментальное ограничение. - person Tomas Mikula; 04.09.2015
comment
Этот подход не будет работать в случае, когда вам нужно выполнить какое-то действие при нажатии на отключенный элемент. - person DarkMental; 21.09.2018

Я знаю, что тема довольно старая, но у меня была похожая проблема, и я решил ее по-другому. Я пытался изменить выбранный элемент ComboBox в его методе onAction, когда элемент был недоступен в тот момент (например, из-за заданного условия). Как сказал @James_D в своем ответе, проблема заключается в установке объекта, который в данный момент изменяется.

Просто добавьте свой код в метод Platform.runLater():

Platform.runLater(() -> box.getSelectionModel().select(0));

В моем случае это сработало, надеюсь, что это сработает и в других.

person Przemysław Tamoń    schedule 07.11.2016