5

Ich möchte Prüfungen für meine Combobox einzuschränken "Zugriff" auf einige der Werte zu beschränken. Ich könnte einfach diese unerreichbaren Gegenstände aus der Liste entfernen, ja, aber ich möchte, dass der Benutzer die anderen Optionen sieht, auch wenn er sie (noch) nicht auswählen kann.JavaFX ComboBox Änderung Wert verursacht IndexOutOfBoundsException

Problem: Auswählen eines anderen Werts in einem ChangeListener verursacht eine IndexOutOfBoundsException, und ich habe keine Ahnung warum.

Hier ist ein SSCCE. Es erstellt ein Kombinationsfeld mit Integer-Werten, und das erste wird standardmäßig ausgewählt. Dann habe ich versucht, es sehr einfach zu halten: Jede Änderung des Wertes wird als "falsch" betrachtet und ich wechsle die Auswahl zurück zum ersten Element. Aber noch, 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(); 
    } 
} 

Getestet habe ich es mit Valueproperty, selectedItemProperty und selectedIndexProperty, sowie alle davon:

box.getSelectionModel().select(0); 

box.getSelectionModel().selectFirst(); 

box.getSelectionModel().selectPrevious(); 

box.setValue(0); 

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

Der einzige denken, dass der Wert arbeitet setzt sich:

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

ist die Ausnahme:

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) 

Was mache ich falsch?

+0

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

Antwort

6

In JavaFX können Sie den Inhalt eines ObservableList nicht ändern, während eine Änderung bereits ausgeführt wird. Was hier passiert ist, dass Ihre Zuhörer (von denen Sie versuchen) als Teil der box.getSelctionModel().getSelectedItems()ObservableList Änderung ausgelöst werden. Sie können also die Auswahl nicht ändern, während eine Auswahländerung durchgeführt wird.

Ihre Lösung ist sowieso ein bisschen unhandlich. Wenn Sie einen anderen Listener für das ausgewählte Element (oder den Kombinationsfeldwert) hätten, würde das Kombinationsfeld mit einer "illegalen" Auswahl vorübergehend angezeigt, selbst wenn Ihre Methode funktioniert. Wenn beispielsweise im obigen Beispiel der Benutzer versucht, "1" auszuwählen, sieht ein anderer Listener, dass die Auswahl auf den unzulässigen Wert "1" und dann wieder auf "0" geändert wird. Der Umgang mit Werten, die in diesem Listener nicht erlaubt sind, würde Ihre Programmlogik wahrscheinlich ziemlich komplex machen.

Ein besserer Ansatz, imho, besteht darin, zu verhindern, dass der Benutzer die unzulässigen Werte auswählt. Sie können mit einer Zellfabrik dies tun, die die disable Eigenschaft der Zelle setzt:

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

folgendes in einem externen Stylesheet Einschließlich wird dem Benutzer den üblichen visuellen Hinweis geben, dass die Einzelteile sind nicht wählbar:

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

ich mit dieser Antwort für das jeweilige Problem des disallowing des Benutzers einverstanden bestimmte Elemente auszuwählen. Aber ich habe kein gutes Argument gesehen, warum es nicht erlaubt ist, die Auswahl innerhalb der Auswahl zu ändern. Ich habe kürzlich einen [Fehlerbericht] (https://bugs.openjdk.java.net/browse/JDK-8133228) dafür eingereicht. Ich habe wenig Hoffnung, dass es behoben wird, aber ich sehe auch nicht, warum es nicht erlaubt sein sollte. Für den Datensatz unterstützt ReactFX [LiveList] (http://www.reactfx.org/javadoc/2.0-M4u1/org/reactfx/collection/LiveList.html) rekursive Änderungen (Änderungen, die in Change-Listenern vorgenommen wurden). –

+0

Einverstanden. (Ich habe nie gesagt, es ist eine gute Sache. Nur, dass es eine Sache ist :)) ich (Art) sehen, da der 'ListChangeListener.Change' API, dass es nicht eine gute Idee sein könnte, Änderungen an die Liste zu ermöglichen, während man eine existierende 'Change'-Datei iteriert, aber es scheint, dass Änderungen an' selectedItem' (und 'value' in einer' ComboBox') trotzdem außerhalb dieser vorgenommen werden können. Aber ich habe mich nicht wirklich in den Quellcode dafür vertieft ... –

+1

'ListChangeListener.Change' ist eine schreckliche API. Das Unterlassen von Änderungen während der Behandlung der aktuellen Änderung ist ein undichtes Implementierungsdetail, keine grundsätzliche Einschränkung. –

5

Ich weiß, der Thread ist ziemlich alt, aber ich hatte ein ähnliches Problem, und ich habe es auf andere Weise ausgearbeitet. Ich habe versucht, den ausgewählten Eintrag von ComboBox in seiner onAction Methode zu ändern, als der Gegenstand zu diesem Zeitpunkt nicht verfügbar war (zum Beispiel wegen der gegebenen Bedingung). Wie @James_D in seiner Antwort sagte, besteht das Problem darin, das Objekt zu setzen, das gerade modifiziert wird.

Fügen Sie einfach Ihren Code innerhalb der Platform.runLater() Methode:

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

In meinem Fall ist es funktionierte, hoffen, dass es auch in den anderen zu arbeiten.

Verwandte Themen