Reputation: 531
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
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