I have embedded JavaFX's WebView in an Eclipse SWT application and need to load html content.
I need to block the execution of the current thread until the webengine has finished loading the contents. Anyone familiar with this scenario and can give some hints and/or best practices to achieve this? It seems that both SWT and JavaFX run in the same UI thread.
Thanks!
Show a modal window while the loading is not finished. You can not block the application thread because SWT/JavaFX operate on the same
A JavaFX webengine loads asynchronously per implementation. Any listener to the loadworker's stateproperty receives callback on the JavaFX thread. This thread also runs your UI, which is why you should NEVER block the JavaFX thread. In event based UI systems such as JavaFX, if you want to do some heavy work, you would do this in your own thread. Never, ever block the JavaFX thread.
The reason I'm answering is because I actually have a similar yet different situation in which I do need the load to be synchronous. My first idea was to wrap the load in my own thread and make that block, but the load operation needs to be called from the JavaFX thread. However, I figured out a way around this obstacle:
First I start a new thread in which I declare a semaphore (with 0 permits) from where I launch a Platform.runLater(...) that initiates an engine, adds a listener to the stateproperty and loads a URL, followed by trying to aquire the semaphore. This blocks, because the semaphore has no permits. In the listener's callback, I start another new thread in which I do my heavy work and from there I set some global data variable, after which I release the semaphore. This terminates my latter new thread and then informs the former new thread that the data is loaded so it may continue. This never blocks the JavaFX thread either.
When wrapping that in a function, you can emulate a synchronous load operation, but you should be cautious when using it: only call the function from outside the JavaFX thread. However, that makes perfect sense, because you should never be calling a synchronous (aka blocking) load on the JavaFX thread.
I had the same problem, and I needed to load the HTML from certain page in synchronous manner, so and I came up with the following solution:
import org.w3c.dom.Document;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronousExample extends Application {
private static String HTML = "";
private static Stage stage;
#Override
public void start(Stage primaryStage) throws Exception {
System.out.print( SynchronousExample.loadUrl("http://google.com") );
}
public static void main(String[] args){
launch(args);
}
private static void attachDocumentPropertyListener(WebView webView){
webView.getEngine().documentProperty().addListener(new ChangeListener<Document>() {
#Override public void changed(ObservableValue<? extends Document> prop, Document oldDoc, Document newDoc) {
String html = webView.getEngine().executeScript(
"document.documentElement.outerHTML"
).toString();
HTML = html;
//after page load autmatically close the window
if(stage != null){
stage.close();
}
}
});
}
public static String loadUrl(String url){
WebView webView = new WebView();
attachDocumentPropertyListener(webView);
webView.getEngine().load(url);
stage = new Stage();
Scene scene = new Scene(webView);
stage.setWidth(1);
stage.setHeight(1);
stage.setScene(scene);
stage.showAndWait();
// here HTML will be available
return HTML;
}
}
Related
I am looking for feedback on why we should use one method over the other as far as loading and displaying new FXML stages.
Most of the time, I see tutorials and such that show the loading of a stage done from a separate class. However, it can also be done within the FXML file's controller itself, and I personally see that way as being a bit cleaner and more manageable.
Consider the following Main.java class:
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
// Method 1:
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout.fxml"));
loader.setController(new LayoutController());
stage.setScene(new Scene(loader.load()));
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This seems to be the prevailing method. It creates the contoller and sets the Scene and then shows it.
However, if we change the start() method to this instead:
#Override
public void start(Stage stage) throws Exception {
LayoutController controller = new LayoutController();
controller.showStage();
}
and move the FXML loading code into the LayoutController constuctor, the result is the same:
public class LayoutController {
#FXML
private Label label;
private Stage stage = new Stage();
public LayoutController() {
// Method 2:
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout.fxml"));
loader.setController(this);
stage.setScene(new Scene(loader.load()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void showStage() {
this.stage.showAndWait();
}
}
The benefit I see here is more separation between the view and logic. Everything about the LayoutController and its associated FXML file are contained in one place.
So my question is this: what is wrong with the second approach? I am assuming it is not the standard method for a reason, but I'm not able to see any drawbacks.
Would a question like this be more suited to Code Review? I'm not really asking for opinions, as there seems to be a general "rule" that the first method be used.
In this case there is not much difference.
For larger programs the second approach is undesirable tough:
It violates the single responsibility principle:
The class is responsible for:
Creating the scene
Creating the stage
Displaying the stage (and even worse it does this in a way that may interfere with other logic (showAndWait))
It's the controller of the view and may become responsible for handling several events
Furthermore the way the class is designed in a way that prevents the responsibilities from being moved to other classes without issues.
In a larger program you likely want to create a class that manages passing data to views, arrange windows or display the view as part of a scene, ect. The second approach is ill suited for this.
Additionally it makes if harder to not repeat yourself. Unless you move the logic to a common supertype, you also need to implement the logic for showing the scene in every controller class. Repetition of the same or similar code results in code that is hard to maintain.
Note: Using a single class for loading the fxml and for use as controller is not necessarily a bad thing, but you should go with the Custom Component approach presented in Introduction to FXML.
In android there is the AsyncTask which runs a thread and then i can use the method on post execute to interact with main thread again. is there an equivalent to this in java? i am trying to run a timer on separate thread (doInBackground) and once the time is finished it will then allow me to interact with the main theard to restart a service (onPostExecute will restart service)
I'm not Android developer but I think it could be easily implemented by using a CompletableFuture on Java 8:
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CompletableFuture;
import javax.swing.SwingUtilities;
public abstract class AsyncTask <Params, Progress, Result> {
protected AsyncTask() {
}
protected abstract void onPreExecute();
protected abstract Result doInBackground(Params... params) ;
protected abstract void onProgressUpdate(Progress... progress) ;
protected abstract void onPostExecute(Result result) ;
final void publishProgress(Progress... values) {
SwingUtilities.invokeLater(() -> this.onProgressUpdate(values) );
}
final AsyncTask<Params, Progress, Result> execute(Params... params) {
// Invoke pre execute
try {
SwingUtilities.invokeAndWait( this::onPreExecute );
} catch (InvocationTargetException|InterruptedException e){
e.printStackTrace();
}
// Invoke doInBackground
CompletableFuture<Result> cf = CompletableFuture.supplyAsync( () -> doInBackground(params) );
// Invoke post execute
cf.thenAccept(this::onPostExecute);
return this;
}
}
Solution Tailored for You
In short, look at java: run a function after a specific number of seconds.
For your purpose, you don't need an AsyncTask. AsyncTask is for running something that needs the time, but you would prefer it didn't (e.g. a complex calculation or fetching data from the internet). It's for getting around the problem that you would need to wait.
What you want to do instead is to introduce a delay, or more precisely you want to schedule some action to happen after a delay. You can use a class like AsyncTask for this as well, but it's an overkill and results in more complicated code. You should instead use a class which is tailored for delayed execution, which is Timer and TimerTask:
new java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
// your code here
}
},
5000
);
Equivalent of AsyncTask in standard Java
Note that AsyncTask is connected to a UI concept (UI = user interface), because it may only be started from the UI thread. It's not for generally running something asynchronously.
Thus, the best matching equivalent of android's AsyncTask is SwingWorker in Java. Swing is Java's standard UI framework. It has a similar concept as android with a UI thread. In Swing, this thread is called the Event Dispatch Thread. Hence, the design of SwingWorker is also very similar to AsyncTask. Even the doInBackground() method has the same name in both classes.
Asynchronous Execution in General
If your requirement is not related to a UI and you only want to source some time consuming operation out so it executes asynchronously, then you need to look at executors. There is a variety of different ways to use executors for many different purposes, so this would go beyond the scope of this answer. If you are interested in further information, start with Jakob Jenkov's tutorial on ExecutorService.
I am using JavaFX 2.2 and I have a class which extends Application. Here is my code:
Class A extends Application {
public void Stage(final Stage primaryStage) { ... }
public void Start(){
launch();
}
btnLogin.setOnAction(new EventHandler<ActionEvent>() {
Platform.exit();
}
}
Class B{ }
Class C extends Application{
public void Stage(final Stage primaryStage) { ... }
public void Start(){
launch();
}
}
Actually, Class A is login screen; it will close when I successfully log in. Then the screen closed by platform.exit() function. After that I execute view button in Class B , Class C called but there are some problems.
java.lang.IllegalStateException: Application launch must not be called more than once
I just terminate the screen by using Platform.exit() function but I can't understand why it can't be closed.
Platform.exit() actually terminates whole jfx.
To keep things safe, just invoke launch() once and show/hide new windows.
Something like:
Platform.setImplicitExit(false);//make fx running in backgound.
Platform.runLater/AndWait {//make sure u create window in jfx thread
//window creation/show code here.
}
If Class B is the main screen and you need to Embed JavaFX in your application for Login Screen or any other screen, you don't need Class A and Class C to extend Application.
You can just create a new Window in Swing inside these classes (A and C) and use JFXPanel to embed JavaFX into your Swing Application. This way you can have full control on the application and you can easily open and close windows for Login or any other functionality that you want.
N.B. You should not have two class extending Application inside one app, as only one JavaFX thread is allowed per JVM.
Everytime you try to do this you will get this error
java.lang.IllegalStateException: Application launch must not be called more than once
Java FX properties is a nice API allowing the developer to create properties instead of using standard get/set method semantics. It also adds subscription to changes, properties expressions support for basic types and collections. Though properties are there in C# as a part of the language, these properties are available only inside the JavaFX container. I.e. if you try listening to changes, you'll run into an IllegalStateException saying that you need to run your listener code inside the main JavaFX thread.
So is there an alternative available for the rest of the Java world?
Update
Here is an example of an IllegalStateException. Am I misusing JavaFX API?
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("s1");
list.add("s2");
ObservableList<String> observableList = FXCollections.observableList(list);
observableList.addListener(new ListChangeListener<String>() {
#Override
public void onChanged(Change<? extends String> change) {
System.out.println("added: " + change.getAddedSubList());
}
});
observableList.add("s3");
}
}
Exception:
Exception in thread "main" java.lang.IllegalStateException
at com.sun.javafx.collections.NonIterableChange.checkState(NonIterableChange.java:101)
at com.sun.javafx.collections.NonIterableChange.getPermutation(NonIterableChange.java:81)
at javafx.collections.ListChangeListener$Change.wasPermutated(ListChangeListener.java:156)
at javafx.collections.ListChangeListener$Change.getAddedSubList(ListChangeListener.java:212)
at Test$1.onChanged(Test.java:23)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:134)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:48)
at com.sun.javafx.collections.ObservableListWrapper.callObservers(ObservableListWrapper.java:97)
at com.sun.javafx.collections.ObservableListWrapper.add(ObservableListWrapper.java:154)
at com.sun.javafx.collections.ObservableListWrapper.add(ObservableListWrapper.java:144)
at Test.main(Test.java:27)
Answer
JavaFX properties can be used independently to the rest of the JavaFX system and there is no requirement that properties on objects that do not effect an active JavaFX Scene Graph have their listener code run on the JavaFX application thread.
Explanation
Running listener code on the JavaFX application thread is only required when the change listeners or bindings effect properties of Nodes in a Scene Graph:
An application must attach nodes to a Scene, and modify nodes that are already attached to a Scene, on the JavaFX Application Thread.
You can write Java programs that use JavaFX properties and have no nodes or scene graph. You can supply properties and change listeners executable on non-JavaFX threads for objects which have no interaction with the scene graph.
Sample
The Oracle JavaFX properties and binding tutorial demonstrates use of JavaFX properties in programs which use no other JavaFX components and have no JavaFX application thread.
Additional sample based on questions in comments
Thanks, examples in this tutorial work for me. However I tried listening to the ObservableList collection and got this exception. Am I doing something wrong?
You are doing something wrong.
I tried running the sample code you added to your question on Java 8, and the error message is more explicit:
Exception in thread "main" java.lang.IllegalStateException: Invalid Change state: next() must be called before inspecting the Change.
When you add a change.next() call, your test application functions as you would expect.
The javadoc for change.next() reads:
Go to the next change. In initial state is invalid a require a call to next() before calling other methods. The first next() call will make this object represent the first change.
Working sample code:
import javafx.collections.*;
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("s1");
list.add("s2");
ObservableList<String> observableList = FXCollections.observableList(list);
observableList.addListener(new ListChangeListener<String>() {
#Override
public void onChanged(Change<? extends String> change) {
while (change.next()) {
System.out.println("added: " + change.getAddedSubList());
}
}
});
observableList.add("s3");
}
}
Output of sample code:
added: [s3]
In JavaFX, I have a Controller class that pulls control components from an FXML file and has methods that act on the component, shown with a Label here:
public class ViewController {
#FXML private Label labelStatus;
public void updateStatusLabel(String label) {
labelStatus.setText("Status: " + label);
}
}
I also have a Java Thread with a run() method, like this:
public class Server extends Thread {
public void run() {
super.run();
}
}
This Server thread handles some socket connections that I need for my particular application. After a connection has been established (in the run() method -- not shown), I need to update the Label in the FXML Controller. How would I do this?
Note: I've purposely made my code and question general so it may help others with the same problem.
You call Platform.runLater(runnable) off the JavaFX UI thread to execute a runnable that updates elements of the active JavaFX Scene Graph on the JavaFX UI thread.
Also review Concurrency in JavaFX, with the Task and Service classes and see if that is not a more appropriate solution to your particular task.
For more information, see:
Usage of JavaFX Platform.runLater and access to UI from a different thread.
Platform.runLater and Task in JavaFX
JavaFx response to SwingUtilities.invokeLater