Progress Indicator won't display until main code stops - java

I've been struggling for days over a stupid issue, and I need your help. I'm simply trying to display a indeterminate process indicator like the one below, within a separate Stage, while my main code performs a loop. If a certain condition is met while looping, the progress stage should close and the main code continues.
Right now I'm able to open a new stage prior to the main code starting the loop, but the progress indicator won't display in the stage yet. But once the condition is met, then the indicator suddenly appears in the stage and rotates. BUT as soon as the main code begins running again the indicator freezes, yet remains visible.
I'll briefly explain what each code below does. How do I make it so that the loading stage appears initially WITH the indicator visible, and then that stage CLOSES when the showMessage() method is called? Basically I want to show the user that background looping is happening, until the main code reaches showMessage().
Main.java
I won't post it, as it only creates the FIRST GUI. It uses menu.fxml as the resource in FXMLLoader ... and menu.fxml uses Controller.java as controller.
Controller.java
This code changes the scene in the Main GUI to have a new layout, which asks the user to click one of the visible buttons. It then checks if the source button is Button1... and if it is then create/show a new stage which uses sample.fxml as the resource. It then runs the final file, Loop.java
public class Controller implements Initializable {
public Stage loadingStage;
#FXML
ProgressIndicator progressIndicator;
public void handlerButtonClick() throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("field.fxml"));
Parent rootOne = loader.load();
((Controller) loader.getController()).setPrimaryStage(primaryStage);
((Controller) loader.getController()).setPrimaryScene(scene);
sceneField = new Scene(rootOne, 420, 510);
primaryStage.setScene(sceneField);
primaryStage.setTitle("Import Data");
primaryStage.show();
}
public void fieldOption(ActionEvent e) throws Exception {
source = e.getSource();
if (source == Button1) {
Loop loopObj = new Main();
String[] args = {};
//Create new stage to contain the Progress Indicator
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root2 = loader2.load();
Controller2 controller = loader2.getController();
loadingStage = new Stage();
loadingStage.setTitle("Loading Stage");
Scene scene = new Scene(root2, 200, 200);
loadingStage.setScene(scene);
loadingStage.show();
//Runs the looper
loopObj.start(stage);
}
}
Sample.fxml
This code creates the Progress Indicator, and uses the controller Controller2
<AnchorPane prefHeight="200.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/2.2" fx:controller="Controller2">
<children>
<ProgressIndicator fx:id="progressIndicator" layoutX="78.0" layoutY="55.0" progress="-1.0"/>
</children>
</AnchorPane>
Controller2.java
This code implements Initializable in order to make the Progress Indicator visible:
public class Controller2 implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
ProgressIndicator progressIndicator = new ProgressIndicator();
progressIndicator.setVisible(true);
}
}
Loop.java
This is the main code that performs the loop. The loading stage should display the Progress Indicator until Loop.java calls its showMessage() function, at which point the loading stage closes. I know that it seems that one of these methods aren't necessary, but its only because they're stripped for demo purposes.
public class Loop extends Application {
#Override
public void start(Stage ignored) throws Exception {
Platform.setImplicitExit(false);
for (int i = 0; i <= 100; i++) {
if (i == 75){
saveAttachment("Hello");
}
}
}
public static void saveAttachment(String subject) throws IOException, MessagingException {
showMessage(subject);
}
private static void showMessage(String subject) throws IOException, MessagingException {
// Close the loading stage here.
}
}
Notice how it uses public void start(Stage ignored). Main.java uses public void start(Stage primaryStage). Perhaps this is bad.
I'm so frustrated please help!
UPDATE
I've applied Brian's suggestions, and within Controller.java have added a new task like so:
Stage loadingStage = new Stage();
//create task object
Task<Void> task = new Task<Void>(){
#Override
protected Void call() throws Exception{
System.out.println("Background task started...");
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root2 = loader2.load();
Controller2 controller = loader2.getController();
loadingStage.setTitle("Hello World");
Scene scene = new Scene(root2, 450, 250);
System.out.println("HERE6");
loadingStage.setScene(scene);
System.out.println("HERE7");
loadingStage.show();
System.out.println("HERE8");
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
System.out.println("Starting background task...");
th.start();
The problem now is that it doesn't reach the printout line saying "HERE7". It successfully enters the task and continues the main code, but the task doesn't get beyond where it prints "HERE6" therefore no new stage is opened. What gives?

In Ctlr2 you have ProgressIndicator progressIndicator = new ProgressIndicator(); but if it's in the FXML file you don't create a new one. Just do #FXML ProgressIndicator progressIndicator; like you have in the first Ctlr for some reason??
But this won't solve your problem, you're running everything on the JavaFX Application thread (GUI thread). What is probably happening (I didn't really read the code) is that you're doing both things on the same thread and the progress indicator has to wait for main to finish and then it can update the GUI. You put tags for task and concurrency, that's what you need.
Check here and do a search for more examples.
https://stackoverflow.com/a/22847734/2855515

Related

Unable to listen to change a JavaFX controller property from inside a thread

(Code at bottom)
Part of an application I'm trying to make involves a timer and I want to be able to display this timer in the bottom corner of the application. I've made a thread that will count down from a given number (in seconds) and display the countdown in the command line.
The problem is, I am unsure how to send this value back to my controller. I have tried to do this by saving the controller as a static variable in another class so methods and variables and so on can be accessed, but this also does not work as I get this error:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:279)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:444)
at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$0(BehaviorSkinBase.java:197)
at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.scene.control.Labeled.setText(Labeled.java:145)
at train.TrainController.updateTime(TrainController.java:34)
at train.Timerr.run(Timerr.java:36)
I think I need to add a listener within the controller but I'm not sure how to do this.
Here I will make a quick example of what I want to do. Everything is just in one package of a new javaFX project. The error I get here is the exact same:
Main:
public class Quick extends Application {
#Override
public void start(Stage primaryStage) throws IOException{
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXML.fxml"));
Parent root = loader.load();
Master.savedController = loader.getController();
Scene scene = new Scene(root, 200, 200);
primaryStage.setTitle("KReact");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Master class:
public class Master {
public static FXMLController savedController;
}
Thread:
public class Timerr extends Thread{
public int t;
Timerr(int time){
t = time;
}
#Override
public void run(){
while(t > 0){
try{
Thread.sleep(1000);
t = t-1;
}catch(InterruptedException e){}
System.out.println(t);
Master.savedController.updateTime(t);
}
}
}
Controller:
public class FXMLController implements Initializable {
#FXML Label time;
public void updateTime(int t){
time.setText(Integer.toString(t));
}
Timerr timer = new Timerr(60);
#FXML public void start(){
timer.start();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
time.setText("60");
}
}
FXML file:
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" fx:controller="quick.FXMLController">
<Label fx:id="time" layoutX="80" layoutY="80"/>
<Button onAction="#start"/>
I haven't been able to find the answer anywhere, and I thought it would be best to give an example code in case it is helpful to answer my question.
Thank you very much in advance for taking a look :)
Edit: think there may be something with Task I can do, but still couldn't find what.
Using timelines instead of a thread fixed this:
JavaFX periodic background task

Bringing a single stage to the front in windows in JavaFX

I am trying to create a stage in JavaFX that can pop up to the front in windows, while the main stage stays minimized.
This only works however, when the main stage is visible on the screen. I have tried making it work using Modality, but then the user can't interact with the main stage, which is not what i want.
The problem can be reproduced with the following Application:
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Scene mainScene = new Scene(new Parent() {});
Stage mainStage = new Stage();
mainStage.setScene(mainScene);
mainStage.show();
mainStage.setIconified(true);
Scene popUpScene = new Scene(new Parent() {});
Stage popUpStage = new Stage();
popUpStage.setScene(popUpScene);
Thread.sleep(5000);
popUp(popUpStage);
}
public static void popUp(Stage popUpStage){
if (popUpStage.isIconified()) popUpStage.setIconified(false);
popUpStage.show();
popUpStage.requestFocus();
popUpStage.toFront();
}
}
Is there anyone who has an answer to this problem?
Just add these two line to popUp. First line brings it to front. Second line allows interaction with the main stage or other windows.
popUpStage.setAlwaysOnTop(true);
popUpStage.setAlwaysOnTop(false);

java.lang.IllegalStateException: Application launch must not be called more than once - JavaFX (first once works, the 2nd non-) [duplicate]

How to call the launch() more than once in java i am given an exception as "ERROR IN MAIN:java.lang.IllegalStateException: Application launch must not be called more than once"
I have create rest cleint in my java application when request comes it call javafx and opening webview after completing webview operarion am closing javafx windows using Platform.exit() method. when second request comes am getting this error how to reslove this error.
JavaFx Application Code:
public class AppWebview extends Application {
public static Stage stage;
#Override
public void start(Stage _stage) throws Exception {
stage = _stage;
StackPane root = new StackPane();
WebView view = new WebView();
WebEngine engine = view.getEngine();
engine.load(PaymentServerRestAPI.BROWSER_URL);
root.getChildren().add(view);
engine.setJavaScriptEnabled(true);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
engine.setOnResized(new EventHandler<WebEvent<Rectangle2D>>() {
public void handle(WebEvent<Rectangle2D> ev) {
Rectangle2D r = ev.getData();
stage.setWidth(r.getWidth());
stage.setHeight(r.getHeight());
}
});
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("app", new BrowserApp());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
RestClient Method:
Calling to JavaFX application
// method 1 to lanch javafx
javafx.application.Application.launch(AppWebview.class);
// method 2 to lanch javafx
String[] arguments = new String[] {"123"};
AppWebview .main(arguments);
You can't call launch() on a JavaFX application more than once, it's not allowed.
From the javadoc:
It must not be called more than once or an exception will be thrown.
Suggestion for showing a window periodically
Just call Application.launch() once.
Keep the JavaFX runtime running in the background using Platform.setImplicitExit(false), so that JavaFX does not shutdown automatically when you hide the last application window.
The next time you need another window, wrap the window show() call in Platform.runLater(), so that the call gets executed on the JavaFX application thread.
For a short summary implementation of this approach:
See the answer by sergioFC
If you are mixing Swing you can use a JFXPanel instead of an Application, but the usage pattern will be similar to that outlined above.
For an example of the JFXPanel apprach, see Irshad Babar
s answer.
Wumpus Sample
This example is bit more complicated than it needs to be because it also involves timer tasks. However it does provide a complete stand-alone example, which might help sometimes.
import javafx.animation.PauseTransition;
import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
// hunt the Wumpus....
public class Wumpus extends Application {
private static final Insets SAFETY_ZONE = new Insets(10);
private Label cowerInFear = new Label();
private Stage mainStage;
#Override
public void start(final Stage stage) {
// wumpus rulez
mainStage = stage;
mainStage.setAlwaysOnTop(true);
// the wumpus doesn't leave when the last stage is hidden.
Platform.setImplicitExit(false);
// the savage Wumpus will attack
// in the background when we least expect
// (at regular intervals ;-).
Timer timer = new Timer();
timer.schedule(new WumpusAttack(), 0, 5_000);
// every time we cower in fear
// from the last savage attack
// the wumpus will hide two seconds later.
cowerInFear.setPadding(SAFETY_ZONE);
cowerInFear.textProperty().addListener((observable, oldValue, newValue) -> {
PauseTransition pause = new PauseTransition(
Duration.seconds(2)
);
pause.setOnFinished(event -> stage.hide());
pause.play();
});
// when we just can't take it anymore,
// a simple click will quiet the Wumpus,
// but you have to be quick...
cowerInFear.setOnMouseClicked(event -> {
timer.cancel();
Platform.exit();
});
stage.setScene(new Scene(cowerInFear));
}
// it's so scary...
public class WumpusAttack extends TimerTask {
private String[] attacks = {
"hugs you",
"reads you a bedtime story",
"sings you a lullaby",
"puts you to sleep"
};
// the restaurant at the end of the universe.
private Random random = new Random(42);
#Override
public void run() {
// use runlater when we mess with the scene graph,
// so we don't cross the streams, as that would be bad.
Platform.runLater(() -> {
cowerInFear.setText("The Wumpus " + nextAttack() + "!");
mainStage.sizeToScene();
mainStage.show();
});
}
private String nextAttack() {
return attacks[random.nextInt(attacks.length)];
}
}
public static void main(String[] args) {
launch(args);
}
}
Update, Jan 2020
Java 9 added a new feature called Platform.startup(), which you can use to trigger startup of the JavaFX runtime without defining a class derived from Application and calling launch() on it. Platform.startup() has similar restrictions to the launch() method (you cannot call Platform.startup() more than once), so the elements of how it can be applied is similar to the launch() discussion and Wumpus example in this answer.
For a demonstration on how Platform.startup() can be used, see Fabian's answer to How to achieve JavaFX and non-JavaFX interaction?
I use something like this, similar to other answers.
private static volatile boolean javaFxLaunched = false;
public static void myLaunch(Class<? extends Application> applicationClass) {
if (!javaFxLaunched) { // First time
Platform.setImplicitExit(false);
new Thread(()->Application.launch(applicationClass)).start();
javaFxLaunched = true;
} else { // Next times
Platform.runLater(()->{
try {
Application application = applicationClass.newInstance();
Stage primaryStage = new Stage();
application.start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
try this, I tried this and found successful
#Override
public void start() {
super.start();
try {
// Because we need to init the JavaFX toolkit - which usually Application.launch does
// I'm not sure if this way of launching has any effect on anything
new JFXPanel();
Platform.runLater(new Runnable() {
#Override
public void run() {
// Your class that extends Application
new ArtisanArmourerInterface().start(new Stage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}

How do you change the text in a button from another window (JavaFX + FXML)?

I am very new to using JavaFX and have having some trouble using JavaFX with FXML. I am creating a program with a "Setup" button that when clicked, opens a new window with the connection (to an Arduino) settings. On the menu is another button ("Connect") that connects to the board. This closes the window. I'm looking to have this change the text of the original "setup" button to "disconnect", however, I don't seem to be able to access the button from the "setup window". Whenever I click "connect" I get the following error:
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
I read online that this is a wrapper for a null pointer exception. I assume that the "setup" button is null and that's why I can't change it, but I can't work out why.
Here is an excerpt from MainController.java:
#FXML
protected void setUpConnection(ActionEvent e) {
SetupController setupController = new SetupController();
setupController.init(this);
}
The above method gets called when the "setup" button is clicked (set in the file: setupMenu.fxml). This then opens up the separate window. Here is the code in SetupController.java that opens the window:
private void openSetupWindow() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("setupMenu.fxml"));
Parent root1 = (Parent)loader.load();
Stage stage = new Stage();
stage.setTitle("Setup Connection");
stage.setScene(new Scene(root1));
stage.show();
} catch(Exception exc) {
exc.printStackTrace();
}
}
When the connect button is clicked, the following method (in SetupController.java) is called:
private void changeButtonText(ConnectionEventType e) {
Button b = main.getSetupButton();
if(e == ConnectionEventType.CONNECT) {
b.setText("Disconnect");
}
else {
b.setText("Setup Connection...");
}
}
(main is the MainController object that was passed in to setupController.init() )
The above code is where I am getting the error.
Just to clarify, I have 2 separate fxml files, one for the main window and one for the pop up. sample.fxml(the main window) has its controller set to MainController and is set up in Main.java (below):
#Override
public void start(Stage primaryStage) throws Exception{
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
GridPane root = loader.load();
Scene scene = new Scene(root, 1200, 900);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setTitle("Nest Control");
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
Am I trying to access the button incorrectly? Is anyone able to help? Like I said, I don't have much experience with using JavaFX or FXML.
I think the answer you are looking for is to store the controllers for each window you open so you can access the variables within the controllers, however without the rest of your code would be hard to advise you, but heres an example of what i mean:
private SetupController yourController;
#Override
public void start(Stage primaryStage) throws Exception{
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
GridPane root = loader.load();
this.yourController=loader.<SetupController>getController();
Scene scene = new Scene(root, 1200, 900);
}
}
You could then pass the variable yourController to other instances in a Model-view-controller type way and access its methods.
or something like this in your case :
private void changeButtonText(ConnectionEventType e) {
Button b = this.yourController.getButton(); //a method that returns your #FXML button object in your controller
if(e == ConnectionEventType.CONNECT) {
b.setText("Disconnect");
}
else {
b.setText("Setup Connection...");
}
}
Or alternatively have a specific method within the controller that will set the text of the button without having to return the button object.
See the examples here and here
However please note the error you get seems to typically attributed to missing #FXML annotations so maybe make sure in this instance that you have annotated all the variables in any controllers also. See here for more details.

Injecting variables to the JavaFX thread

I'm trying to learn JavaFX and I'm having some trouble understanding how the JavaFX thread can interact with the rest of my application. I'm using JavaFX for the interface window but I have some other stuff running in the main thread.
I load my FXML from a file I've created with the Scene Builder and I've attached the open method to a MenuItem in Scene Builder.
I've tried having an instance variable in the Editor class and populating it in the constructor as well as the start method but it's always null in the open method. As far as I understand this is because the open method is called from the JavaFX thread. What is the best practice to solve this problem? This goes the other way to, what if I want to access a component if the JavaFX thread in my main thread?
public class Editor extends Application {
private Stage stage;
private FileChooser fileChooser;
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setTitle("Editx");
stage.setScene(new Scene(
FXMLLoader.load(this.getClass().getResource("./Editor.fxml")),
1280,
800));
stage.show();
this.fileChooser = new FileChooser();
this.fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("OBJ", "*.obj"));
this.fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("All Files", "*.*"));
}
#FXML
private void open() {
// Both this.fileChooser and this.stage is null here for example
File file = this.fileChooser.showOpenDialog(this.stage);
}
}

Categories

Resources