JavaFX: Updating UI elements in a Controller class from a Thread - java

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

Related

JavaFX Event handling from a different class

I looked at the stackoverflow questiions that seem similar to my problem, but none were of any help.
Here is my problem:
For a project, I am making a JavaFX app that is in pure Java without FXML. I have two classes, a controller Controller class and a class containing gui stuff GUI.
Controller has a member variable of type GUI and I am trying to assign an event handler to one of the buttons in GUI but it doesn't seem to work. It only works when I try implementing the handler inside the GUI class, but I need it to work in Comtroller.
In the constructor of Controller is as follows:
this.view = view;
view.addSimpleHandler(new SimpleHandler());
view is of type GUI and addSimpleHandler is a member function of view
SimpleHandler is an inner class of Controller that implements EventHandler and overrides the handle() function
public void addSimpleHandler(EventHandler<ActionEvent> e) {
simpleButton.setOnAction(e);
}
here is my main method and class signature for GUI
public class GUI extends Application {
//member variables for the GUI design including simpleButton
private Button simpleButton;
public static void main(String[] args) {
GUI view = new GUI();
Controller controller = new Controller(view);
Application.launch(view.getClass(), args);
}
public GUI() {
simpleButton = new Button("Simple button");
//rest of code is setting up GUI into my panes
}
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(mainPane, sceneWidth, sceneHeight);
//mainPane is a pane that contains simpleButton with a screenwidth and screenHeight
primaryStage.setTitle("Simple");
primaryStage.setScene(scene);
primaryStage.show();
}
Don't instantiate your application class manually. To understand why, see the documentation regarding the JavaFX life-cycle (emphasis mine):
The entry point for JavaFX applications is the Application class. The JavaFX runtime does the following, in order, whenever an application is launched:
Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information)
Constructs an instance of the specified Application class
Calls the init() method
Calls the start(javafx.stage.Stage) method
Waits for the application to finish, which happens when either of the following occur:
the application calls Platform.exit()
the last window has been closed and the implicitExit attribute on Platform is true
Calls the stop() method
As you can see, JavaFX itself will instantiate the application class and it's that instance which has its life-cycle methods invoked—those methods being init(), start(Stage), and stop(). However, in your code you have the following:
public static void main(String[] args) {
GUI view = new GUI(); // created your own instance
Controller controller = new Controller(view); // gave controller that instance
// Launches JavaFX which starts the life-cycle documented above
Application.launch(view.getClass(), args);
}
You create your own instance of GUI. This instance is not managed by JavaFX which means its start(Stage) method is never invoked. When you create the Controller instance and pass it your instance of GUI you're adding the EventHandler to a node which is never displayed. The window you see displayed is from the GUI instance created as part of the call to Application#launch and that GUI instance is never associated with a Controller.
For JavaFX applications you should consider the init() and start(Stage) methods as the entry points1. In other words, create the Controller in one of those aforementioned life-cycle methods. Although I suppose you could do the same in the constructor instead2. Whichever you choose, take note of which thread invoke each method, which is documented in the same place as the life-cycle. Here are the essentials:
The application class is loaded, initialized, and constructed on the JavaFX Application Thread.
The init() method is invoked by the JavaFX-Launcher thread.
The start(Stage) and stop() methods are invoked by the JavaFX Application Thread.
Remember that certain actions can only be executed on the JavaFX Application Thread.
1. You can still execute code before the call to Application#launch within the main method if needed. The only constraint is that code should not be directly related to JavaFX.
2. Your constructor is currently public and has zero parameters—keep it that way. JavaFX requires the application class to have a public, no-argument constructor in order to construct an instance via reflection.

Updating JFrame control with progress of call to another class

Using NetBeans 8, I have created a JavaFX in Swing Application. The application uses JFrame and JApplet.
The JFrame has a Label and a button that calls a processing class. The processing class calls 5 other subclasses.
I would like to have the processing class send status updates to the Label on the JFrame as the class is processing.
I have found that Sockets and ServerSockets can be used to send data from the processing class to the JFrame class but the Label only gets updated when the processing class completes.
There is too much default code to display.
Here is a summary:
The JFrame class has the typical main, init, createScene methods.
The createScene method creates a GridPane and adds the Label and Button to it, along with an EventHandler for the button.
The EventHandler calls the processing class.
The processing class simply calls 5 other methods.
I would like to do something like this:
public class processingClass {
public static void process(){
updateJFrameLabel("About to do something");
doSomething();
updateJFrameLabel("status: something Done");
doSomethingElse();
updateJFrameLabel("status: something Else Done");
doMore();
updateJFrameLabel("more done");
}
}
How can I update the JFrame label real-time as the process is running?
I understand how to use Sockets but I am not sure how to get the Label to update asynchronously.
Any help or a link to information would be helpful.

Android managing threads without using AsyncTask

I have a project I am working on where I need to improve my knowledge on Threads.
Scenario:
I have an Activity which calls a method Which use uses a thread:
Object soapResponse = soaphttp.fetchNextCatalogueRange(0, numberOfItems);
In the soaphttp class I have:
Thread soapThread = new Thread(new Runnable()
{
private Object serverResponse = new Object();
public void run()
{
// Do network stuff here
}
});
soapThread.start();
try
{
// crude synchronisation
soapThread.join();
}
The problem
Using join() blocks the UI thread.
If I dont use join() I get null pointer exceptions (data sync errors)
The Challenge:
In my activity I would like to do stuff on the UI thread while the soaphttp class is fetching data and then sync i.e tell the UI thread that the data is ready.
for example display a progress bar .. which will terminate when the data has finished being fetched.
How can I do this without having to use AsyncTask ?
At the very end of your thread's run() method, use one of the following:
the post() method of View class,
the runOnUiThread() method of Activity class
in order to refresh your UI in the UI thread.
You can use the same methods to somehow alter your UI at the start of the run() method (make same widgets disabled, show some kind of progress indicator...)

Independent JavaFX properties implementation?

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]

JavaFX/SWT WebView synchronous loadcontent()

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;
}
}

Categories

Resources