Search code examples
javafxtableview

showing more table items when scrolling down


i have a table view that want to show 10 items in each time i scroll down to the buttom. the table view is inside a scene and i want to load it inside another scene (ex: in anchorpane) by clicking a button.

i have a main list that have all items and a sub list that have the shown items.

i tried this code:

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 700, 500);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

public class HelloController {
    @FXML
    private AnchorPane ancPersons;


    @FXML
    protected void onHelloButtonClick() throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("PersonsTable.fxml"));
        ancPersons.getChildren().add(fxmlLoader.load());
        PersonsTable personsTable=fxmlLoader.getController();
        personsTable.setData();
    }
}

PersonSetterGetter class:

public class PersonSetterGetter {
    int id;
    String name;

    public PersonSetterGetter(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

PersonsTable class:

public class PersonsTable {
    ObservableList<PersonSetterGetter> mainList = FXCollections.observableArrayList();
    ObservableList<PersonSetterGetter> subList = FXCollections.observableArrayList();
    @FXML
    public TableView<PersonSetterGetter> tableView;
    @FXML
    public TableColumn<PersonSetterGetter, Integer> colId;
    @FXML
    public TableColumn<PersonSetterGetter, String> colName;

    public void setData() {

        mainList.addAll(new PersonSetterGetter(1, "p1"),
                new PersonSetterGetter(2, "p2"),
                new PersonSetterGetter(3, "p3"),
                new PersonSetterGetter(4, "p4"),
                new PersonSetterGetter(5, "p5"),
                new PersonSetterGetter(6, "p6"),
                new PersonSetterGetter(7, "p7"),
                new PersonSetterGetter(8, "p8"),
                new PersonSetterGetter(9, "p9"),
                new PersonSetterGetter(10, "p10"),
                new PersonSetterGetter(11, "p11"),
                new PersonSetterGetter(12, "p12"),
                new PersonSetterGetter(13, "p13"),
                new PersonSetterGetter(14, "p14"),
                new PersonSetterGetter(15, "p15"),
                new PersonSetterGetter(16, "p16"),
                new PersonSetterGetter(17, "p17"),
                new PersonSetterGetter(18, "p18"),
                new PersonSetterGetter(19, "p19"),
                new PersonSetterGetter(20, "p20"),
                new PersonSetterGetter(21, "p21"),
                new PersonSetterGetter(22, "p22"),
                new PersonSetterGetter(23, "p23"),
                new PersonSetterGetter(24, "p24"),
                new PersonSetterGetter(25, "p25"),
                new PersonSetterGetter(26, "p26"),
                new PersonSetterGetter(27, "p27"),
                new PersonSetterGetter(28, "p28"),
                new PersonSetterGetter(29, "p29"),
                new PersonSetterGetter(30, "p30"));
        tableView.setItems(subList);
        final int[] start = {0};
        final int[] step = {10};
        ScrollBar tvScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
        tvScrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
            if ((Double) newValue == 1.0) {
                if (step[0] <= mainList.size()) {
                    subList.addAll(mainList.subList(start[0], step[0]));
                    start[0] = step[0];
                    step[0] += 10;
                    tableView.scrollTo(start[0]);
                }
            }
        });
    }

}

hello-view.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="500.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demo.HelloController">
   <children>
       <Button layoutX="14.0" layoutY="14.0" onAction="#onHelloButtonClick" text="show persons" />
      <AnchorPane fx:id="ancPersons" layoutX="100.0" layoutY="120.0" prefHeight="244.0" prefWidth="440.0" />
   </children>
</AnchorPane>

PersonsTable.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demo.PersonsTable">
   <children>
      <TableView fx:id="tableView" prefHeight="237.0" prefWidth="309.0">
        <columns>
          <TableColumn fx:id="colId" prefWidth="75.0" text="id" />
          <TableColumn fx:id="colName" prefWidth="233.60001525878909" text="name" />
        </columns>
      </TableView>
   </children>
</AnchorPane>

and i expected to have 10 rows shown by each scroll. but when i run i have an error says:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1857)
    at javafx.fxml/javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1724)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Node.fireEvent(Node.java:8792)
    at javafx.controls/javafx.scene.control.Button.fire(Button.java:203)
    at javafx.controls/com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:208)
    at javafx.controls/com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3897)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1878)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2623)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:557)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:943)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:77)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at javafx.base/com.sun.javafx.reflect.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml/com.sun.javafx.fxml.MethodHelper.invoke(MethodHelper.java:84)
    at javafx.fxml/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1854)
    ... 46 more
Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.control.ScrollBar.valueProperty()" because "tableViewScrollBar" is null
    at com.example.demo/com.example.demo.PersonsTable.setData(PersonsTable.java:60)
    at com.example.demo/com.example.demo.HelloController.onHelloButtonClick(HelloController.java:19)
    ... 58 more`

i got the part that gets the scrollbar from this question and says in a comment:

    // Be sure to put the listener after the stage is shown or the application will throw a NullPointerException

i'm sure that my stage is shown so what is the problem?


Solution

  • The reason for the exception is that, you are trying to set the listener to the scrollBar which is not yet rendered. You need to wait till the tableView skin is rendered. After that you can access the ScrollBar and can set the listener.

    Having said that, I can see a couple of more issues in your code:

    • You are not yet setting the cell value factories to the columns. So even if you fix the above scroll bar issue, the data will be not shown. You can include the cell value factories logic in the fxml initialize method.

    • Even if you fix the cell value factories the data will not be shown, because you are loading the data only when the scrollBar value changes. You need to first load some data. For that you can move the logic to a method and call it from listener and also when setData() is first called.

    • And this one is more like a suggestion: I would prefer to include observable properties in the person object, so that you can correctly set the cell value factories.

    Combining all the above, below is the code of your person object and controller. I just slightly modified other logic, but I believe there can be a more better way.

    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    public class PersonSetterGetter {
        private IntegerProperty id = new SimpleIntegerProperty();
        private StringProperty name=new SimpleStringProperty();
    
        public PersonSetterGetter(int id, String name) {
            this.id.set(id);
            this.name.set(name);
        }
    
        public int getId() {
            return id.get();
        }
    
        public IntegerProperty idProperty() {
            return id;
        }
    
        public void setId(final int id) {
            this.id.set(id);
        }
    
        public String getName() {
            return name.get();
        }
    
        public StringProperty nameProperty() {
            return name;
        }
    
        public void setName(final String name) {
            this.name.set(name);
        }
    }
    

    Controller:

    import javafx.application.Platform;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.fxml.FXML;
    import javafx.scene.control.ScrollBar;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    
    public class PersonsTable {
        private ObservableList<PersonSetterGetter> mainList = FXCollections.observableArrayList();
        private ObservableList<PersonSetterGetter> subList = FXCollections.observableArrayList();
        @FXML
        public TableView<PersonSetterGetter> tableView;
        @FXML
        public TableColumn<PersonSetterGetter, Number> colId;
        @FXML
        public TableColumn<PersonSetterGetter, String> colName;
    
        private int start = 0;
        private int step = 10;
    
        @FXML
        public void initialize() {
            colId.setCellValueFactory(p -> p.getValue().idProperty());
            colName.setCellValueFactory(p -> p.getValue().nameProperty());
        }
    
        public void setData() {
            for (int i = 1; i < 31; i++) {
                mainList.add(new PersonSetterGetter(i, "p" + i));
            }
            tableView.setItems(subList);
            tableView.skinProperty().addListener((obs, old, skin) -> {
                if (skin != null) {
                    ScrollBar tvScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
                    tvScrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
                        if (newValue.doubleValue() == 1.0) {
                            loadData(true);
                        }
                    });
                }
            });
            // First load some data
            loadData(false);
        }
    
        private void loadData(boolean reachedEnd) {
            if (step <= mainList.size()) {
                subList.addAll(mainList.subList(start, step));
                start = step;
                step += 10;
                if (step > mainList.size()) {
                    step = mainList.size();
                }
                if (reachedEnd) {
                    // I don't think this line is required.
                    Platform.runLater(() -> tableView.scrollTo(start));
                }
            }
        }
    }