Search code examples
listviewjavafxobservablelistjfoenix

OutBounds Exception on Filtered ViewList


im working on a small application that fetches information from a DB and displays it in a ListView , then I select elements of the list and move it to a second list, I must be able to add and remove elements between list. I have also implemented both lists with FilteredLists for search functionality. My code works, Im able to switch elements between lists, but I get an OutOfBounds exception when I try to return elements from the second list to the first one, or when I move the last element of the 1st list to the second one. Can anyone help me to figure it out?

Thanks in advance.

The exception

Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
at javafx.collections.transformation.FilteredList.get(FilteredList.java:172)
at javafx.scene.control.ListCell.updateItem(ListCell.java:459)
at javafx.scene.control.ListCell.lambda$new$160(ListCell.java:167)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.transformation.FilteredList.sourceChanged(FilteredList.java:147)
at javafx.collections.transformation.TransformationList.lambda$getListener$23(TransformationList.java:106)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at com.sun.javafx.collections.ObservableListWrapper.removeAll(ObservableListWrapper.java:185)
at com.kalypso.WCExporter.MainController.removeItemFromList(MainController.java:351)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1771)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at javafx.scene.control.Button.fire(Button.java:185)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$353(GlassViewEventHandler.java:432)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)

Lists Declaration

@FXML
private JFXListView leftList;

@FXML
private JFXTextField leftFilter;

@FXML
private JFXListView rightList;

@FXML
private JFXTextField rightFilter;
private ObservableList<String> leftData = FXCollections.observableArrayList();
private ObservableList<String> rightData = FXCollections.observableArrayList();
private FilteredList<String> leftFilteredData;
private FilteredList<String> rightFilteredData;

This method populates the first list

This list holds all the values that are going to be used

private void loadResultList(){
        if( session != null && session.isOpen()) {
            //Clear list View
            leftList.getItems().clear();

            //Get entity node
            HibernateUtil hibernateUtil = new HibernateUtil();
            //Get query results
            List resultSet = hibernateUtil.executeSQLQuery(session, selectedEntity.getValue().get("query").asText());

            //Wrap resultset into Observable list
            resultSet.forEach(result -> leftData.add(((Map)result).get("NAME").toString()));

            // 1. Wrap the ObservableList in a FilteredList (initially display all data).
            leftFilteredData = new FilteredList<>(leftData, p -> true);

            // 2. Set the filter Predicate whenever the filter changes.
            wrapListAndAddFiltering(leftFilter, leftFilteredData);

            leftList.setItems(leftFilteredData);
            leftList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        } else{
            Alert noDBCon = new Alert(Alert.AlertType.ERROR);
            noDBCon.setTitle("No Database connection");
            noDBCon.setHeaderText("No Database Connection");
            noDBCon.setContentText("Select and connect to a DB before doing any transaction.");
            noDBCon.showAndWait();
        }
    }

private void wrapListAndAddFiltering(JFXTextField filterField,FilteredList<String> filteredData) {
    filterField.textProperty().addListener((observable, oldValue, newValue) -> {
        filteredData.setPredicate(item -> {
            String filter = filterField.getText().toLowerCase();
            if (newValue == null || newValue.isEmpty()) {
                return true;
            }
            if (item.toLowerCase().contains(filter)) {
                return true;
            }
            return false;
        });
    });
}

Methods to add and remove elements

@FXML
private void addItemToList(){
    //1.- Add elements to right list
    //Wrap left elements into Observable list
    leftList.getSelectionModel().getSelectedItems().forEach(item -> rightData.add(item.toString()));

    //Wrap the ObservableList in a FilteredList (initially display all data).
    rightFilteredData = new FilteredList<>(rightData, p -> true);
    wrapListAndAddFiltering(rightFilter, rightFilteredData);
    rightList.setItems(rightFilteredData);

    //2.- Remove elements from left list
    leftData.removeAll(leftList.getSelectionModel().getSelectedItems());
    leftFilteredData = new FilteredList<>(leftData, p -> true);
    wrapListAndAddFiltering( leftFilter, leftFilteredData );
    leftList.setItems(leftFilteredData);
}

@FXML
private void removeItemFromList(){
    //1.-add elements to left list
    rightList.getSelectionModel().getSelectedItems().forEach(item -> leftData.add(item.toString()));
    leftFilteredData = new FilteredList<>(leftData, p -> true);
    wrapListAndAddFiltering(leftFilter, leftFilteredData);
    leftList.setItems(leftFilteredData);

    //1.-Remove items from right list
    ObservableList<String> temp = FXCollections.observableArrayList();
    rightList.getSelectionModel().getSelectedItems().forEach(el -> temp.add(el.toString()));
    //rightList.getSelectionModel().getSelectedItems().forEach(rightData::remove);
    rightData.removeAll(temp);
    //rightData.removeAll();
    rightFilteredData = new FilteredList<>(rightData, p -> true);
    wrapListAndAddFiltering( rightFilter, rightFilteredData );
    rightList.setItems(rightFilteredData);
}

The GUI

enter image description here


Solution

  • I created an MCVE. Comments in the code. It shows how to set up the FilteredList and how to move data from one ListView to another.

    Main

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    /**
     *
     * @author blj0011
     */
    public class JavaFXApplication239 extends Application
    {
    
        @Override
        public void start(Stage stage) throws Exception
        {
            Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
    
            Scene scene = new Scene(root);
    
            stage.setScene(scene);
            stage.show();
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args)
        {
            launch(args);
        }
    
    }
    

    Controller

    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.ResourceBundle;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.collections.transformation.FilteredList;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.ListView;
    import javafx.scene.control.SelectionMode;
    import javafx.scene.control.TextField;
    
    /**
     *
     * @author blj0011
     */
    public class FXMLDocumentController implements Initializable
    {
    
        @FXML
        private ListView lvLeft, lvRight;
    
        @FXML
        private TextField tfLeft, tfRight;
    
        ObservableList<String> leftData = FXCollections.observableArrayList();
        ObservableList<String> rightData = FXCollections.observableArrayList();
    
        FilteredList<String> filteredLeftData, filteredRightData;
    
        @Override
        public void initialize(URL url, ResourceBundle rb)
        {
            leftData.addAll(getFakeDataFromDB());//get data from DB
            //rightData.addAll(getFakeDataFromDB());//Used for testing
            filteredLeftData = new FilteredList(leftData, s -> true);
            filteredRightData = new FilteredList(rightData, s -> true);
    
            //Set filtered Lists
            tfLeft.textProperty().addListener((observable, oldValue, newValue) -> {
                if (newValue == null || newValue.length() == 0) {
                    filteredLeftData.setPredicate(null);
                }
                else {
                    filteredLeftData.setPredicate(t -> {
                        return t.toUpperCase().startsWith(newValue.toUpperCase());
                    });
                }
            });
    
            tfRight.textProperty().addListener((observable, oldValue, newValue) -> {
                if (newValue == null || newValue.length() == 0) {
                    filteredRightData.setPredicate(null);
                }
                else {
                    filteredRightData.setPredicate(t -> {
                        return t.toUpperCase().startsWith(newValue.toUpperCase());
                    });
                }
            });
    
            //Set listview items
            lvLeft.setItems(filteredLeftData);
            lvRight.setItems(filteredRightData);
    
            //Set selecton model selection mode
            lvLeft.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
            lvRight.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        }
    
        @FXML
        private void handleBtnActionMoveToLeft(ActionEvent actionEvent)
        {        
            List<String> itemsToMove = new ArrayList(lvRight.getSelectionModel().getSelectedItems());//If you don't do this "new ArrayList(..):", then  you need to first addAll then removeAll
            System.out.println("list size: " + itemsToMove.size());
            rightData.removeAll(itemsToMove);
            leftData.addAll(itemsToMove);
        }
    
        @FXML
        private void handleBtnActionMoveToRight(ActionEvent actionEvent)
        {
            List<String> itemsToMove = new ArrayList(lvLeft.getSelectionModel().getSelectedItems());//If you don't do this "new ArrayList(..):", then  you need to first addAll then removeAll
            System.out.println("list size: " + itemsToMove.size());
            leftData.removeAll(itemsToMove);
            rightData.addAll(itemsToMove);
        }
    
        List<String> getFakeDataFromDB()
        {
            List<String> tempList = new ArrayList();
            tempList.add("Hello");
            tempList.add("Hello World!");
            tempList.add("Bye");
            tempList.add("Bye World!");
            tempList.add("Been");
            tempList.add("Bad");
    
            return tempList;
        }
    
    }
    

    FXML

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.geometry.Insets?>
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.ListView?>
    <?import javafx.scene.control.TextField?>
    <?import javafx.scene.layout.HBox?>
    <?import javafx.scene.layout.VBox?>
    
    
    <HBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication239.FXMLDocumentController">
       <children>
          <ListView fx:id="lvLeft" prefHeight="200.0" prefWidth="200.0" />
          <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" HBox.hgrow="ALWAYS">
             <children>
                <Label text="Left" />
                <TextField fx:id="tfLeft" />
                <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="5.0">
                   <children>
                      <Button mnemonicParsing="false" onAction="#handleBtnActionMoveToRight" text="Move To Right" />
                      <Button mnemonicParsing="false" onAction="#handleBtnActionMoveToLeft" text="Move To Left" />
                   </children>
                   <opaqueInsets>
                      <Insets />
                   </opaqueInsets>
                </VBox>
                <Label text="Right" />
                <TextField fx:id="tfRight" />
             </children>
          </VBox>
          <ListView fx:id="lvRight" prefHeight="200.0" prefWidth="200.0" />
       </children>
    </HBox>