JavaFX ComboBox 更改值导致 IndexOutOfBoundsException

Posted

技术标签:

【中文标题】JavaFX ComboBox 更改值导致 IndexOutOfBoundsException【英文标题】:JavaFX ComboBox change value causes IndexOutOfBoundsException 【发布时间】:2015-11-28 23:30:17 【问题描述】:

我想对我的组合框进行检查,以限制对某些值的“访问”。我可以从列表中删除那些无法访问的项目,是的,但我希望用户看到其他选项,即使他(还)不能选择它们。

问题:在 changelistener 中选择另一个值会导致 IndexOutOfBoundsException,我不知道为什么。

这是一个 SSCCE。它创建一个具有整数值的 ComboBox,默认情况下选择第一个。然后我尽量保持简单:值的每次更改都被认为是“错误的”,我将选择更改回第一个元素。但是,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)

我做错了什么?

【问题讨论】:

见***.com/questions/13587134/… 【参考方案1】:

我知道这个线程已经很老了,但我遇到了类似的问题,我以不同的方式解决了这个问题。当项目当时不可用时(例如由于给定条件),我尝试在其onAction 方法中更改 ComboBox 的选定项目。正如@James_D 在他的回答中所说,问题在于设置当前正在修改的对象。

只需在Platform.runLater() 方法中添加您的代码:

Platform.runLater(() -&gt; box.getSelectionModel().select(0));

在我的情况下它有效,希望它也适用于其他人。

【讨论】:

【参考方案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 ;

【讨论】:

对于不允许用户选择某些项目的特定问题,我同意这个答案。但是我还没有看到一个很好的论据,为什么不允许从更改选择中更改选择。我最近为它提交了bug report。我不太希望它会被修复,但是我不明白为什么不应该允许它。作为记录,ReactFX 的 LiveList 支持递归更改(从更改侦听器中进行的更改)。 同意。 (我从来没有说过这是一件好事:只是它是一件事:)。)我(有点)看到,鉴于ListChangeListener.Change API,在迭代时允许更改列表可能不是一个好主意现有的Change,但似乎无论如何都可以在此之外对selectedItem(和value 中的ComboBox)进行更改。但是我还没有真正深入研究过这个的源代码...... ListChangeListener.Change 是一个糟糕的 API。在处理当前更改时不允许更改是泄漏的实现细节,而不是基本约束。 这种方法在您需要在点击禁用的项目时执行一些操作的情况下不起作用。

以上是关于JavaFX ComboBox 更改值导致 IndexOutOfBoundsException的主要内容,如果未能解决你的问题,请参考以下文章

如何为 JavaFX 中的 ComboBox 中的项目添加值

如何在 JavaFx 的可编辑 ComboBox 中获取输入值?

如何修复 javafx 中的渲染错误(ComboBox、ListView)

javafx 创建 ComboBox TableCell

如何以编程方式在 JavaFX ComboBox 中设置字符串值

在 JavaFX 中管理 ComboBox 项