Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get ScrollPane vertical positions? #1108

Closed
PavelTurk opened this issue May 9, 2022 · 10 comments
Closed

How to get ScrollPane vertical positions? #1108

PavelTurk opened this issue May 9, 2022 · 10 comments

Comments

@PavelTurk
Copy link
Contributor

PavelTurk commented May 9, 2022

I have a TextArea and a ScrollPane:

private final StyleClassedTextArea textArea = new StyleClassedTextArea();
private final VirtualizedScrollPane scrollPane = new VirtualizedScrollPane(textArea);

And I need to know when my vertical bar is at the highest and the lowest positions. By other words when vertical scroll bar is on top or bottom. Something like this:

if (scrollPane.getVerticalBar().getPosition() == 0) { //0 -> top, 1 -> bottom
...
}

I've found several issues related to this problem but still have no solution. Could anyone say how to do it?

@Jugen
Copy link
Collaborator

Jugen commented May 23, 2022

You can try the following:

scroll.estimatedScrollYProperty().addListener
(
    // Need runLater otherwise getTotalHeightEstimate sometimes reports
    // NullPointerException when setting the text area with text ?
    y -> Platform.runLater( () ->
    {
        double currentAbsolutePos = ((Var<Double>) y).getValue();
        double totalEstimatedHeight = scroll.getTotalHeightEstimate();
        double viewPortHeight = scroll.getHeight();

        double fractionalPos = currentAbsolutePos / (totalEstimatedHeight - viewPortHeight);

        if ( fractionalPos == 0.0 )
        {
            System.out.println( "top" );
        }
        else if ( fractionalPos >= 1 )
        {
            System.out.println( "bottom" );
        }
    }
));

@PavelTurk
Copy link
Contributor Author

@Jugen Thank you for your solution. The only problem - I don't like Platform.runLater() inside listener of a property. As I understand getTotalHeightEstimate() must return double, so, it it is a bug. Maybe I should firstly open issue that it throws NullPointerException and when it is resolved, we will return to this issue?

@PavelTurk
Copy link
Contributor Author

@Jugen Getting scroll position is one of the most used features (for example, someElement.scrollTop is used very often in JS), so it will be always necessary to have a solid solution.

@Jugen
Copy link
Collaborator

Jugen commented May 23, 2022

Please try and leave out the Platform.runLater() and see how it behaves for you in your application.
The NPE only happened with me when I pasted a large amount of text into the area while it already contained a fair amount of text, so it might just be an edge case.

@PavelTurk
Copy link
Contributor Author

Your solution seems to work (I get top and bottom messages), but once I got this:

2022-05-23 17:24:42.065 [ERROR] [JavaFX Application Thread] mdm.console.gui.ConsoleApplication - java.lang.NullPointerException
	at org.fxmisc.flowless@0.6.9/org.fxmisc.flowless.Virtualized.getTotalHeightEstimate(Virtualized.java:17)
	at mdm.console.gui@1.0.0/mdm.console.gui.log.AbstractLogView.lambda$new$0(AbstractLogView.java:127)
	at reactfx@2.0-M5/org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765)
	at reactfx@2.0-M5/org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.value.ValBase.invalidate(ValBase.java:32)
	at reactfx@2.0-M5/org.reactfx.value.ValWrapper.lambda$connect$0(ValWrapper.java:21)
	at javafx.base@19-ea/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
	at javafx.base@19-ea/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
	at javafx.base@19-ea/javafx.beans.value.ObservableValueBase.fireValueChangedEvent(ObservableValueBase.java:96)
	at reactfx@2.0-M5/org.reactfx.StreamBinding.lambda$new$0(StreamBinding.java:15)
	at reactfx@2.0-M5/org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.ProperEventStream.emit(ProperEventStream.java:18)
	at reactfx@2.0-M5/org.reactfx.MappedStream.lambda$observeInputs$0(MappedStream.java:25)
	at reactfx@2.0-M5/org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.ProperEventStream.emit(ProperEventStream.java:18)
	at reactfx@2.0-M5/org.reactfx.ThenAccumulateForStream.handleEvent(ThenAccumulateForStream.java:70)
	at reactfx@2.0-M5/org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.ProperEventStream.emit(ProperEventStream.java:18)
	at reactfx@2.0-M5/org.reactfx.FilterStream.lambda$observeInputs$0(FilterStream.java:20)
	at reactfx@2.0-M5/org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.ProperEventStream.emit(ProperEventStream.java:18)
	at reactfx@2.0-M5/org.reactfx.EventStreams$27.tryEmit(EventStreams.java:599)
	at reactfx@2.0-M5/org.reactfx.EventStreams$27.lambda$observeInputs$1(EventStreams.java:593)
	at reactfx@2.0-M5/org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.ProperEventStream.emit(ProperEventStream.java:18)
	at reactfx@2.0-M5/org.reactfx.EventStreams$3.lambda$observeInputs$0(EventStreams.java:105)
	at reactfx@2.0-M5/org.reactfx.value.ChangeListenerWrapper.accept(Val.java:786)
	at reactfx@2.0-M5/org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.value.ValBase.invalidate(ValBase.java:32)
	at reactfx@2.0-M5/org.reactfx.value.Val$2.lambda$connect$0(Val.java:691)
	at reactfx@2.0-M5/org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765)
	at reactfx@2.0-M5/org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.value.ValBase.invalidate(ValBase.java:32)
	at reactfx@2.0-M5/org.reactfx.value.Val$2.lambda$connect$0(Val.java:691)
	at reactfx@2.0-M5/org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765)
	at reactfx@2.0-M5/org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.value.ValBase.invalidate(ValBase.java:32)
	at reactfx@2.0-M5/org.reactfx.value.Val$2.lambda$connect$0(Val.java:691)
	at reactfx@2.0-M5/org.reactfx.collection.InvalidationListenerWrapper.onChange(LiveList.java:413)
	at reactfx@2.0-M5/org.reactfx.collection.InvalidationListenerWrapper.onChange(LiveList.java:399)
	at reactfx@2.0-M5/org.reactfx.util.ListNotifications.lambda$takeHead$0(NotificationAccumulator.java:317)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68)
	at reactfx@2.0-M5/org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57)
	at reactfx@2.0-M5/org.reactfx.collection.ProperLiveList.fireModification(ProperLiveList.java:25)
	at reactfx@2.0-M5/org.reactfx.collection.ProperLiveList.fireElemInsertion(ProperLiveList.java:50)
	at reactfx@2.0-M5/org.reactfx.collection.MemoizationListImpl.get(MemoizationList.java:102)
	at org.fxmisc.flowless@0.6.9/org.fxmisc.flowless.SizeTracker.lengthFor(SizeTracker.java:197)
	at org.fxmisc.flowless@0.6.9/org.fxmisc.flowless.CellPositioner.getSizedCell(CellPositioner.java:219)
	at org.fxmisc.flowless@0.6.9/org.fxmisc.flowless.VirtualFlow.getCell(VirtualFlow.java:211)
	at org.fxmisc.richtext@0.10.9/org.fxmisc.richtext.GenericStyledArea.followCaret(GenericStyledArea.java:2009)
	at org.fxmisc.richtext@0.10.9/org.fxmisc.richtext.GenericStyledArea.lambda$layoutChildren$50(GenericStyledArea.java:1765)
	at reactfx@2.0-M5/org.reactfx.Suspendable.suspendWhile(Suspendable.java:49)
	at org.fxmisc.richtext@0.10.9/org.fxmisc.richtext.GenericStyledArea.layoutChildren(GenericStyledArea.java:1757)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1207)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Parent.layout(Parent.java:1214)
	at javafx.graphics@19-ea/javafx.scene.Scene.doLayoutPass(Scene.java:579)
	at javafx.graphics@19-ea/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2499)
	at javafx.graphics@19-ea/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:405)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at javafx.graphics@19-ea/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:404)
	at javafx.graphics@19-ea/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:434)
	at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)
	at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
	at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
	at javafx.graphics@19-ea/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)
	at javafx.graphics@19-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
	at javafx.graphics@19-ea/com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
	at javafx.graphics@19-ea/com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:316)
	at java.base/java.lang.Thread.run(Thread.java:832)

I need to get scroll position in a window that shows log information. Nobody can paste there text (I mean Ctrl+V) manually, but automatically added text can be rather large.

@Jugen
Copy link
Collaborator

Jugen commented May 24, 2022

Okay here is a better version that doesn't have the NPE problem:

scroll.estimatedScrollYProperty().addListener( (ob,ov,currentAbsolutePos) ->
{
    double viewPortHeight = scroll.getHeight();
    double totalEstimatedHeight = (double) scroll.totalHeightEstimateProperty().getOrSupply( () -> 1.0 );
    double fractionalPos = (double) currentAbsolutePos / (totalEstimatedHeight - viewPortHeight);
    
    if ( fractionalPos == 0.0 )
    {
        System.out.println( "top" );
    }
    else if ( fractionalPos >= 1 )
    {
        System.out.println( "bottom" );
    }
});

@PavelTurk
Copy link
Contributor Author

@Jugen Thank you very much for your help. You solution seems to be OK, but once I got fractionalPos=0.9999900008883984 when scroll bar was at the bottom(?). So, I will test and if it happens again I will report.

@PavelTurk
Copy link
Contributor Author

Yes, sometimes I get fractionalPos = 0.9999......, maybe the problem is that when I scroll to bottom this way:

    textArea.moveTo(textArea.getText().length());
    textArea.requestFollowCaret();

there is 1 or 2 pixels left to bottom. I mean, I can't scroll to real bottom using code. So, when I check if it is a bottom, I get 0.9999....

@Jugen
Copy link
Collaborator

Jugen commented May 25, 2022

So maybe try either (totalEstimatedHeight - viewPortHeight - 2) or fractionalPos >= 0.9999 ?

@PavelTurk
Copy link
Contributor Author

@Jugen Yes, thank you. This is the way I went.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants