I have a problem how to correctly update Java FX UI. I'm moving from Swing to FX for the first time and also ExecutorService. Problem is I need to show gif file and update progress bar during code execution but instead I get white screen with mouse loading icon. The gif and progressbar are eventually showed but after finishing this for cycle: for (int i = 0; i < futures.size(); i++)
I thought that running tasks in ExecutorService is in separate thread and using Platform.runLater for progressbar will separate UI from long running code in ExecutorService. Can you give me some explanation what is going on please?
Controller.java:
public void initialize() {
ivGif.setImage(new Image(Main.class.getResourceAsStream("/test/loading.gif")));
}
public void synchronizeFiles() {
Platform.runLater(() -> pbDownloading.setVisible(true));
ExecutorService pool = Executors.newFixedThreadPool(1);
ArrayList<Future<Boolean>> futures = new ArrayList<>();
File localFile = new File(simplified code here);
Future<Boolean> f = pool.submit(new DownloadTask(new URL(simplified code here), localFile));
futures.add(f);
for (int i = 0; i < futures.size(); i++) {
final int position = i;
Platform.runLater(() -> {
pbDownloading.setProgress(position / (double) futures.size());
});
if (!futures.get(i).get(600, TimeUnit.SECONDS)) {
System.out.println("ShutdownNow");
pool.shutdownNow();
}
}
}
DownloadTask.java:
public class DownloadTask implements Callable<Boolean> {
protected Category cat = Category.getInstance(DownloadTask.class.getName());
private URL fileURL;
private File toPath;
public DownloadTask(URL fileURL, File toPath) {
this.fileURL = fileURL;
this.toPath = toPath;
}
private void downloadFile(URL fileURL, File toPath) throws IOException {
ReadableByteChannel readableByteChannel = Channels.newChannel(fileURL.openStream());
if (!toPath.getParentFile().exists()) {
if (!toPath.getParentFile().mkdirs()) throw new IOException("Unable to create parent dirs for file: "+toPath.getAbsolutePath());
}
FileOutputStream fileOutputStream = new FileOutputStream(toPath);
fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
URLConnection urlConnection = fileURL.openConnection();
if (!toPath.setLastModified(urlConnection.getLastModified())) cat.error("Unable to write modified time stamp for file: "+toPath.getAbsolutePath());
}
#Override
public Boolean call() {
try {
downloadFile(fileURL, toPath);
} catch (IOException e){
System.out.println(e);
cat.error(e, e);
return false;
}
return true;
}
}
Thank you
EDIT: I didn't realize that the main class is running from FX Application Thread. So I had reverse threads and just applied the new thread (() -> {...}).Start(); in the main class in the start method and it's ok.
Related
I'm looking for a suitable way to display the processing time of parallel running Tasks on a separate stage.
I want to execute different tasks combined in an ArrayList - one after the other. For this case I'm using a ThreadPool. After each executed list, I want to wait until all tasks are completed. Only when the tasks have reached the status „succeeded“, I want to do something in the MainThread. After that I want to execute another list of tasks and visualize them on a separate stage as well. The following figure shows the desired processing sequence (depending on the source code listed below):
enter image description here
For this purpose I have written the classes MyLoader. The MyLoader-class contains a separate Task and binds the progress-properties with a Label and a Progressbar in the constructor:
public class MyLoader {
public Label label = null;
public ProgressBar progressBar = null;
public VBox vbox;
public Task<Integer> task = null;
public String name;
public MyLoader(String name) {
this.name = name;
this.label = new Label();
this.progressBar = new ProgressBar();
this.vbox = new VBox(2);
//UI-Layout for Progress
this.vbox.getChildren().addAll(this.label, this.progressBar);
HBox.setHgrow(this.vbox, Priority.ALWAYS);
this.vbox.setAlignment(Pos.CENTER);
this.progressBar.prefWidthProperty().bind(this.vbox.widthProperty().subtract(20));
//Counter-Size
Random r = new Random();
int max = r.nextInt((100 - 50) + 1) + 50;
//Task
this.task = new Task<Integer>() {
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
protected void succeeded() {
updateMessage(name+" succeeded!");
System.out.println(name+" succeeded!");
super.succeeded();
}
};
//Bind Properties
this.label.textProperty().bind(task.messageProperty());
this.progressBar.progressProperty().bind(task.progressProperty());
}
}
In the MainClass, I combine several MyLoader instances in an ArrayList and run them with an ExecutorService. To create the new stage I use the static method progressStage(List). Each Stage is shown before the ExecutorService executes the respective tasks. Here's the MainClass code:
public class MainClass extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//Thread-Pool
ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//FirstLoaders
List<MyLoader> firstLoaders = new ArrayList<MyLoader>();
firstLoaders.add(new MyLoader("A"));
firstLoaders.add(new MyLoader("B"));
//Show 1. Stage
Stage firstStage = progressStage(firstLoaders);
firstStage.show();
//Execute firstLoaders
for(MyLoader l1 : firstLoaders)
es.execute(l1.task);
//1) TODO: How can I wait for the completion of the first loaders and start the second loaders?
//... doSomething1() ...
//SecondLoaders
List<MyLoader> secondLoaders = new ArrayList<MyLoader>();
secondLoaders.add(new MyLoader("C"));
secondLoaders.add(new MyLoader("D"));
secondLoaders.add(new MyLoader("E"));
//Show 2. Stage
Stage secondStage = progressStage(secondLoaders);
secondStage.setX(firstStage.getX());
secondStage.setY(firstStage.getY()+firstStage.getHeight());
secondStage.show();
for(MyLoader l2 : secondLoaders)
es.execute(l2.task);
//2) TODO How can I wait for the completion of the second loaders and start the primaryStage?
//... doSomething2() ...
Scene scene = new Scene(new StackPane(), 450, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
static Stage progressStage(List<MyLoader> loaderTasks) {
int count = loaderTasks.size();
VBox loadBox = new VBox(count);
for(int i=0; i<count; i++)
loadBox.getChildren().add(loaderTasks.get(i).vbox);
HBox.setHgrow(loadBox, Priority.ALWAYS);
loadBox.setAlignment(Pos.CENTER);
Stage dialogStage = new Stage();
dialogStage.setScene(new Scene(loadBox, 300, count * 50));
dialogStage.setAlwaysOnTop(true);
return dialogStage;
}
}
The program is executable so far - but the calculation sequence appears completely parallel.
What I tasted:
1) So far I have managed to get the process to be read and stopped using the get() method. But then the stage is only displayed when the threads in the background have finished their work.
//1) TODO: „doSomeThing1()“
List<Integer> integers = new ArrayList<Integer>();
for(MyLoader ml : firstLoaders)
integers.add(ml.task.get());
System.out.println(integers.toString());
2) Also with the Task.setOnSucceded() method I could not get any useful results yet. Mainly because the stage is only shown after the computing. The problem is that I am not able to query the status of all tasks at a defined time.
3) The application of a CountDownLatch has also achieved a comparable result.
4) In addition, the shutdown() method of the ExecutorService causes a termination. This solution is therefore also not suitable.
//1) TODO: „doSomeThing1()“
es.shutdown();
try {
es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
//SecondLoaders
//...
}catch (InterruptedException e) {
e.printStackTrace();
}
Is there a suitable approach for such intentions? So far I have not come to any useful result.
When a task is done, just update a counter and check, if the task currently completed was the last one in the current set.
The following code demonstrates this. (There are certainly things in the code that could be improved though, but the concept should get clear.)
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
private VBox taskViewContainer;
ExecutorService executor;
int tasksDone;
private void runTasks(List<MyTask> tasks, IntegerProperty index) {
if (tasks.isEmpty()) {
index.set(index.get()+1);
} else {
int taskCount = tasks.size();
tasksDone = 0;
for (MyTask task : tasks) {
taskViewContainer.getChildren().add(new TaskView(task));
task.setOnSucceeded(evt -> {
++tasksDone;
if (tasksDone == taskCount) {
// proceed to next task set after all tasks are done
index.set(index.get() + 1);
}
});
executor.submit(task);
}
}
}
#Override
public void init() throws Exception {
// create executor during initialisation
executor = Executors.newFixedThreadPool(4);
}
#Override
public void stop() throws Exception {
// shutdown executor when javafx shuts down
executor.shutdownNow();
}
#Override
public void start(Stage primaryStage) throws Exception {
taskViewContainer = new VBox();
Label text = new Label();
// generate random set of tasks
Random random = new Random();
List<List<MyTask>> taskLists = new ArrayList<>();
for (int i = 0; i < 20; ++i) {
int count = random.nextInt(10) + 1;
List<MyTask> tasks = new ArrayList<>(count);
taskLists.add(tasks);
for (int j = 0; j < count; ++j) {
tasks.add(new MyTask(String.format("%d.%c", i+1, (char) ('A'+j)), random.nextInt((100 - 50) + 1) + 50));
}
}
// property holding the current index in the task set list
IntegerProperty index = new SimpleIntegerProperty(-1);
index.addListener((o, oldValue, newValue) -> {
// gui update for change of task set
taskViewContainer.getChildren().clear();
text.setText(String.format("Task set %d / %d done", newValue, taskLists.size()));
int i = newValue.intValue();
if (i < taskLists.size()) {
// launch next set of tasks
runTasks(taskLists.get(i), index);
}
});
// start initial tasks
index.set(0);
text.setMinWidth(200);
text.setMaxWidth(Double.MAX_VALUE);
HBox root = new HBox(text, taskViewContainer);
root.setMinHeight(10 * 50);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
class TaskView extends HBox {
TaskView(MyTask task) {
setPrefSize(400, 50);
ProgressBar progress = new ProgressBar();
progress.progressProperty().bind(task.progressProperty());
Label label = new Label(task.getName());
Label message = new Label();
message.textProperty().bind(task.messageProperty());
getChildren().addAll(progress, new VBox(label, message));
}
}
class MyTask extends Task<Integer> {
private final int max;
private final String name;
public String getName() {
return name;
}
public MyTask(String name, int max) {
this.max = max;
this.name = name;
}
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
}
The above code does not take the possibility of canceling tasks/tasks terminating with an exception.
I want to run a task in background updating intermediate results in the view.I am trying to implement MVC JavaFX application. The task is defined in the Model.
I want to send to the main threath partial results in order to show them in the View.
I use updateValue() to do so. Also, I define object property and a listener in the controller.
My problem: The method changed() from the listener, is not being fired each time that updateValue() is executed in the Task. Why? How can I force it to do this?.
I have not found much complex examples.
What I have so far:
Model.cpp
ComplexObject _complexO;
public Task<ComplexObject> getModelTask() {
return new Task<ComplexObject>() {
#Override
protected ComplexObject call() throws Exception {
int numberOfFiles = 0;
boolean filesToRead = true;
while (filesToRead){
// ....
_complexO = new ComplexObject();
try{
//..
if(f.exists()){
_complexO.initialize();
numberOfScans ++;
}
else{
_complexO.initializeToNull();
}
String stringNumber = Converter.toString(numberOfFiles);
updateMessage(stringNumber);
updateValue(_complexO );
} catch(Exception ex){
ex.printStackTrace();
_complexO = null;
return _complexO;
}
filesToRead = areThereFilesToRead();
}
return _complexO;
}
};
}
Controller.cpp
...
Task< ComplexObject> task = _model.getModelTask();
_AJavaFXTextField.textProperty().bind(task.messageProperty());
_AJavaFXTextField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue observable, String oldValue, String newValue) {
System.out.println("Success with messageProperty!!" + newValue);
}
});
SimpleObjectProperty<ComplexObject> complexObjectProperty = new SimpleObjectProperty<>();
complexObjectProperty.bind(task.valueProperty());
complexObjectProperty.addListener(new ChangeListener<ComplexObject>(){
#Override
public void changed(ObservableValue<? extends ComplexObject> observable, ComplexObject oldValue, ComplexObject newValue) {
if(newValue.data == null ) {
System.out.println("value is new!!! " + scansNumber);
}
else if(newValue.isValid()){
System.out.println("I want to plot newValue data here");
}
}
});
Thread th= new Thread(task);
System.out.println("call TASK");
th.start();
}
My questions/conclusions here:
How to force to all times that I execute in the task updateValue() to really execute the listener - so execute the code where I want to plot data.
Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.
Why I find that the code of the listener is fired more times when debug mode than normal execution?
Any recomendation of good sources about this topic (from a complex point of view) would be great.
I am looking from something in JavaFX to replace SwingWorker.
What I really whant at the end: To return a list of complexObjects from the task, and ideally, updateValue() would send the objects one per one (partial results)
I have followed:
https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html
Thanks very much for any contribuction
Task only guaranties that a value passes to updateValue or a value passed later will be set to the value property. This is done to increase performance of the application thread by limiting the number of changes the listeners are notified of.
Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.
As described above there simply is no guaranty about the number of updates.
Why I find that the code of the listener is fired more times when debug mode than normal execution?
In general debugging makes your program smaller. The smaller the update frequency from the thread of your Task, the smaller the number of updates between the times the Task class updates the properties and the smaller the number of skipped. (The updates are probably executed every frame or every few frames.) If you even use a break-point/stepper in the task, you probably make the Task extremely slow while the application thread runs at normal speed.
It should be easy enough to implement publish on your own by using a List to buffer the updates
public abstract class JavaFXWorker<S, T> extends Task<S> {
private List<T> chunks = new ArrayList<>();
private final Object lock = new Object();
private boolean chunkUpdating = false;
protected final void publish(T... results) {
synchronized (lock) {
chunks.addAll(Arrays.asList(results));
if (!chunkUpdating) {
chunkUpdating = true;
Platform.runLater(() -> {
List<T> cs;
synchronized (lock) {
cs = chunks;
// create new list to not unnecessary lock worker thread
chunks = new ArrayList<>();
chunkUpdating = false;
}
try {
process(cs);
} catch (RuntimeException ex) {
}
});
}
}
}
protected void process(List<T> chunks) {
}
}
Sample use
#Override
public void start(Stage primaryStage) {
ListView<Integer> lv = new ListView<>();
Button btn = new Button("Run");
btn.setOnAction((ActionEvent event) -> {
JavaFXWorker<Void, Integer> worker = new JavaFXWorker<Void, Integer>() {
#Override
protected Void call() throws Exception {
final int maxCount = 100;
Random random = new Random();
int breakIndex = random.nextInt(maxCount-1)+1;
for (int i = 0; i < breakIndex; i++) {
publish(i);
}
// some break simulating a part long part of the task with no updates
Thread.sleep(3000);
for (int i = breakIndex; i <= maxCount; i++) {
publish(i);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
lv.getItems().addAll(chunks);
}
};
new Thread(worker).start();
});
Scene scene = new Scene(new VBox(btn, lv));
primaryStage.setScene(scene);
primaryStage.show();
}
My code currently reads my Gmail inbox via IMAP (imaps) and javamail, and once it finds an email with zip/xap attachment, it displays a stage (window) asking whether to download the file, yes or no.
I want the stage to close once I make a selection, and then return to the place within the loop from which the call came. My problem arises because you cannot launch an application more than once, so I read here that I should write Platform.setImplicitExit(false); in the start method, and then use primartyStage.hide() (?) and then something like Platform.runLater(() -> primaryStage.show()); when I need to display the stage again later.
The problem occuring now is that the flow of command begins in Mail.java's doit() method which loops through my inbox, and launch(args) occurs within a for loop within the method. This means launch(args) then calls start to set the scene, and show the stage. Since there is a Controller.java and fxml associated, the Controller class has an event handler for the stage's buttons which "intercept" the flow once start has shown the stage. Therefore when I click Yes or No it hides the stage but then just hangs there. As if it can't return to the start method to continue the loop from where launch(args) occurred. How do I properly hide/show the stage whenever necessary, allowing the loop to continue whether yes or no was clicked.
Here is the code for Mail.java and Controller.java. Thanks a lot!
Mail.java
[Other variables set here]
public static int launchCount = 0;#FXML public Text subjectHolder;
public static ReceiveMailImap obj = new ReceiveMailImap();
public static void main(String[] args) throws IOException, MessagingException {
ReceiveMailImap.doit();
}
#Override
public void start(Stage primaryStage) throws Exception {
loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
root = loader.load();
controller = loader.getController();
controller.setPrimaryStage(primaryStage);
scene = new Scene(root, 450, 250);
controller.setPrimaryScene(scene);
scene.getStylesheets().add("styleMain.css");
Platform.setImplicitExit(false);
primaryStage.setTitle("Download this file?");
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void doit() throws MessagingException, IOException {
Folder inbox = null;
Store store = null;
try {
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props, null);
store = session.getStore("imaps");
store.connect("imap.gmail.com", "myAccount#gmail.com", "Password");
inbox = store.getFolder("Inbox");
inbox.open(Folder.READ_WRITE);
Message[] messages = inbox.getMessages();
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(UIDFolder.FetchProfileItem.FLAGS);
fp.add(UIDFolder.FetchProfileItem.CONTENT_INFO);
fp.add("X-mailer");
inbox.fetch(messages, fp);
int doc = 0;
int maxDocs = 400;
for (int i = messages.length - 1; i >= 0; i--) {
Message message = messages[i];
if (doc < maxDocs) {
doc++;
message.getSubject();
if (!hasAttachments(message)) {
continue;
}
String from = "Sender Unknown";
if (message.getReplyTo().length >= 1) {
from = message.getReplyTo()[0].toString();
} else if (message.getFrom().length >= 1) {
from = message.getFrom()[0].toString();
}
subject = message.getSubject();
if (from.contains("myAccount#gmail.com")) {
saveAttachment(message.getContent());
message.setFlag(Flags.Flag.SEEN, true);
}
}
}
} finally {
if (inbox != null) {
inbox.close(true);
}
if (store != null) {
store.close();
}
}
}
public static boolean hasAttachments(Message msg) throws MessagingException, IOException {
if (msg.isMimeType("multipart/mixed")) {
Multipart mp = (Multipart) msg.getContent();
if (mp.getCount() > 1) return true;
}
return false;
}
public static void saveAttachment(Object content)
throws IOException, MessagingException {
out = null; in = null;
try {
if (content instanceof Multipart) {
Multipart multi = ((Multipart) content);
parts = multi.getCount();
for (int j = 0; j < parts; ++j) {
part = (MimeBodyPart) multi.getBodyPart(j);
if (part.getContent() instanceof Multipart) {
// part-within-a-part, do some recursion...
saveAttachment(part.getContent());
} else {
int allow = 0;
if (part.isMimeType("application/x-silverlight-app")) {
extension = "xap";
allow = 1;
} else {
extension = "zip";
allow = 1;
}
if (allow == 1) {
if (launchCount == 0) {
launch(args);
launchCount++;
} else {
Platform.runLater(() -> primaryStage.show());
}
} else {
continue;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if ( in != null) { in .close();
}
if (out != null) {
out.flush();
out.close();
}
}
}
public static File createFolder(String subject) {
JFileChooser fr = new JFileChooser();
FileSystemView myDocs = fr.getFileSystemView();
String myDocuments = myDocs.getDefaultDirectory().toString();
dir = new File(myDocuments + "\\" + subject);
savePathNoExtension = dir.toString();
dir.mkdir();
System.out.println("Just created: " + dir);
return dir;
}
}
Controller.java
public class Controller implements Initializable {
#FXML
private Text subjectHolder;
public Button yesButton, noButton;
public ReceiveMailImap subject;
#Override
public void initialize(URL url, ResourceBundle rb) {
subject= new ReceiveMailImap();
subjectHolder.setText(subject.returnSubject());
}
public Stage primaryStage;
public Scene scene;
#FXML
ComboBox<String> fieldCombo;
public void setPrimaryStage(Stage stage) {
this.primaryStage = stage;
}
public void setPrimaryScene(Scene scene) {
this.scene = scene;
}
public String buttonPressed(ActionEvent e) throws IOException, MessagingException {
Object source = e.getSource();
if(source==yesButton){
System.out.println("How to tell Mail.java that user clicked Yes?");
return "POSITIVE";}
else{subject.dlOrNot("no");
System.out.println("How to tell Mail.java that user clicked No?");
primaryStage.hide();
return "NEGATIVE";}
}
}
There are a lot of issues with the code you have posted, but let me just try to address the ones you ask about.
The reason the code hangs is that Application.launch(...)
does not return until the application has exited
In general, you've kind of misunderstood the entire lifecycle of a JavaFX application here. You should think of the start(...) method as the equivalent of the main(...) method in a "traditional" Java application. The only thing to be aware of is that start(...) is executed on the FX Application Thread, so if you need to execute any blocking code, you need to put it in a background thread.
The start(...) method is passed a Stage instance for convenience, as the most common thing to do is to create a scene graph and display it in a stage. You are under no obligation to use this stage though, you can ignore it and just create your own stages as and when you need.
I think you can basically structure your code as follows (though, to be honest, I have quite a lot of trouble understanding what you're doing):
public class Mail extends Application {
#Override
public void start(Stage ignored) throws Exception {
Platform.setImplicitExit(false);
Message[] messages = /* retrieve messages */ ;
for (Message message : messages) {
if ( /* need to display window */) {
showMessage(message);
}
}
}
private void showMessage(Message message) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(root, 450, 250);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle(...);
// showAndWait will block execution until the window is hidden, so
// you can query which button was pressed afterwards:
stage.showAndWait();
if (controller.wasYesPressed()) {
// ...
}
}
// for IDEs that don't support directly launching a JavaFX Application:
public static void main(String[] args) {
launch(args);
}
}
Obviously your logic for decided whether to show a window is more complex, but this will give you the basic structure.
To check which button was pressed, use showAndWait as above and then in your controller do
public class Controller {
#FXML
private Button yesButton ;
private boolean yesButtonPressed = false ;
public boolean wasYesPressed() {
return yesButtonPressed ;
}
// use different handlers for different buttons:
#FXML
private void yesButtonPressed() {
yesButtonPressed = true ;
closeWindow();
}
#FXML
private void noButtonPressed() {
yesButtonPressed = false ; // not really needed, but makes things clearer
closeWindow();
}
private void closeWindow() {
// can use any #FXML-injected node here:
yesButton.getScene().getWindow().hide();
}
}
Below is a functioning class that simulates extracting data from a website. The question is how to close the window and threads after the data is retrieved? The error message is
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Platform.exit has been called
On line: final JFXPanel fxPanel = new JFXPanel() {
Here is the code. When Platform.exit() is inserted at the indicated locations, the above error occurs. In the actual code, javafx is used for obtaining and scanning the html text. Also, javafx is used for setting attributes on the server and starting a program on the server, both done using java script. The javafx functionality was removed from this minimal example that demonstrates the error that occurs when using Platform.exit().
public class Step11a {
public static void main(String[] args) throws ParseException, InterruptedException {
Step11a st = new Step11a();
st.getData();
st.getData();
}
private JTable table;
private String url;
private WebView webView;
public Step11a() {
url = "https://www.tsp.gov/investmentfunds/shareprice/sharePriceHistory.shtml";
table = new JTable();
}
private Scene createScene(ArrayBlockingQueue<DefaultTableModel> platfromQueue) {
StackPane root = new StackPane();
Scene scene = new Scene(root);
webView = new WebView();
WebEngine webEngine = webView.getEngine();
Worker<Void> worker = webEngine.getLoadWorker();
worker.stateProperty().addListener((Observable o) -> {
if (worker.getState() == Worker.State.SUCCEEDED) {
DefaultTableModel tableDataModel = (DefaultTableModel) table.getModel();
// Following steps deleted for minimal example
// 1) Get html code from server
// 2) Locate elements to modify
// 3) Create javascript string to modify element values
// 4) Execute javascript to update elemetns
// 5) Execute javascript to start data retrieval on server
// 6) Get html and extract table of data
// For this example, we just set the rows and columns
// in the table
tableDataModel.setColumnCount(11);
tableDataModel.setRowCount(24);;
try {
platfromQueue.put(tableDataModel);
} catch (Exception e) {
e.printStackTrace();
}
}
});
webEngine.load(url);
root.getChildren().add(webView);
return scene;
}
private DefaultTableModel initAndShowGUI() { // This method is invoked on the EDT thread
JFrame frame = new JFrame("WebViewTable");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JFXPanel fxPanel = new JFXPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 400);
}
};
frame.add(fxPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
ArrayBlockingQueue<DefaultTableModel> platformQueue = new ArrayBlockingQueue<DefaultTableModel>(5) ;
Platform.runLater(() -> {
initFX(fxPanel,platformQueue);
});
DefaultTableModel tspData = null;
try {
tspData = platformQueue.take();
} catch (Exception e) {
e.printStackTrace();
}
// Platform.exit(); // At this location causes error on next call
// error occurs on final JFXPanel fxPanel = new JFXPanel() {
return tspData;
}
private void initFX(JFXPanel fxPanel, ArrayBlockingQueue<DefaultTableModel> platformQueue) {
// This method is invoked on the JavaFX thread
// System.out.println("JavaFx thread name "+Thread.currentThread().getName());
Scene scene = createScene(platformQueue);
fxPanel.setScene(scene);
}
private DefaultTableModel getData() {
ArrayBlockingQueue<DefaultTableModel> swingQueue = new ArrayBlockingQueue<>(5);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
DefaultTableModel tspDataLocal = initAndShowGUI( );
swingQueue.put(tspDataLocal);
} catch (java.lang.ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
DefaultTableModel tableJFrameData = null;
try {
tableJFrameData = swingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Platform.exit(); // At this location causes error on next call
// error occurs on final JFXPanel fxPanel = new JFXPanel() {
return tableJFrameData;
}
}
My application having the two Screens.
Screen-1:
It's having two buttons and one Label
1) Download:
If we click on this, then we will start the downloading process but we still in screen1 and allow you to access the "Navigate" control.
2) Navigate: If we click on this, Then we will redirect to the screen-2.
Screen-2:
1)Back: If we click on this, then we will back to the Screen-1.
While downloading process, I want to allow the user to access the other controls as well. If we started the download process and navigates to some other screen and redirects to the download screen, then we will show the current downloading progress instead of opening it as fresh. For this, I implemented like following. I created one class for implementing this download process but I am unable to update the UI of the screen from that class. Please help me on this.
Screen-1
public class MainsceneController implements Initializable {
#FXML
Button Download, Navigate;
#FXML
Label percentage;
#FXML
HBox progTag;
SyncService service = new SyncService(MainsceneController.this);
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
void DownlaodManager() {
service.downloadProjectFiles();
}
#FXML
void Naviagtion() {
URL location = SecondSceneController.class.getResource("SecondScene.fxml");
ViewManager.getInstance().setView(location);
}
}
Screen-2:
public class SecondSceneController implements Initializable {
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
void goBack() {
URL location = MainsceneController.class.getResource("mainscene.fxml");
ViewManager.getInstance().setView(location);
}
}
Background downloading task
public class SyncService {
long currentDownload, totalFileSize;
MainsceneController controller;
DownloadingFilesTask downloadingFilesTask;
public SyncService(MainsceneController controller) {
this.controller = controller;
downloadingFilesTask = new DownloadingFilesTask();
}
public void downloadProjectFiles() throws IOException {
DownloadingFilesTask downloadingFilesTask = new DownloadingFilesTask();
downloadingFilesTask.progressProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldProgress, Number newProgress) {
System.out.println("Progress changed");
controller.percentage.setText("Progress changed:" + currentDownload);
}
});
downloadingFilesTask.stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> source, Worker.State oldState, Worker.State newState) {
if (newState.equals(Worker.State.SUCCEEDED)) {
System.err.println("Completed downloading files");
controller.percentage.setText("Progress changed:" + currentDownload);
}
}
});
//progress listeners.
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(downloadingFilesTask.progressProperty());
bar.visibleProperty().bind(downloadingFilesTask.runningProperty());
controller.progTag.getChildren().clear();
controller.progTag.getChildren().add(bar);
new Thread(downloadingFilesTask).start();
}
class DownloadingFilesTask extends Task<Void> {
#Override
protected Void call() throws Exception {
try {
String fullUrl = "http://s3-us-west-2.amazonaws.com/absprod/media/25/manuals/53454a73d9eda$$53454a73d9f571397049971.mp4";
String destLocation = "C:\\Users\\naresh.repalle\\Desktop\\ABS Test\\53454a73d9eda$$53454a73d9f571397049971.mp4";
File destFile = new File(destLocation);
URL downloadingUrl = new URL(fullUrl);
RandomAccessFile file = null;
InputStream stream = null;
int downloaded = 0;
int size = -1;
try {
// Open connection to URL.
HttpURLConnection connection = (HttpURLConnection) downloadingUrl.openConnection();
// Specify what portion of file to download.
connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
connection.setConnectTimeout(10 * 1000);
connection.setReadTimeout(10 * 1000);
// Connect to server.
connection.connect();
// Make sure response code is in the 200 range.
if (connection.getResponseCode() / 100 != 2) {
System.err.println("Wrong response code while downloading file." + connection.getResponseCode());
}
// Check for valid content length.
int contentLength = connection.getContentLength();
if (contentLength < 1) {
System.err.println("Wrong file size while downloading file." + contentLength);
}
/*
* Set the size for this download if it hasn't been already set.
*/
if (size == -1) {
size = contentLength;
}
totalFileSize = size;
// Open file and seek to the end of it.
file = new RandomAccessFile(destFile, "rw");
file.seek(downloaded);
stream = connection.getInputStream();
int MAX_BUFFER_SIZE = 1024;
while (true) {
/*
* Size buffer according to how much of the file is left to download.
*/
byte buffer[];
if (size - downloaded > MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}
// Read from server into buffer.
int read = stream.read(buffer);
if (read == -1) {
System.out.println("read: " + read);
break;
}
// Write buffer to file.
file.write(buffer, 0, read);
downloaded += read;
currentDownload = downloaded;
stateChanged();
}
} catch (Exception e) {
System.err.println("Exception in Downloading file: " + e.toString());
} finally {
/*
* Change status to complete if this point was reached because downloading
* has finished.
*/
// Close file.
if (file != null) {
try {
file.close();
} catch (Exception e) {
}
}
// Close connection to server.
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
System.err.println("exception in downloading: " + e.toString());
}
}
}
} catch (Exception ex) {
System.err.println("Unable to download file: " + ex);
}
return null;
}
private void stateChanged() {
updateProgress(currentDownload, totalFileSize);
}
}
}
Your SyncService doesn't need to reload the FXML; it just needs to communicate with the existing controller. A simple way to do this would be to just pass a reference to SyncService's constructor:
public class SyncService {
MainSceneController controller ;
public SyncService(MainSceneController controller) {
this.controller = controller ;
}
// ...
public void downloadProjectFiles() throws IOException {
DownloadingFilesTask downloadingFilesTask = new DownloadingFilesTask();
// Remove the following:
// URL location = MainsceneController.class.getResource("mainscene.fxml");
// FXMLLoader fxmlLoader = new FXMLLoader();
// fxmlLoader.setLocation(location);
// fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
// final AnchorPane root = (AnchorPane) fxmlLoader.load(location.openStream());
//get the controller
// final MainsceneController controller = (MainsceneController) fxmlLoader.getController();
// Then code as before
// ...
}
}
I think I would actually do it differently: I don't like the strong coupling between the SyncService and the MainSceneController. I would initialize the progress bar in the MainSceneController, expose the progress as a property in the SyncService and bind to it in the controller. But you should be able to use the simpler approach to get it working.