O valor de alteração do JavaFX ComboBox causa IndexOutOfBoundsException




integer setvalue (2)

Eu quero incluir cheques para minha caixa de combinação para restringir o "acesso" a alguns dos valores. Eu poderia simplesmente remover esses itens inacessíveis da lista, sim, mas gostaria que o usuário visse as outras opções, mesmo que ele não possa selecioná-las (ainda).

Problema: Selecionar outro valor dentro de uma changelistener causa uma IndexOutOfBoundsException e não tenho idéia do motivo.

Aqui está um SSCCE. Cria um ComboBox com valores inteiros, e o primeiro é selecionado no padrão. Então eu tentei manter isso muito fácil: toda mudança do valor é considerada como "errada" e eu mudo a seleção de volta para o primeiro elemento. Mas ainda assim, 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();
    }
}

Eu testei com valueProperty, selectedItemProperty e selectedIndexProperty, assim como todos estes:

box.getSelectionModel().select(0);

box.getSelectionModel().selectFirst();

box.getSelectionModel().selectPrevious();

box.setValue(0);

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

O único pensamento que funciona é definir o valor em si:

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

Aqui está a exceção:

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)

O que estou fazendo de errado?


Eu sei que o segmento é bastante antigo, mas eu tive um problema semelhante e trabalhei de uma maneira diferente. Tentei alterar o item selecionado do ComboBox em seu método onAction quando o item não estava disponível naquele momento (por exemplo, devido à condição dada). Como disse @James_D em sua resposta, o problema é definir o objeto que está sendo modificado no momento.

Basta adicionar seu código dentro do método Platform.runLater() :

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

No meu caso, funcionou, espero que funcione nos outros também.


No JavaFX, você não pode alterar o conteúdo de um ObservableList enquanto uma alteração já está em andamento. O que está acontecendo aqui é que seus ouvintes (qualquer um dos que você tentar) estão sendo disparados como parte do box.getSelctionModel().getSelectedItems() ObservableList mudando. Então, basicamente, você não pode alterar a seleção enquanto uma alteração de seleção está sendo processada.

Sua solução é um pouco difícil de qualquer maneira. Se você tivesse outro ouvinte no item selecionado (ou valor de caixa de combinação), mesmo se o método funcionasse, temporariamente veria a caixa de combinação com uma seleção "ilegal". Por exemplo, no exemplo acima, se o usuário tentar selecionar "1", outro ouvinte verá a alteração da seleção para o valor não permitido "1" e depois para "0". Lidar com valores que não deveriam ser permitidos nesse ouvinte provavelmente tornaria sua lógica de programa bastante complexa.

Uma abordagem melhor, imho, é impedir que o usuário selecione os valores não permitidos. Você pode fazer isso com uma fábrica de células que define a propriedade de disable da célula:

    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);
            }
        }
    });

Incluir o seguinte em uma folha de estilos externa dará ao usuário a dica visual habitual de que os itens não são selecionáveis:

.combo-box-popup .list-cell:disabled  {
    -fx-opacity: 0.4 ;
}