I've been away from Java for some years and have started to pick it up again a couple of days ago. I'll need to create a GUI using FXML and to get some practice I'm implementing a little chat application as an exercise.
I want to create a background thread which listens on a port and sends received messages to a textArea. By what I've read, this is best done by using the 'javafx.concurrent' package.
So I came up with the following:
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class ListenOnPort extends Service<Void> {
#Override protected Task<Void> createTask() {
return new Task<Void>() {
#Override protected Void call() throws Exception {
updateMessage("Bla!");
/*
int max = 50;
for (int i = 1; i <= max; i++) {
updateProgress(i, max);
updateMessage(String.valueOf(i));
Thread.sleep(50);
}
*/
return null;
} //call()
};
} //createTask()
}// ListenOnPort
The [shortened] controller is:
public class FXMLDocumentController {
#FXML private Label status;
#FXML private ProgressBar progressBar;
ListenOnPort listenService;
#FXML void startListening(ActionEvent event) {
localPort = Integer.parseInt(listenPort.getText());
status.setText("Attempting to open local port " +localPort +" for listening.");
listenService.start();
}
#FXML void initialize() {
// assertions
listenService = new ListenOnPort();
/*>>>*/ progressBar.progressProperty().bind(listenService.progressProperty());
status.textProperty().bind(listenService.messageProperty());
}
}
Which results in:
java.lang.NullPointerException
at p2pchat.FXMLDocumentController.initialize(FXMLDocumentController.java:130)
Line 130 is the second to last line of code, marked with '/>>>/'.
Why do I get a nullPointerException? What am i doing wrong?
First, make sure you created a ProgressBar element in the .fxml file with an fx:id of progressBar.
Then in your IDE click in your project folder and press F5, it will refresh the .fxml and it will see the most updated version. Otherwise the IDE will not see the recent modifications you've made to your .fxml file and will cause the NullPointerException.
Related
I'm currently working on a small project that requires a server and a small user interface, which I built with JavaFX. I have to start the user interface via the command prompt using mvn javafx:run (as you can see, I use maven to manage my dependencies). I start the server normally via Eclipse. This also works so far and both programs start up as desired. It just seems like they can't communicate with each other. The commands from the user interface do not arrive in the "actual" program. It seems like there are two separate versions running and not a unified one.
Here is my GUI main-class i start using mvn javafx:run:
public class FX extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("../main/Scene.fxml"));
Scene scene = new Scene(root);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
And heres the corresponding controller:
public class Controller {
#FXML
private TextField senderField;
#FXML
private TextField receiverField;
#FXML
private TextField amountField;
#FXML
private TextField tfbpField;
#FXML
private TextField tflField;
#FXML
private TextField nonceField;
#FXML
private Button submitButton;
#FXML
void submitPressed(ActionEvent event) {
System.out.println("Submit Pressed");
byte[] sender = SHA3Helper.hexToDigest(senderField.getText());
byte[] receiver = SHA3Helper.hexToDigest(receiverField.getText());
double amount = Double.parseDouble(amountField.getText());
double tfbp = Double.parseDouble(tfbpField.getText());
double tfl = Double.parseDouble(tflField.getText());
Transaction transaction = new Transaction(sender, receiver, amount, 0, tfbp, tfl);
System.out.println(transaction.getAmount());
DependencyManager.getPendingTransactions().addPendingTransaction(transaction);
for(int i = 0; i<100 ; i++) {
System.out.println("Receiving test");
}
}
}
As you can see i implemented a simple for-loop to check whether "Receiving test" will also be displayed on eclipse or not - with the rusult that i can see it only in my cmd, not in Eclipse.
And here is the Tomcat that starts the actual program. Of course I start this one first.
public class Start
{
public static void main( String args[] )
{
try
{
Tomcat tomcat = new Tomcat( );
String webappDirectory = new File( "src/main/webapp" ).getAbsolutePath( );
tomcat.setPort( 8080 );
Context context = tomcat.addWebapp( "", webappDirectory );
Tomcat.addServlet( context, "blockchain", new ServletContainer( new Application( ) ) );
context.addServletMappingDecoded( "/blockchain/api/*", "blockchain" );
tomcat.start( );
tomcat.getServer( ).await( );
}
catch ( Exception e )
{
e.printStackTrace( );
}
}
}
Through dubugging I quickly found out that the problem lies in the communication between the two programs. Commands from the UserInterface do not reach the actual program.
I've written a small Spring Boot/Vaadin application that displays a simple UI to take user input and make a call to another service that takes some time to run. When the task is submitted, I'm displaying a progress dialog that shows a progress bar, a message informing the user what is going on and a close button to allow them to close the dialog when the job completes. I'm using a ListenableFuture to be notified when the task is done.
I can get the dialog to appear with status of "executing" and the progress bar doing its thing, but when the task is done (I have debug statements going to the console to let me know), it's not triggering the logic to update the status message and enable the close button. I can't figure out what I'm doing wrong.
Here's the code:
MainView1.java
#Route("rescheduleWorkOrders1")
#CssImport("./styles/shared-styles.css")
public class MainView1 extends VerticalLayout {
...
private final BackendService service;
public MainView1(BackendService service) {
this.service = service;
configureView();
addSubmitButton();
bindFields();
}
private void addSubmitButton() {
Button submit = new Button("Submit", this::submit);
add(submit);
}
private void submit(ClickEvent<?> event) {
UserData data = binder.getBean();
ListenableFuture<ResponseEntity<String>> future = service.executeTask(data);
ProgressDialog dialog = new ProgressDialog(future);
dialog.open();
}
private void configureView() {
...
}
private void bindFields() {
...
}
}
ProgressDialog.java
public class ProgressDialog extends Dialog {
private final ListenableFuture<ResponseEntity<String>> future;
private ProgressBar bar;
private Paragraph message;
private Button close;
public ProgressDialog(ListenableFuture<ResponseEntity<String>> future) {
super();
this.future = future;
configureView();
this.future.addCallback(new ListenableFutureCallback<>() {
#Override
public void onSuccess(ResponseEntity<String> result) {
message.setText("Task complete. Status: " + result.getStatusCode());
bar.setVisible(false);
close.setEnabled(true);
}
#Override
public void onFailure(Throwable ex) {
message.setText(ex.getMessage());
bar.setVisible(false);
close.setEnabled(true);
}
});
}
private void configureView() {
bar = new ProgressBar();
bar.setIndeterminate(true);
bar.setVisible(true);
message = new Paragraph("Executing task ...");
close = new Button("Close", this::close);
close.setEnabled(false);
add(bar, message, close);
}
private void close(ClickEvent<?> event) {
this.close();
}
}
BackendService.java
#Service
public class BackendService {
#Async
public ListenableFuture<ResponseEntity<String>> executeTask(UserData data) {
...
RestTemplate template = new RestTemplate();
ResponseEntity<String> response = template.postForEntity(uri, entity, String.class);
System.out.println(response);
return AsyncResult.forValue(response);
}
}
Note: I do have #EnableAsync specified in a #Configuration annotated class.
When dealing with asynchronous code in Vaadin you need to:
Use UI#access when updating the UI outside an active request. This acquires a lock on the UI, to prevent it being updated by two threads simultaneously.
Enable server push by adding the #Push annotation to your main layout or view. This allows the server to push updates to the client even if no request is active.
Without the former, you can get ConcurrentModificationExceptions in the best case, and very subtle bugs in the worst.
Without the latter, the changes will be applied (i.e. dialog closed), but the changes will only be sent to the client the next time the client sends a request. I believe this is your main issue.
More information can be found in the documentation.
I have written a piece of code for downloading a file from internet (in background service) and showing the progress of download in a popup stage. The code compiles successfully and there is no runtime error. However no download takes place and progress indicator remains indeterminate.
The code is tailored for illustrating my point. Please have a look at it and let me understand where I have gone wrong.
Thanks!
public class ExampleService extends Application {
URL url;
Stage stage;
public void start(Stage stage)
{
this.stage = stage;
stage.setTitle("Hello World!");
stage.setScene(new Scene(new StackPane(addButton()), 400, 200));
stage.show();
}
private Button addButton()
{
Button downloadButton = new Button("Download");
downloadButton.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
FileChooser fileSaver = new FileChooser();
fileSaver.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "pdf"));
File file = fileSaver.showSaveDialog(stage);
getDownloadService(file).start();
}
});
return downloadButton;
}
private Service getDownloadService(File file)
{
Service downloadService = new Service()
{
protected Task createTask()
{
return doDownload(file);
}
};
return downloadService;
}
private Task doDownload(File file)
{
Task downloadTask = new Task<Void>()
{
protected Void call() throws Exception
{
url = new URL("http://www.daoudisamir.com/references/vs_ebooks/html5_css3.pdf");
// I have used this url for this context only
org.apache.commons.io.FileUtils.copyURLToFile(url, file);
return null;
}
};
showPopup(downloadTask);
return downloadTask;
}
Popup showPopup(Task downloadTask)
{
ProgressIndicator progressIndicator = new ProgressIndicator();
progressIndicator.progressProperty().bind(downloadTask.progressProperty());
Popup progressPop = new Popup();
progressPop.getContent().add(progressIndicator);
progressPop.show(stage);
return progressPop;
// I have left out function to remove popup for simplicity
}
public static void main(String[] args)
{
launch(args);
}}
The line:
org.apache.commons.io.FileUtils.copyURLToFile(url, file);
...doesn't provide you any information about the progress of your download (there is no callback or any other indication of its progress). It just downloads something without giving you feedback.
You will have to use something else that gives you feedback on the progress.
Take a look at this questions answers for solutions with feedback (it is for Swing, but you should be able to adapt them for JavaFX): Java getting download progress
You bind the ProgressIndicator's progress property to the Task's progress property, so that changes in the latter will be reflected in the former. However you never actually update your Task's progress.
If you want the progress indicator to show something, you're going to have to call updateProgress(workDone, max) within your task's body (or elsewhere). And that might be tricky if the download logic you're using doesn't give you any progress callbacks. (You could, perhaps, spawn a thread to repeatedly check the size of the file on the filesystem and use that as your current workDone; but you'd need to know what the eventual/complete size of the file would be in order to turn this into a percentage, which may or may not be easy.)
How can I safely update the widgets on a JavaFX GUI from within a JavaFX Service. I remember when I was developing with Swing, I used to 'invoke later' and other various swing worker utilities to ensure that all updates to the UI were handled safely in the Java Event Thread. Here is an example of a simple service thread that handles datagram messages. The bit that is missing is where the datagram message is parsed and corresponding UI widgets are updated. As you can see the service class is very simplistic.
I'm not sure if I need to use simple binding properties (like message) or alternatively should I should pass widgets to the constructor of my StatusListenerService (which is probably not the best thing to do). Can someone give me a good similar example that I would work from.
public class StatusListenerService extends Service<Void> {
private final int mPortNum;
/**
*
* #param aPortNum server listen port for inbound status messages
*/
public StatusListenerService(final int aPortNum) {
this.mPortNum = aPortNum;
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
updateMessage("Running...");
try {
DatagramSocket serverSocket = new DatagramSocket(mPortNum);
// allocate space for received datagrams
byte[] bytes = new byte[512];
//message.setByteBuffer(ByteBuffer.wrap(bytes), 0);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (!isCancelled()) {
serverSocket.receive(packet);
SystemStatusMessage message = new SystemStatusMessage();
message.setByteBuffer(ByteBuffer.wrap(bytes), 0);
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
updateMessage("Cancelled");
return null;
}
};
}
}
The "low-level" approach is to use Platform.runLater(Runnable r) to update the UI. This will execute r on the FX Application Thread, and is the equivalent of Swing's SwingUtilities.invokeLater(...). So one approach is simply to call Platform.runLater(...) from inside your call() method and update the UI. As you point out, though, this essentially requires the service knowing details of the UI, which is undesirable (though there are patterns that work around this).
Task defines some properties and has corresponding updateXXX methods, such as the updateMessage(...) method you call in your example code. These methods are safe to call from any thread, and result in an update to the corresponding property to be executed on the FX Application Thread. (So, in your example, you can safely bind the text of a label to the messageProperty of the service.) As well as ensuring the updates are performed on the correct thread, these updateXXX methods also throttle the updates, so that you can essentially call them as often as you like without flooding the FX Application Thread with too many events to process: updates that occur within a single frame of the UI will be coalesced so that only the last such update (within a given frame) is visible.
You could leverage this to update the valueProperty of the task/service, if it is appropriate for your use case. So if you have some (preferably immutable) class that represents the result of parsing the packet (let's call it PacketData; but maybe it is as simple as a String), you make
public class StatusListener implements Service<PacketData> {
// ...
#Override
protected Task<PacketData> createTask() {
return new Task<PacketData>() {
// ...
#Override
public PacketData call() {
// ...
while (! isCancelled()) {
// receive packet, parse data, and wrap results:
PacketData data = new PacketData(...);
updateValue(data);
}
return null ;
}
};
}
}
Now you can do
StatusListener listener = new StatusListener();
listener.valueProperty().addListener((obs, oldValue, newValue) -> {
// update UI with newValue...
});
listener.start();
Note that the value is updated to null by the code when the service is cancelled, so with the implementation I outlined you need to make sure that your listener on the valueProperty() handles this case.
Also note that this will coalesce consecutive calls to updateValue() if they occur within the same frame rendering. So this is not an appropriate approach if you need to be sure to process every data in your handler (though typically such functionality would not need to be performed on the FX Application Thread anyway). This is a good approach if your UI is only going to need to show the "most recent state" of the background process.
SSCCE showing this technique:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class LongRunningTaskExample extends Application {
#Override
public void start(Stage primaryStage) {
CheckBox enabled = new CheckBox("Enabled");
enabled.setDisable(true);
CheckBox activated = new CheckBox("Activated");
activated.setDisable(true);
Label name = new Label();
Label value = new Label();
Label serviceStatus = new Label();
StatusService service = new StatusService();
serviceStatus.textProperty().bind(service.messageProperty());
service.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) {
enabled.setSelected(false);
activated.setSelected(false);
name.setText("");
value.setText("");
} else {
enabled.setSelected(newValue.isEnabled());
activated.setSelected(newValue.isActivated());
name.setText(newValue.getName());
value.setText("Value: "+newValue.getValue());
}
});
Button startStop = new Button();
startStop.textProperty().bind(Bindings
.when(service.runningProperty())
.then("Stop")
.otherwise("Start"));
startStop.setOnAction(e -> {
if (service.isRunning()) {
service.cancel() ;
} else {
service.restart();
}
});
VBox root = new VBox(5, serviceStatus, name, value, enabled, activated, startStop);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class StatusService extends Service<Status> {
#Override
protected Task<Status> createTask() {
return new Task<Status>() {
#Override
protected Status call() throws Exception {
Random rng = new Random();
updateMessage("Running");
while (! isCancelled()) {
// mimic sporadic data feed:
try {
Thread.sleep(rng.nextInt(2000));
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
if (isCancelled()) {
break ;
}
}
Status status = new Status("Status "+rng.nextInt(100),
rng.nextInt(100), rng.nextBoolean(), rng.nextBoolean());
updateValue(status);
}
updateMessage("Cancelled");
return null ;
}
};
}
}
private static class Status {
private final boolean enabled ;
private final boolean activated ;
private final String name ;
private final int value ;
public Status(String name, int value, boolean enabled, boolean activated) {
this.name = name ;
this.value = value ;
this.enabled = enabled ;
this.activated = activated ;
}
public boolean isEnabled() {
return enabled;
}
public boolean isActivated() {
return activated;
}
public String getName() {
return name;
}
public int getValue() {
return value;
}
}
public static void main(String[] args) {
launch(args);
}
}
I have a javafx program which I want to use to analyze a website. For the start I just want to print the sourcecode of a site into a TextArea, but before that, I write "loading website sourcecode..."
My target is to write "loading website sourcecode..." first, and then after some seconds when the parsing of the site is finished, add that sourcecode.
At the Moment when I press the button, nothing happens and after 3-5 seconds the "loading website sourcecode..." message and the sourcecode is displayed at once.
So I actually want to show strings one after another. I already googled for 2 hours and tried things with threads, invokelater, platform.runLater() and so on but nothing worked, the code is simple.
ModelView.java - Controller Class
package root;
import javafx.application.Platform;
...
public class ModelView {
#FXML public TextField UrlInput;
// This gets called when the button is pressed
public void checkUrl() throws InterruptedException
{
String url = UrlInput.getText();
LogWriter lw = new LogWriter();
lw.printMsg("loading website sourcecode...");
lw.printMsg(HTMLParser.directUrlToCode(url));
}
}
LogWriter.java
package root;
import javafx.beans.value.ObservableValue;
...
public class LogWriter extends Thread{
#FXML TextArea Log;
public LogWriter()
{
Log = (TextArea) Main.scene.lookup("#Log");
}
void printMsg(String s)
{
Log.setText(this.Log.getText()+"\n"+s);
}
}
EDIT:
There is not much to say about the HTMLParser methods, but I add that it extends Thread.
I tried changing ModelView.java to that:
ModelView.java - version 2
package root;
import javafx.application.Platform;
...
public class ModelView<V> {
#FXML public TextField UrlInput;
// This gets called when the button is pressed
public void checkUrl() throws InterruptedException
{
String url = UrlInput.getText();
LogWriter lw0 = new LogWriter();
lw0.start();
lw0.printMsg("loading website sourcecode...");
HTMLParser hp = new HTMLParser();
hp.start();
LogWriter lw1 = new LogWriter();
lw1.start();
lw1.printMsg(hp.directUrlToCode(url));
}
}
Still the same effect.
EDIT2:
This is another version I tried, in this case, "loading website sourcecode..." is not even displaying, I am going on with my tries...
ModelView.java checkUrl() - Version 3
public void checkUrl() throws InterruptedException
{
String url = UrlInput.getText();
Task<Void> task = new Task<Void>(){
#Override protected Void call() throws Exception {
if(isCancelled()) {
updateMessage("Cancelled");
LogWriter lw = new LogWriter();
lw.printMsg("loading website sourcecode...");
}
return null;
}
};
Thread t = new Thread(task);
t.start();
HTMLParser hp = new HTMLParser();
LogWriter lw1 = new LogWriter();
lw1.printMsg(hp.directUrlToCode(url));
}
First of all, manipulating an observable variable from outside of the JavaFX application thread is a bad idea. You won't be able to bind other variables to it (you'll get IllegalStateExceptions)
Second, I'd implement LogWriter like this:
// ...
public class LogWriter {
private final TextArea txtLog;
public LogWriter(TextArea txtLog) {
this.txtLog = txtLog;
}
void printMsg(final String s) {
if (!Platform.isFxApplicationThread()) {
Platform.runLater(new Runnable() {
#Override
public void run() {
printMsg(s);
}
});
} else {
txtLog.setText(txtLog.getText() + "\n" + s);
}
}
}
and use it this way:
//...
#FXML
private TextArea txtLog;
// ...
public void checkUrl() {
final String url = UrlInput.getText();
final LogWriter lw = new LogWriter(txtLog);
Task<String> task = new Task<String>() {
#Override
protected String call() {
lw.printMsg("loading website sourcecode...");
return HTMLParser.directUrlToCode(url);
}
#Override
protected void succeeded() {
super.succeeded();
lw.printMsg(getValue());
}
#Override
protected void failed() {
super.failed();
if (getException() != null) {
getException().printStackTrace();
}
lw.printMsg("failed!");
}
};
new Thread(task).start();
}
Note that HTMLParser does not need to extend Thread.