0

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?

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 1
    It'd probably be easier to do this if you extend [`TableViewSkin`](https://openjfx.io/javadoc/20/javafx.controls/javafx/scene/control/skin/TableViewSkin.html) and observe the [`VirtualFlow#position`](https://openjfx.io/javadoc/20/javafx.controls/javafx/scene/control/skin/VirtualFlow.html#positionProperty) property, reacting when it becomes `1.0` (or at least close to it). Maybe fire an event or use some other callback functionality to allow other code to do whatever needs doing when scrolled to the bottom. – Slaw Jul 28 '23 at 18:52
  • 1
    Though note you may also need to handle the scenario where there's more data to be loaded, but the currently loaded data is not enough to warrant vertical scrolling (i.e., all the data fits on screen, thus there's nothing to scroll). – Slaw Jul 28 '23 at 18:53
  • 1
    I'd consider a different UI for this, e.g. TableView which displays up to 10 items, and a Pagination control to switch pages, [like this](https://stackoverflow.com/questions/15349185/javafx-tableview-paginator/15349741#15349741). Or you could have ListView>, so the list items type will be ObservableList>, then you create a cell factory for the ListView which renders up to ten items at a time (potentially in a TableView). – jewelsea Jul 29 '23 at 00:02
  • I [edited](https://stackoverflow.com/posts/76789370/revisions) the question to remove JavaScript/HTML formatting, which does strange thing on StackOverflow when JavaScript and HTML is not involved. – jewelsea Aug 02 '23 at 21:34

1 Answers1

2

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));
            }
        }
    }
}
Sai Dandem
  • 8,229
  • 11
  • 26
  • Thanks Sai. but it looks like that it wont work if i have 35 items for example:for (int i = 1; i < 35; i++). it will load only 30. – احمد ربايعة Jul 31 '23 at 06:07
  • 1
    As mentioned in the answer, there is lot of other stuff you need to improve. All I tried is to let you know the main cause for the exception. Anyway, a simple size check of the step will fix your issue. Updated in the code-> in loadData() method. – Sai Dandem Jul 31 '23 at 06:49