0

I hope someone can explain why this feature is gone. I have an application that has a Listview displaying logfile data. I´m reading an inputstream of lines into the ObservableList of the Listview. The vertical scrollbar of the ListViw can be "pinned" to the bottom of the ListView by mouse while its beeing filled and its like Autoscroll it stays there and i can see the live data scrolling.

I call no scrollTo or something else. I ported this application to JavaFX20 (20 because since JavaFx17 there was a bug in the scrollTo method) (no change in code) but now the vertical scrollbar does not stick/pin on the bottom anymore - so no autoscrolling anymore.. Now i tried the approach using scrollTo the last entry in the List after inserting an item but this performs very bad as huge amounts of data will be filled in. I dont want to lose the autoscroll since it performed very well in JavaFX8!!!

    try (InputStreamReader reader = (new InputStreamReader(input))) {   
LineIterator it = IOUtils.lineIterator(reader);                 
       while (it.hasNext()) {
        final String s = it.next();
        Platform.runLater(() -> {list.getItems().add(s);
        list.scrollTo(list.getItems().size()-1});}
       }}
dirrio
  • 5
  • 3
  • The controls API went through a relatively significant redesign for JavaFX 9(ish?) because the skins were becoming public API. As a result, the skin and behavior classes changed quite a bit. It's possible the feature you're talking about was simply missed while refactoring. You could file a bug report/feature request, and in the meantime see if you can implement it yourself. – Slaw Jul 20 '23 at 16:58
  • @Slaw thx for your answer. But not sure what to search for in the source. Any direction? – dirrio Jul 20 '23 at 18:18
  • I would use `scrollTo(lastIndex)` every time I appended an item to the end of the items list. I'm not sure what you mean by, "_this performs very bad as huge amounts of data will be filled in_". Intermediate items are not "loaded" when scrolling to a specific item. So, if you have a bunch of initial items but immediately scroll to the end, only a few of the items at the start of the list will be "loaded" (possibly none). After that, you _want_ the items at the end of the list to be "loaded", as those are the items being displayed. – Slaw Jul 20 '23 at 20:12
  • That exactly what i´ve done. In JavaFX 8 it works like fun (without a call to scrollTo(list.size()-1) and in 20 this scrollTo blocks the UI.The lines of data will be inserted from a InputStream by a background thread via a Task via a Platfrom.runlater call and in case of JavaFX20 as there is no autoscroll anymore i have to call scrollTo(list.size()-1) but as this will be called after every line of data it blocks the UI thread trying to update the UI. In JavaFX8 the scrollTo was not needed!It was done with some Skin or behaviour magic inside the ListView i´m now trying to rebuild in JavaFX 20. – dirrio Jul 20 '23 at 20:50
  • 2
    If the UI thread is being blocked, then my guess is you're doing something wrong when it comes to loading the data. I don't know what JavaFX 8 could have been doing specially so that the same code no longer works in JavaFX 20. Could you _create_ and post a [mre] demonstrating the issue? – Slaw Jul 20 '23 at 20:58
  • Once again the call to ScrollTo was NOT necessary in JavaFX8 . A JavaFX Task calls the following Method to fill the list and scrollTo (s.o) – dirrio Jul 21 '23 at 06:29
  • I also tried to update the scrollbar via ListChangeListener e.g when item was added call scrollTo on list view - same blocking result. The items are inserted so fast that the UI can not update. I wonder why this works in the past out of the box without calling scrollTo. – dirrio Jul 21 '23 at 06:38
  • 1
    I can't be sure without a proper [mre], but it looks like you may be calling `Platform.runLater` too much, thus overwhelming the FX thread. – Slaw Jul 21 '23 at 06:46
  • And thats the point why i want to have the behaviour like JavaFX8 :-) I know i can brake the insert down or only update every 100th insert. But why ? It worked without these hacks in 8. You know now what i mean?! I do not want to work around a feature that was there in 8. Once again in 8 i only had to insert, scrolling was done by some unknown magic inside ListView or other class involved. – dirrio Jul 21 '23 at 07:03
  • 1
    @jewelsea It looks like there actually is a significant performance degradation in `ListView` between JavaFX 8 and JavaFX 20. I tested rapidly appending items to a list view on both versions (same code). Anywhere from 1 to 10,000 items could be appended in a single "unit of work". In version 8, whether I let the list stay at the top, called `scrollTo` every time I added items, or let the auto-scrolling feature do its thing, I could append roughly 3.3 million items within 10 seconds, with an average time to append less than 1 millisecond. – Slaw Jul 21 '23 at 21:09
  • 1
    In version 20, I saw the similar performance _if I did not call_ `scrollTo`. But if I _did_ call `scrollTo`, the amount of items that could be appended in 10 seconds dropped to roughly 480 _thousand_ (~85% decrease!), with an average append time of about 120 milliseconds (~80,000% increase!). Additionally, I noticed that scrolling a significant portion "down" a large list (after everything was appended) it started to lag, whereas that lag didn't occur in JavaFX 8. – Slaw Jul 21 '23 at 21:13
  • (the items that were appended were random strings generated by a background thread at "full throttle", limited by the 10,000 capacity of a blocking queue). – Slaw Jul 21 '23 at 21:22
  • 1
    @Slaw For performance in this situation, there was a recent related fix: https://bugs.openjdk.org/browse/JDK-8293836 "Rendering performance degradation at bottom of TableView with many rows". You might want to try your test on the latest ea 21 version and see if it is still an issue. – jewelsea Jul 21 '23 at 22:42
  • @dirrio Once you fix the too-many-`runLater`-calls problem (which is technically bad even in JavaFX 8), see if using the latest JavaFX 21-ea version improves the performance of your application. [I noticed](https://stackoverflow.com/questions/76731400/javafx-8-listview-vs-javafx-20-listview-autoscrolling-behaviour?noredirect=1#comment135294613_76741695) an improvement in my tests, so unless you're planning on appending more than ~160,000 items per second, I think you should be fine. Though lag starts around 2.5 million items in the list (which is kinda odd given it's a "virtualized" control). – Slaw Jul 22 '23 at 00:46

1 Answers1

3

You may be able to achieve behavior close to what you want by using JavaFX 17.0.1 (that exact version) and explicitly calling scrollTo to scroll to the end of the list after you append to it, and yes I know that is not exactly what you are asking.

IMO this isn't really a question suited to StackOverflow. To follow up on it, I suggest, posting to the openjfx-dev mailing list or filing a bug or feature request with the JavaFX project.

Reproducible example with runLater

A minimal reproducible example for this issue, or at least functionality related to it as I understand it.

It creates a ListView and in a separate thread makes a call to add an item to the ListView every 10 milliseconds, scrolling to the end of the ListView after each item is added (performing the addition and scrolling on the JavaFX application thread using Platform.runLater).

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;

public class ListScrollApp extends Application {
    private final int MAX_LINES_TO_ADD = 1_000;
    private final int INIT_LINES = 100_000;

    @Override
    public void start(Stage stage) {
        ListView<String> listView = new ListView<>();
        for (int i = 0; i < INIT_LINES; i++) {
            listView.getItems().add(
                    "Item " + (i+1)
            );
        }

        stage.setScene(new Scene(listView));
        stage.show();

        Thread populatorThread = new Thread(
                () -> {
                    for (int i = INIT_LINES; i < INIT_LINES + MAX_LINES_TO_ADD; i++) {
                        final int curLineNum = i+1;
                        Platform.runLater(() -> {
                            listView.getItems().add(
                                    "Item " + curLineNum
                            );
                            listView.scrollTo(
                                     listView.getItems().size() - 1
                            );
                        });

                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // no action required.
                        }
                    }
                },
                "populator"
        );
        populatorThread.setDaemon(true);
        populatorThread.start();
    }

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

Behavior with JavaFX 8

Performance

If run in Java 8 it will run well (no performance issue).

Scroll Behavior

If you comment out the scrollTo call it will only exhibit the "autoscrolling" behavior referred to in the question if you drag the thumb to the end of the list immediately after starting the app.

From what I can see, with 8 it will equivalently tail the list as items are added (autoscroll) only if the scroll bar was shown (e.g. there were enough items in the list for there to be a scroll bar) and the scroll bar is positioned on the last item.

If the scroll bar is positioned at the front of the list, the list doesn't scroll.

Scrolling Bug

If the scroll bar is positioned in the middle of the list, items slowly move by about a pixel each time a new item is added (a bug that was fixed in later versions).

Behavior with JavaFX 17

Performance

If run in JavaFX 17.0.1 it will run well (no performance issue).

If you run in JavaFX 17.0.8 it will perform worse (see benchmarks below).

Scrolling behavior

If you comment out the scrollTo, the visible item won't change as items are added to the list, the view will not autoscroll to the last item in the list.

IMO, this is correct behavior, though this is just an opinion and the desired or required behavior could be debated.

Behavior with JavaFX 18

Bug

If you run the example app in JavaFX 18 it is completely broken, doesn't render right, and logs to the console:

Jul 21, 2023 5:35:41 PM javafx.scene.control.skin.VirtualFlow addTrailingCells
INFO: index exceeds maxCellCount. Check size calculations for class javafx.scene.control.skin.ListViewSkin$2

Behavior with JavaFX 20

Performance

If you run in Java 20 (or 21-ea+17), it will perform quite badly. JavaFX takes longer to render the updated ListView. Eventually, all the runLater calls in the example flood the JavaFX event queue and the app becomes quite unresponsive. For a smaller number of INIT_LINES, e.g. 1_000, it will perform fine.

Scrolling behavior

Same as Java 17.

Additional thoughts

Most of these are just my opinions, it is OK if you don't agree with them.

  • Unless you plan to file a feature request or regression bug, it doesn’t matter how it behaved in JavaFX 8 or how and why it behaved that way it did, if you are now targeting a different version. Only achieving your goal in the desired version matters.

  • Calling runLater and scrollTo for every line is suboptimal (regardless of whether it worked for your use-case in the past).

    You run the risk of overloading the JavaFX system and flooding the run queue (as you do when you try to port your code to the JavaFX 20 when the list view performs less well with large item numbers currently).

    I advise using an alternate approach which does risk flooding the run queue, even if your previous approach worked OK for you in an earlier version and you don't want to change it.

    One such approach is demonstrated in this list view based log viewer.

  • The behavior of the ListView, when items are added, was never documented, so it is an implementation detail and not a regression IMO.

    The recent JavaFX ListView implementations perform worse but have fewer bugs, I think.

    If you really want to be sure which item is shown, then use the documented API (scrollTo) to do that, rather than relying on undocumented behavior which can be subject to change without notice.

  • For performance in this situation with Java 8, there was a recent related fix:

    I tried the JavaFX 21-ea+17 version to see if it addressed the performance issue I observed with the sample app I have provided here, and it did not (at least not adequately IMO).

    • Though Slaw notes in comments:

      Ran my tests on 21-ea+24, and it definitely performs better, though not quite as well as in JavaFX 8. Managed to append ~1.9 million items over 10 seconds, with an average append time of ~32 milliseconds. And the scrolling lag seems almost completely gone.

FAQ

Eventually, all the runLater calls in the example flood the JavaFX event queue and the app becomes quite unresponsive"

In this example, and in the OP's code, that may be the cause of the unresponsiveness.

Yes, unrestrained flooding of the runLater queue is never a good idea.

But I don't think that's the root of the problem, which I believe is actually in the skin (unless the skin/flow calls runLater a lot?).

Yes, the poor ListView performance is independent of runLater, but is dependent on the number of items in the list backing the ListView.

I think that there were bugs in the rendering of list view cells in earlier JavaFX versions. The modifications to fix those introduced a performance regression sometime after JavaFX 17.0.1. There may have been some change that made the ListView performance depend in some way on the size of the backing list.

For later versions such as 20, the ListView renders OK and doesn't log to the console, but performance is worse.

Even without runLater, in recent JavaFX versions, there are issues rendering lists and tables which have large backing lists (e.g. 100,000 or more items) - which really should not be the case given the whole "Virtual Flow" concept.

I recall another recent question on performance with large backing lists which kleopatra also analyzed and confirmed. There has been some development work and bug fixes around performance of the virtual flows in recent releases, but performance still does not match JavaFX 8 performance. Indeed for the sample app, the performance of JavaFX 17.0.8 is worse than JavaFX 17.0.1 and JavaFX 20 performs worse than JavaFX 17 (benchmarks below).

AnimationTimer based example

This runs everything on the JavaFX application thread using an AnimationTimer rather than a multi-threaded solution that relies on Platform.runLater calls.

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;

public class ListAnimatedScrollApp extends Application {
    private final int MAX_LINES_TO_ADD = 1_000;
    private final int INIT_LINES = 1_000;

    @Override
    public void start(Stage stage) {
        ListView<String> listView = new ListView<>();
        for (int i = 0; i < INIT_LINES; i++) {
            listView.getItems().add(
                    "Item " + (i+1)
            );
        }

        stage.setScene(new Scene(listView));
        stage.show();

        AnimationTimer populationTimer = new AnimationTimer() {
            private int curLineNum = INIT_LINES;
            private long last = 0;
            @Override
            public void handle(long now) {
                if (curLineNum >= INIT_LINES + MAX_LINES_TO_ADD) {
                    stop();
                }

                if (last != 0) {
                    System.out.println((now - last) / 1_000_000.0);
                }
                last = now;

                listView.getItems().add(
                        "Item " + curLineNum
                );
                listView.scrollTo(listView.getItems().size() - 1);
                curLineNum++;
            }
        };

        populationTimer.start();
    }

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

Benchmark results for the AnimationTimer example

On my system:

  • With JavaFX 20.0.1 or 21-ea+17, the above app will refresh the view around every 100-120 milliseconds
  • With JavaFX 17.0.8 every ~50 milliseconds.
  • With JavaFX 17.0.1 every 16-17 milliseconds.

If, instead, the INIT_LINES value is changed to 1_000, then the view will refresh every 16-17 milliseconds for all versions (e.g. the standard 60fps frame rate cap for JavaFX apps).

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • "_Eventually, all the runLater calls in the example flood the JavaFX event queue and the app becomes quite unresponsive_" – In this example, and in the OP's code, that may be the cause of the unresponsiveness. But I don't think that's the root of the problem, which I believe is actually in the skin (unless the skin/flow calls `runLater` a lot?). When I was messing around with this, I used an `AnimationTimer` to essentially poll a queue for any data once per frame. So, I had no flooding of `runLater` calls, and yet performance still progressively degraded... _fast_. – Slaw Jul 21 '23 at 23:44
  • I guess my point is that, even if the OP fixes their too-many-calls-to-`runLater` problem, if they're using the `ListView` the way I think they are, then that may not be enough to make the application run smoothly. Only solutions I can think of is to make sure the list stays small enough (less than ~300,000; at least on my computer), or use some sort of pagination. Which may be better anyway, as a list with hundreds of thousands to millions of items is not exactly usable. – Slaw Jul 21 '23 at 23:46
  • Finally saw [this comment](https://stackoverflow.com/questions/76731400/javafx-8-listview-vs-javafx-20-listview-autoscrolling-behaviour/76741695#comment135294165_76731400). Ran my tests on `21-ea+24`, and it definitely performs better, though not quite as well as in JavaFX 8. Managed to append ~1.9 million items over 10 seconds, with an average append time of ~32 milliseconds. And the scrolling lag seems almost completely gone. – Slaw Jul 22 '23 at 00:35
  • "Calling runLater and scrollTo for every line is suboptimal (regardless of whether it worked for your use-case in the past)." No in JavaFX 8 i do not need to call scrollTol()! Just set t he bar to the bottom and the list autoscrolls and performs very well. – dirrio Jul 26 '23 at 13:19
  • @dirrio Yes I realize that and it is mentioned in my answer. It does not mean that calling `runLater` for each line is an optimal solution. – jewelsea Jul 26 '23 at 17:37
  • runLater is not the issue its as u mentioned calling scrollTo. BTW in my test i also found out that performance also depends on which garbage collector u use in the app. Shenandoa performed badly with my listview after it was filled (no scrolling test) just the filled listview took minutes to be scrollable manually. Input was a 80mb logfile . The default gc performed in this case well. – dirrio Jul 27 '23 at 19:02
  • How did things perform for you using the suggested 17.0.1 JavaFX version? – jewelsea Jul 27 '23 at 20:03
  • 1
    @jewelsea i need to give it a try. Many thanks so far for all your help and work here! @Slaw/@jewelsea btw: i filled a bug report for this but i don´t know if it was accepted - received no email confirmation or something like that. – dirrio Jul 28 '23 at 08:29
  • @jewelsea i remembered why i jumped to jfx20 as of this one: JDK-8276553 ListView scrollTo() is broken after fix for JDK-8089589 . Since jfx17 they introduced a bug causing scrollTo to be broken. It generates empty cells on heavy inserting. When i search in bugs db for bugs containing scrollTo and jfx version > 17 i see that there are many issues around. – dirrio Jul 29 '23 at 07:27
  • @dirrio yes, that’s why I suggested 17.0.1, later versions of 17 like 17.0.8 and then on into 18, 19 and 20 seemed to have performance issues around this. But 17.0.1 seemed ok when I tried it. Your mileage may vary. I think the fixes are still a work in progress. Hopefully the issues will be fully fixed in future releases. If you can’t find an appropriate solution you can raise the issue in the openjfx-dev mailing list. I then it should be possible to work around it in the meantime adopting some of the prior comments on this thread. – jewelsea Jul 29 '23 at 08:06
  • 1
    @all: Its done they evaluated my ticket and assigned it a bug : JDK-8313639 : Scrollbar could be "pinned" to the bottom of ListView in JavaFX 8 – dirrio Aug 03 '23 at 06:31