We're migrating our application from jxbrowser v6.14 to v7.1. After replacing all the jxbrowser apis to new apis referring v6-v7 doc, I get no compilation error. However, it falls into an IllegalStateException. Since this is coming quite internally, I am not able to sort out what went wrong.
import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.browser.callback.AlertCallback;
import com.teamdev.jxbrowser.browser.callback.InjectJsCallback;
import com.teamdev.jxbrowser.browser.event.ConsoleMessageReceived;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.engine.EngineOptions;
import com.teamdev.jxbrowser.js.JsObject;
import com.teamdev.jxbrowser.view.javafx.BrowserView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swt.FXCanvas;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.nio.file.Paths;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.*;
import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;
/**...*/
public final class HelloWorld extends Application {
private Browser browser;
private BrowserView view;
protected Control control;
private static Engine engine;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Platform.setImplicitExit(false);
browser = createBrowserObject();// creates the browser object
Display display = new Display();
Composite shell = new Shell(display);
shell.setSize(400, 400);
shell.setLayout(new FillLayout());
final FXCanvas canvas = new FXCanvas(shell, SWT.NONE);
view = BrowserView.newInstance(browser);
Scene scene = new Scene(new BorderPane(view), 700, 500);
canvas.setScene(scene);
browser.on(ConsoleMessageReceived.class, evt -> {
// Logger.log("Message: " + evt.consoleMessage().message());
});
browser.set(AlertCallback.class, (params, tell) -> {
// Logger.log("Alert dialog shown to the user. Info: " + params.message());
runOnUIThread(new Runnable() {
#Override
public void run() {
String title = "Information";
String message = params.message();
}
}, true);
// send OK signal to JxBrowser so that it can proceed further, else it will remain blocked
tell.ok();
});
// Inject bridge objects into JavaScript memory
browser.set(InjectJsCallback.class, params -> {
JsObject window = params.frame().executeJavaScript("window");
Object bridge = null;//getBridge();
if(bridge != null ){
window.putProperty("console", bridge);
window.putProperty("strategyBridge", bridge);
}
runOnUIThread(new Runnable() {
public void run() {
control.setData("initializing", "true");
if(true){
control.setFocus();
}
control.setData("initializing", "false");
}
}, false);
return InjectJsCallback.Response.proceed();
});
browser.navigation().loadUrl("http://abc.html");
}
/**
* Executes runnable on UIThread
* #param runnable
* #param async
*/
public static void runOnUIThread(final Runnable runnable, boolean async){
if(Thread.currentThread() == Display.getDefault().getThread()){
runnable.run();
}else{
if(async){
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
runnable.run();
}
});
}else{
Display.getDefault().syncExec(new Runnable() {
#Override
public void run() {
runnable.run();
}
});
}
}
}
public static Browser createBrowserObject() {
String browserDir = "D:/temporary/"+ System.currentTimeMillis();
EngineOptions options = EngineOptions.newBuilder(HARDWARE_ACCELERATED)
.userDataDir(Paths.get(browserDir))
.remoteDebuggingPort(9223)//?
.licenseKey(getLicenceKey())
.build();
engine = Engine.newInstance(options);
return engine.newBrowser();
}
private static String getLicenceKey(){
return "ABCD..";
}
}
The maven dependencies added are:
<dependency>
<groupId>org.eclipse.swt</groupId>
<artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
<version>4.3</version>
</dependency>
<dependency>
<groupId>com.teamdev.jxbrowser</groupId>
<artifactId>jxbrowser</artifactId>
<version>7.1</version>
</dependency>
<dependency>
<groupId>com.teamdev.jxbrowser</groupId>
<artifactId>jxbrowser-javafx</artifactId>
<version>7.1</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.teamdev.jxbrowser</groupId>
<artifactId>jxbrowser-win64</artifactId>
<version>7.1</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>javafx</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/jfxswt.jar</systemPath>
</dependency>
The exception is found when loadUrl method is invoked, here is the complete exceptions trace:
thread "JavaFX Application Thread" java.lang.IllegalStateException at com.teamdev.jxbrowser.internal.util.Preconditions.checkState(Preconditions.java:60) at com.teamdev.jxbrowser.internal.rpc.NewServiceConnection.invokeAsync(NewServiceConnection.java:261) at com.teamdev.jxbrowser.browser.internal.WindowedWidget.detach(WindowedWidget.java:60) at com.teamdev.jxbrowser.view.javafx.internal.HeavyweightWidget.detach(HeavyweightWidget.java:205) at com.teamdev.jxbrowser.view.javafx.internal.HeavyweightWidget.hide(HeavyweightWidget.java:193) at com.teamdev.jxbrowser.view.javafx.internal.HeavyweightWidget.close(HeavyweightWidget.java:112) at com.teamdev.jxbrowser.view.javafx.internal.Platform.lambda$execute$0(Platform.java:119) at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295) at java.security.AccessController.doPrivileged(Native Method)..
Unfortunately, both JavaFX/SWT and Swing/SWT combinations are not supported by JxBrowser 7. This could be a reason why you're getting these errors.
The native SWT support is under development, but not yet released.
Related
Background:
I am using Firebase for my Android App Development This app require every user upload an object from their phone to the bucket/ Firebase Storage once a day. So, I want to make the process of "object upload" as a background service with JobSchedulerAPI.
I have copied source code from Firebase and a youtube video related to Job Scheduler. But when I clicked the button, the app stopped immediately.
MainActivity.Java
package com.scheduleupload;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
final ComponentName mComponentName = new ComponentName(this, Scheduler.class);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
JobInfo info = new JobInfo.Builder(123, mComponentName)
.setPeriodic(15 * 60 * 1000)
.build();
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
int resultCode = scheduler.schedule(info);
if (resultCode == JobScheduler.RESULT_SUCCESS) {
Log.d(TAG, "Job scheduled");
} else {
Log.d(TAG, "Job scheduling failed");
}
}
});
}
}
Scheduler.Java
package com.scheduleupload;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.net.Uri;
import android.util.Log;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;
public class Scheduler extends JobService {
private static final String TAG = "ExampleJobService";
private boolean jobCancelled =false;
private StorageReference mStorage;
mStorage = FirebaseStorage.getInstance().getReference();
String myUri = "content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D693";
final Uri uri = Uri.parse(myUri);
StorageReference filepath = mStorage.child("Photos").child(uri.getLastPathSegment());
#Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG, "Job Started");
doBackgroundWork(params);
return true;
}
private void doBackgroundWork(JobParameters params) {
new Thread(new Runnable() {
#Override
public void run() {
filepath.putFile(uri);
}
}).start();
}
#Override
public boolean onStopJob(JobParameters params) {
return true;
}
}
Thx a lot for spending such a long while to read my question
crash log:
05-07 21:53:52.775 6261-6261/com.scheduleupload E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.scheduleupload, PID: 6261
java.lang.IllegalArgumentException: No such service ComponentInfo{com.scheduleupload/com.scheduleupload.Scheduler}
at android.os.Parcel.readException(Parcel.java:2008)
at android.os.Parcel.readException(Parcel.java:1950)
at android.app.job.IJobScheduler$Stub$Proxy.schedule(IJobScheduler.java:180)
at android.app.JobSchedulerImpl.schedule(JobSchedulerImpl.java:44)
at com.scheduleupload.MainActivity$1.onClick(MainActivity.java:35)
at android.view.View.performClick(View.java:6294)
at android.view.View$PerformClick.run(View.java:24770)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Have found solution, it is because the Scheduler can't access to some of the storage. Simply change to file location to somewhere it is accessible by Job Scheduler can solve the problem
I am performing some background task using this class
class Download extends Task{
protected Object call() throws Exception {
try {
updateMessage("Establishing Connection");
DownloadHelper downloadHelper = new DownloadHelper();
downloadHelper.performTask();
return null;
} catch (IOException | ParseException ex) {
logger.error(ExceptionUtils.getStackTrace(ex));
throw ex;
}
}
}
This Task in turn calls DownloadHelper to perform some task.
class DownloadHelper{
public DownloadHelper(){
}
public void performTask(){
----
----
}
}
Is there a way to update the status message of the Task API (updateMessage()) from the DownloadHelper class.?
The expedient approach is to pass a reference to the Download task as a parameter to the DownloadHelper constructor. To minimize coupling, you can instead pass a reference to your implementation of updateMessage() as a parameter of type Consumer, "an operation that accepts a single input argument and returns no result."
DownloadHelper helper = new DownloadHelper(this::updateMessage);
Your helper's implementation of performTask() can then ask the updater to accept() messages as needed.
Consumer<String> updater;
public DownloadHelper(Consumer<String> updater) {
this.updater = updater;
}
public void performTask() {
updater.accept("Helper message");
}
A related example is seen here.
import java.util.function.Consumer;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/45708923/230513
*/
public class MessageTest extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("MessageTest");
StackPane root = new StackPane();
Label label = new Label();
root.getChildren().add(label);
Scene scene = new Scene(root, 320, 120);
primaryStage.setScene(scene);
primaryStage.show();
Download task = new Download();
task.messageProperty().addListener((Observable o) -> {
label.setText(task.getMessage());
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
private static class Download extends Task<String> {
#Override
protected String call() throws Exception {
updateMessage("Establishing connection");
DownloadHelper helper = new DownloadHelper(this::updateMessage);
helper.performTask();
return "MessageTest";
}
#Override
protected void updateMessage(String message) {
super.updateMessage(message);
}
}
private static class DownloadHelper {
Consumer<String> updater;
public DownloadHelper(Consumer<String> updater) {
this.updater = updater;
}
public void performTask() {
updater.accept("Helper message");
}
}
public static void main(String[] args) {
launch(args);
}
}
I am a student and learning JavaFX since a month.
I am developing a application where I want a service to repeatedly start again after its execution of the task. For this I have come to know that 'ScheduledService' is used.
So can anybody please explain the use of scheduledservice with simple example and also how it differs from the 'Service' in JavaFX. Thanks ;)
EDIT : How can I define that this ScheduledService named DataThread should be restarted every 5 seconds ?
public class DataThread extends ScheduledService<Void>
{
#Override
public Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
for(i=0;i<10;i++)
{
System.out.println(""+i);
}
return null;
}
};
}
}
Considering you have a sound knowledge of Service class. ScheduledService is just a Service with a Scheduling functionality.
From the docs
The ScheduledService is a Service which will automatically restart itself after a successful execution, and under some conditions will restart even in case of failure
So we can say it as,
Service -> Execute One Task
ScheduledService -> Execute Same Task at regular intervals
A very simple example of Scheduled Service is the TimerService, which counts the number of times the Service Task has been called. It is scheduled to call it every 1 second
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimerServiceApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TimerService service = new TimerService();
AtomicInteger count = new AtomicInteger(0);
service.setCount(count.get());
service.setPeriod(Duration.seconds(1));
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
System.out.println("Called : " + t.getSource().getValue()
+ " time(s)");
count.set((int) t.getSource().getValue());
}
});
service.start();
}
public static void main(String[] args) {
launch();
}
private static class TimerService extends ScheduledService<Integer> {
private IntegerProperty count = new SimpleIntegerProperty();
public final void setCount(Integer value) {
count.set(value);
}
public final Integer getCount() {
return count.get();
}
public final IntegerProperty countProperty() {
return count;
}
protected Task<Integer> createTask() {
return new Task<Integer>() {
protected Integer call() {
//Adds 1 to the count
count.set(getCount() + 1);
return getCount();
}
};
}
}
}
I have a sample Hello World JavaFx. I am using Eclipse and eFxclipse plugin. My Eclipse is kepler which is Eclipse 4.3.2 version and Java servion is Jdk1.7-045.
What I try to add is very little concurrency codes, I just want to update button text in the example. Could this backend task interact with upfront UI control, for example button, scene? If not, how could I make tailored backend task, then interact with UI control?
Thanks in advance
package com.juhani.fx.exer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application{
private static final short COREPOOLSIZE=2;
private static final short MAXIMUMPOOLSIZE=2;
private static final int WORKQUEUECAPACITY=100;
private static Logger log = LogManager.getLogger(
HelloWorld.class.getName());
private ExecutorService executors = new ThreadPoolExecutor(COREPOOLSIZE,MAXIMUMPOOLSIZE,20,TimeUnit.MINUTES,new ArrayBlockingQueue<Runnable>(WORKQUEUECAPACITY));
public static void main(String[] args) {
LogMessage logMessage = new LogMessage("BEGIN",1.0,1,"HELLOWORLD");
log.trace(logMessage.toString());
launch(args);
}
#Override
public void start(final Stage primaryStage) throws InterruptedException {
primaryStage.setTitle("Hello World!");
final Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
final StackPane root = new StackPane();
root.getChildren().add(btn);
final Scene scene= new Scene(root,300,250);
primaryStage.setScene(scene);
primaryStage.show();
Task<Boolean> task = new Task<Boolean>() {
#Override
public Boolean call() {
for(int i=0;i<20;i++){
btn.setText("First row\nSecond row "+i);
primaryStage.show();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(new LogMessage("entering interruption",1.0,2,"exception").toString());
}
}
return new Boolean(true);
}
};
executors.submit(task);
}
}
This answer specially talks about the use of Platform.runLater. If you are using Task, you are better off updating the UI using the method it provides as stated in
kleopatra's answer.
For updating the UI, you have to be on the Javafx thread.
Once you are on any other thread, use Platform.runLater() to update those data back to Javafx UI. A working example can be found below
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
private static final short COREPOOLSIZE = 2;
private static final short MAXIMUMPOOLSIZE = 2;
private static final int WORKQUEUECAPACITY = 100;
private ExecutorService executors = new
ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE, 20, TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(WORKQUEUECAPACITY));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) throws InterruptedException {
primaryStage.setTitle("Hello World!");
final Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
final StackPane root = new StackPane();
root.getChildren().add(btn);
final Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
Task<Boolean> task = new Task<Boolean>() {
#Override
public Boolean call() {
final AtomicInteger i = new AtomicInteger(0);
for( ; i.get() < 20; i.incrementAndGet()) {
Platform.runLater(new Runnable() {
#Override
public void run() {
btn.setText("First row\nSecond row " + i);
}
});
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {}
}
return Boolean.valueOf(true);
}
};
executors.submit(task);
}
}
For more information you can go through the links provided here
A Task is designed to interact with the ui on the fx-application thread, to take advantage of that support you should use it as designed :-)
As a general rule, you must not access ui in the call method [*] of the Task. Instead, update one of its properties (message, progress ...) and bind that property to your ui. Sample code:
Task<Boolean> taskWithBinding = new Task<Boolean>() {
#Override
public Boolean call() {
final AtomicInteger i = new AtomicInteger(0);
for( ; i.get() < 20; i.incrementAndGet()) {
updateMessage("First row\nSecond row " + i);
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
};
btn.textProperty().bind(taskWithBinding.messageProperty());
[*] The one exception is outlined (wrap the access into an runLater) in the other answer. Doing so is technically correct - but then you are by-passing a Task's abilities and could use an arbitrary Runnable ...
I'm trying to update the GUI status based on a time consuming task. When I push on a button, I want the button to be inactive and the cursor to change until the job is completed. I've come up with this code that mostly works as needed.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class TestWait2 extends Application {
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
primaryStage.setScene(new Scene(root));
MyService myService = new MyService();
primaryStage.getScene().getRoot().cursorProperty()
.bind(Bindings.when(myService.runningProperty())
.then(Cursor.WAIT).otherwise(Cursor.DEFAULT));
Button startButton = new Button();
startButton.setText("Button");
startButton.disableProperty().bind(myService.runningProperty());
root.getChildren().add(startButton);
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
myService.start();
}
});
primaryStage.show();
}
private class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);
return null;
}
};
}
}
public static void main(String[] args) {
launch(args);
}
}
When I launch it works great the first time. The problem is that if I click on the button a second time it get an error.
Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException: Can only start a Service in the READY state. Was in state SUCCEEDED
Any thoughts on how to get around that issue?
I'm running on Java 8u5.
The problem with your code that you try to call myService.start(); when its on SUCCEEDED state(since you started it already once).
this is according to javadoc of Service start method
Starts this Service. The Service must be in the READY state to succeed in * *this call.
This method should only be called on the FX application thread.
to make your code work, you need to you call myService.restart().
since you are planning to use your service over and over you can do the following:
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//replace this line
// myService.start();
//with this
myService.restart();
}
});
Adding this to the program seemed to solve this.
myService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
myService.reset();
}
});
I also found I can add the following directly to the MyService class.
#Override
protected void succeeded() {
reset();
}
There doesn't seem to be any other way to have it go to the ready state after completing it's work.