kirbycope
kirbycope

Reputation: 531

JavaFX Wait for Page to Load Before Continuing

I have a method that sets a new web page for the WebView's WebEngine that needs to wait until the page has finished loading to continue in the current method.

Essentially I want:

MyMethod()
{
    navigateToNewUrl();
    waitForPageToLoad(); // This is the part I am struggling with
    doSomethingElse();
}

I have tried using ChangeListener() but it will only execute after my method has finished executing. I have googled many terms that lead to more frustration, like "java non-blocking wait for boolean". Eventually I made it as far as starting new threads (to prevent Application GUI from locking up) and using CountDownTimer ( as opposed to Thread.join() ). The following code is my umpteenth attempt at getting to whatI want.

public static Boolean pageLoaded;
public volatile static CountDownLatch latch;

private static void TestMethod()
{
    // Set the CountDownLatch
    latch = new CountDownLatch(1);

    // Set the page loaded flag
    pageLoaded = false;

    // Navigate to new window
    myWebView.getEngine().load("https://www.google.com/");

    // Call a method, that starts a thread, that checks if the page has loaded
    WaitForPageLoaded();

    // Wait for the CountDownLatch, latch
    try { latch.await(); } catch (InterruptedException e) { /* TODO Auto-generated catch block */ e.printStackTrace(); }

    // Write result to console
    System.out.println("[pageLoaded] " + pageLoaded);
}

private static void WaitForPageLoaded()
{
    Thread thread = new Thread()
    {
        public void run()
        {
            // 120 iterations * 250 ms = 30 seconds
            for (int i = 0; i < 120; i++)
            {
                // Check if page has loaded
                if (pageLoaded == true)
                {
                    break;
                }
                else
                {
                    System.out.println("Waited " + (i*250) + " milliseconds, so far...");
                    try { Thread.sleep(250); }
                    catch (InterruptedException e) { /* TODO Auto-generated catch block */ e.printStackTrace(); }
                }
            }

            // Tell the calling method to move on with life
            latch.countDown();
        }
    }
}

And in another .java file (class) I have:

        // Get the WebEngine and set a listener for a state change
        WebEngine webEngine = webView.getEngine();
        webEngine.getLoadWorker().stateProperty().addListener
        (
            new ChangeListener<State>()
            {
                public void changed(@SuppressWarnings("rawtypes") ObservableValue ov, State oldState, State newState)
                {
                    if (newState == State.SUCCEEDED)
                    {
                        // Set the page loaded flag
                        OtherClassName.pageLoaded = true;
                    }
                }
            }
        );

Upvotes: 2

Views: 6763

Answers (1)

James_D
James_D

Reputation: 209408

Like almost all UI frameworks, JavaFX is based on an event-driven model. Behind the scenes there is a loop that runs (in the FX Application Thread) that processes events. You should not deal directly with this loop.

You are trying to solve the problem by recreating this loop and waiting for a condition, instead of registering handlers that respond to the appropriate events. Instead of waiting in a thread (which thread? there's no obvious way to do this) for the page to load, you just need to respond to each page completing loading and perform the next step in your test process. You can do this by registering a listener on the web engine's stateProperty.

So your structure should look like

public class MyApp extends Application {

    private WebView webView ;
    private WebEngine engine ;

    // variables representing the current state of your testing process...

    @Override
    public void start(Stage primaryStage) {

        webView = new WebView();
        engine = webView.getEngine();

        engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
            if (newState == Worker.State.SUCCEEDED) {
                // new page has loaded, process:
                testMethod();
            }
        });

        // set up UI etc...

        engine.load(...); // load first page

    }

    private void testMethod() {
        // check current state, do next appropriate thing, update variables...
    }

}

This structure achieves exactly what you are trying to do - testMethod() is invoked repeatedly, but only when each page has finished loading - but the "waiting" is managed for you by the framework.

Upvotes: 7

Related Questions