2015-09-03 18 views
5

Desidero includere controlli per la mia casella combinata per limitare "l'accesso" ad alcuni valori. Potrei semplicemente rimuovere quegli elementi non accessibili dalla lista, sì, ma vorrei che l'utente vedesse le altre opzioni, anche se non è in grado di selezionarle (ancora).JavaFX ComboBox modifica il valore causa IndexOutOfBoundsException

Problema: la selezione di un altro valore all'interno di un alimentatore di liste causa un'eccezione IndexOutOfBoundsException e non ho idea del perché.

Ecco un SSCCE. Crea un ComboBox con valori Integer e il primo è selezionato come predefinito. Poi ho cercato di mantenerlo molto semplice: ogni modifica del valore è considerata "errata" e cambio la selezione al primo elemento. Ma ancora, 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(); 
    } 
} 

ho provato con ValueProperty, selectedItemProperty e selectedIndexProperty, così come tutti i questi:

box.getSelectionModel().select(0); 

box.getSelectionModel().selectFirst(); 

box.getSelectionModel().selectPrevious(); 

box.setValue(0); 

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

il solo pensare che funziona è l'impostazione del valore in sé:

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

Qui è l'eccezione:

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) 

Cosa sto sbagliando?

+0

Vedere http://stackoverflow.com/questions/13587134/how-to-disable-some-items-of-javafx-combobox –

risposta

6

In JavaFX, non è possibile modificare il contenuto di ObservableList mentre è già in corso una modifica. Quello che sta accadendo qui è che i tuoi ascoltatori (quelli che provi) vengono licenziati come parte della modifica box.getSelctionModel().getSelectedItems()ObservableList. Quindi, in pratica, non è possibile modificare la selezione mentre viene elaborata una modifica di selezione.

La tua soluzione è un po 'ingombrante comunque. Se avevi un altro listener sull'elemento selezionato (o sul valore della casella combinata), anche se il tuo metodo funzionasse, vedrebbe temporaneamente la casella combinata con una selezione "illegale". Ad esempio nell'esempio precedente, se l'utente tenta di selezionare "1", un altro listener vedrebbe la modifica della selezione al valore non consentito "1", quindi torna a "0". Affrontare i valori che non dovrebbero essere consentiti in questo listener probabilmente renderà la logica del tuo programma piuttosto complessa.

Un approccio migliore, imho, è quello di impedire all'utente di selezionare i valori non consentiti. È possibile farlo con una fabbrica di cellule che imposta la proprietà disable della cella:

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

Compreso quanto segue in un foglio di stile esterno darà all'utente la solita suggerimento visivo che le voci non sono selezionabili:

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

Sono d'accordo con questa risposta per il problema specifico di non consentire all'utente di selezionare determinati elementi. Ma non ho visto una buona argomentazione perché non sia permesso cambiare la selezione dall'interno cambiando la selezione. Recentemente ho archiviato un [bug report] (https://bugs.openjdk.java.net/browse/JDK-8133228). Ho poche speranze che venga corretto, ma ancora una volta, non vedo perché non dovrebbe essere permesso. Per la cronaca, la [LiveList] (http://www.reactfx.org/javadoc/2.0-M4u1/org/reactfx/collection/LiveList.html) di ReactFX supporta le modifiche ricorsive (modifiche apportate dall'interno dei listener di modifiche). –

+0

concordato. (Non ho mai detto che fosse una buona cosa: solo che è una cosa :).) I (sorta di) vedere, data l'API 'ListChangeListener.Change', che potrebbe non essere una buona idea consentire modifiche alla lista mentre sta iterando un 'Change' esistente, ma sembra che le modifiche al' selectedItem' (e 'value' in un' ComboBox') potrebbero essere fatte al di fuori di questo comunque. Ma non ho davvero approfondito il codice sorgente per questo ... –

+1

'ListChangeListener.Change' è una terribile API. Non consentire modifiche mentre si gestisce il cambiamento corrente è un dettaglio di implementazione che perde, non un vincolo fondamentale. –

5

So che il thread è piuttosto vecchio ma ho avuto un problema simile e l'ho risolto in un modo diverso. Ho provato a cambiare l'elemento selezionato di ComboBox nel suo metodo onAction quando l'elemento non era disponibile in quel momento (ad esempio a causa della condizione data). Come @James_D ha detto nella sua risposta, il problema è impostare l'oggetto che viene attualmente modificato.

Basta aggiungere il codice all'interno del metodo Platform.runLater():

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

Nel mio caso ha funzionato, spero che lavorerà negli altri.

Problemi correlati