I have a small javafx application using scene builder which on a button click should read a string from COM port at regular intervals and update in a text field.
But now it only shows the last string if I use a for loop, and nothing if i put the code in infinite loop (That's my temporary requirement).
Can anyone help me so that at each read from COM port the new string is updated in the text field.
Here is the code I used for both the cases :
Note : In both cases in controller class, I'm getting perfect output on console.
public class Main extends Application
{
#Override
public void start(Stage primaryStage)
{
try
{
Parent root = FXMLLoader.load(getClass().getResource("test.fxml"));
Scene scene = new Scene(root);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setTitle("test");
primaryStage.setScene(scene);
primaryStage.show();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
Here is the Controller class :
// In this case it shows only the last string in the text field.
public class Controller implements Initializable
{
#FXML
private Button sayHelloButton;
#FXML
private TextField helloField;
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
}
#FXML
public void printHello(ActionEvent event)
{
if(event.getSource() == sayHelloButton)
{
SerialPort serialPort = new SerialPort("COM22");
for(int i=0;i<5;i++)
{
try
{
if(!serialPort.isOpened())
{
serialPort.openPort();
serialPort.setParams(9600, 8, 1, 0);
}
String str = serialPort.readString(10,3000);
System.out.println(str);
helloField.clear();
helloField.setText(str);
}
catch(Exception e)
{
helloField.setText(e.toString());
}
}
}
}
}
Here is the method with infinite loop :
//this shows nothing in the text field
#FXML
public void printHello(ActionEvent event)
{
if(event.getSource() == sayHelloButton)
{
SerialPort serialPort = new SerialPort("COM22");
while(true)
{
try
{
if(!serialPort.isOpened())
{
serialPort.openPort();
serialPort.setParams(9600, 8, 1, 0);
}
String str = serialPort.readString(10,3000);
System.out.println(str);
helloField.clear();
helloField.setText(str);
}
catch(Exception e)
{
helloField.setText(e.toString());
}
}
}
}
There are a couple things happening here. In your first example, you state that the console output is correct but the TextField only shows the last result.
This is expected if the loop executes quickly. The TextField is being updated, but it happens so quickly that you can't see it until the loop ends and the last result is still being displayed. Even if you have a delay built into the loop, this could still block the UI from being updated until the loop is completed.
With your infinite loop, the issue is that the loop is being run on the JavaFX Application Thread (JFXAT). This blocks any updates to the GUI until the loop is finished, which is never is.
You will need to move the infinite loop to a new background thread. From there, you can update the GUI using the Platform.runLater() method.
SerialPort serialPort = new SerialPort("COM22");
new Thread(() -> {
while(true)
{
try
{
if(!serialPort.isOpened())
{
serialPort.openPort();
serialPort.setParams(9600, 8, 1, 0);
}
String str = serialPort.readString(10,3000);
System.out.println(str);
// Update the UI on the JavaFX Application Thread
Platform.runLater(() -> {
helloField.clear();
helloField.setText(str);
});
}
catch(Exception e)
{
Platform.runLater(() -> helloField.setText(e.toString()));
}
}
}).start();
This allows your UI to continually update as the background thread sends it new information.
Related
apologies for the length of my code. I realized last night that I was on the wrong path and now have gotten stuck on an issue that I think relates to JavaFX event handling. Initially I had the logic functioning outside a GUI in a basic loop that depended on interaction through the console. Everything was working great. I've now tried to get this to work in a GUI with interaction from the user.
I have two main problems with the code below.
The first is that the text in textArea is not updating with additional text after the startButton executes the start of my main logic sequence. The first append starts right under the first while loop. I was hoping to have this show up in the GUI as the logic executes. I'm not sure if I need to tell the GUI to update at certain intervals or if there's something else wrong.
Second, I'm not sure how to get the program to wait for the user to type in something into textField before hitting the textButton I created to continue on. I used to have a scanner created which caused the program to wait in the console for input. I realize I need some way of telling it to wait for a button press when it's running inside JavaFX.
I chose not to include the rest of the code to make things easier to read, but I can add it on if it will help resolve this issue.
Thank you everyone for your help!
public class FxApp extends Application {
//Creates FileParser object with methods that alter the incoming Array of Strings into the format we need
FileParser fileParser = new FileParser();
Configure configure = new Configure();
private String text;
private String initialState;
private ArrayList<Machine> machines = new ArrayList<Machine>();
private Map<String, String> initialStates = new HashMap<String, String>();
private Map<String, String> states = new HashMap<String, String>();
private Map<String, ArrayDeque<String>> queues = new HashMap<String, ArrayDeque<String>>();
private Map<Integer, ArrayList<String>> parsedData = new HashMap<Integer, ArrayList<String>>();
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("File Chooser");
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().addAll(new ExtensionFilter("Text Files", "*.txt"));
Button startButton = new Button("Start");
Button openButton = new Button("Click to open a file...");
openButton.setPrefSize(200, 80);
Button textButton = new Button("Enter");
TextArea textArea = new TextArea();
textArea.setWrapText(true);
TextField textField = new TextField();
Label lbl = new Label();
VBox vbox = new VBox(lbl, openButton, startButton, textArea, textField, textButton);
vbox.setSpacing(10);
vbox.setPadding(new Insets(15));
lbl.setText("This tool creates virtual automata based \ron the file.");
Scene scene = new Scene(vbox, 640, 480);
primaryStage.setScene(scene);
primaryStage.show();
openButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
//Execute the method to convert to string array before sending to file parser
try {
fileParser.convertFile(file);
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
text = textField.getText();
}
});
startButton.setOnAction(new EventHandler <ActionEvent>()
{
public void handle(ActionEvent event)
{
machineCreation();
String exit = "no";
String nextLine = null;
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
int nextInt = 0;
states = initialStates;
while(!(exit.toLowerCase().equals("yes"))) {
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(!(listOfCurrentTransitions.isEmpty())) {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
else {
textArea.appendText("No transitions for this machine exist from its current state");
}
//Tell GUI to wait for user input in textField and execute textButton which assigns to String text. Resume on button click.
The while loop blocks the JavaFX application thread which prevents updates of the GUI and handling of events.
You need to execute the logic of a single iteration of the loop on each "text commit" instead:
private TextArea textArea;
private void activateState(int nextInt) {
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(listOfCurrentTransitions.isEmpty()) {
textArea.appendText("No transitions for this machine exist from its current state");
} else {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
...
textArea = new TextArea();
...
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
machineCreation();
activateState(0);
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// read input and ask for more input...
int nextState = Integer.parseInt(textField.getText()); // TODO: deal with invalid input
activateState(nextState);
}
});
You probably need to fix the logic a bit to verify a transition is valid, change the values of some fields ect...
I'm trying to get my JavaFX Preloader splash sccreen to show up before my application. I'm using Eclipse IDE and when I click "run", half the time the splash screen will display correctly and the other half of the time I will get a gray or black screen instead of where the image should be.
I'm not sure what the issue is to cause it to only display correctly sometimes.
SplashController:
public class SplashController extends Preloader {
private static final double WIDTH = 676;
private static final double HEIGHT = 227;
private Stage preloaderStage;
private Label progressText;
private Pane splashScreen;
public SplashController() {}
#Override
public void init() throws Exception {
ImageView splash =
new ImageView(new Image(Demo.class.getResource("pic.png").toString()));
progressText =
new Label("VERSION: " + getVersion() + " ~~~ Loading plugins, please wait...");
splashScreen = new VBox();
splashScreen.getChildren().addAll(splash, progressText);
progressText.setAlignment(Pos.CENTER);
}
#Override
public void start(Stage primaryStage) throws Exception {
this.preloaderStage = primaryStage;
Scene splashScene = new Scene(splashScreen);
this.preloaderStage.initStyle(StageStyle.UNDECORATED);
final Rectangle2D bounds = Screen.getPrimary().getBounds();
this.preloaderStage.setScene(splashScene);
this.preloaderStage.setX(bounds.getMinX() + bounds.getWidth() / 2 - WIDTH / 2);
this.preloaderStage.setY(bounds.getMinY() + bounds.getHeight() / 2 - HEIGHT / 2);
this.preloaderStage.show();
}
}
And then in my main class Demo I simply have:
public class Demo extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new
FXMLLoader(Demo.class.getResource("FXMLDocument.fxml"));
GridPane root = loader.load();
--------other app code here---------
}
public static void main(String[] args) {
LauncherImpl.launchApplication(Demo.class, SplashController.class, args);
}
}
Likely, you are executing some long running process on the JavaFX application thread or a thread involved in the application startup, which prevents the smooth operation of the preloader.
I suggest you review an Oracle Preloader sample and compare to your application. Ensure that you are using concurrent features like Task correctly, similar to the linked example. Check the linked sample works in your environment.
Source code (just copied from the Oracle Preloader sample link)
Note how in the start method of the main LongAppInit application class, that a Task and thread is spawned to ensure that the long application initiation does not take place on the JavaFX application thread. Also see how the notifyPreloader() method of application is called at various times within the long application initialization to let the preloader know of the current state of the initialization process so that it can reflect the progress accurately in the UI in real time.
LongAppInitPreloader.java
public class LongAppInitPreloader extends Preloader {
ProgressBar bar;
Stage stage;
boolean noLoadingProgress = true;
private Scene createPreloaderScene() {
bar = new ProgressBar(0);
BorderPane p = new BorderPane();
p.setCenter(bar);
return new Scene(p, 300, 150);
}
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
//application loading progress is rescaled to be first 50%
//Even if there is nothing to load 0% and 100% events can be
// delivered
if (pn.getProgress() != 1.0 || !noLoadingProgress) {
bar.setProgress(pn.getProgress()/2);
if (pn.getProgress() > 0) {
noLoadingProgress = false;
}
}
}
#Override
public void handleStateChangeNotification(StateChangeNotification evt) {
//ignore, hide after application signals it is ready
}
#Override
public void handleApplicationNotification(PreloaderNotification pn) {
if (pn instanceof ProgressNotification) {
//expect application to send us progress notifications
//with progress ranging from 0 to 1.0
double v = ((ProgressNotification) pn).getProgress();
if (!noLoadingProgress) {
//if we were receiving loading progress notifications
//then progress is already at 50%.
//Rescale application progress to start from 50%
v = 0.5 + v/2;
}
bar.setProgress(v);
} else if (pn instanceof StateChangeNotification) {
//hide after get any state update from application
stage.hide();
}
}
}
LongAppInit.java
public class LongInitApp extends Application {
Stage stage;
BooleanProperty ready = new SimpleBooleanProperty(false);
private void longStart() {
//simulate long init in background
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
int max = 10;
for (int i = 1; i <= max; i++) {
Thread.sleep(200);
// Send progress to preloader
notifyPreloader(new ProgressNotification(((double) i)/max));
}
// After init is ready, the app is ready to be shown
// Do this before hiding the preloader stage to prevent the
// app from exiting prematurely
ready.setValue(Boolean.TRUE);
notifyPreloader(new StateChangeNotification(
StateChangeNotification.Type.BEFORE_START));
return null;
}
};
new Thread(task).start();
}
#Override
public void start(final Stage stage) throws Exception {
// Initiate simulated long startup sequence
longStart();
stage.setScene(new Scene(new Label("Application started"),
400, 400));
// After the app is ready, show the stage
ready.addListener(new ChangeListener<Boolean>(){
public void changed(
ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
if (Boolean.TRUE.equals(t1)) {
Platform.runLater(new Runnable() {
public void run() {
stage.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();
}
}
I'm not very sure how to word this question but I'll try. My application runs commands against a website with a click of a button. The issue is during each loop the getLoadWorker increases by 1. In the load worker i set listeners. Here is how it works.
MenuItem executeToHere = new MenuItem("Execute to here");
executeToHere.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
listViewStepItem item = stepListView.getSelectionModel().getSelectedItem();
int selectedIndex = stepList.getSelectionModel().getSelectedIndex();
WebBrowser browser = new WebBrowser(item.getWebView(), item.getListView());
for(int i=0; i < selectedIndex; i++){
listViewStepItem item2 = stepList.getItems().get(i);
if(item2.comboBoxSelected.contains("http://")){
browser.loadURL();
} else if(item2.comboBoxSelected.contains("enter")){
browser.enterText();
} else if(item2.comboBoxSelected.contains("click")){
browser.click();
}
}
browser.setWorker();
}
});
public class WebBrowser{
public WebBrowser(WebView fxmlWebView, WebEngine webEngine){
this.view = fxmlWebView;
this.engine = webEngine;
}
public void loadUrl(String url){
webEngine.load(url);
}
public void enterText(){
System.out.println("ENTER TEXT");
}
public void click(){
System.out.println("click");
}
public void setWorker(){
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>(){
public void changed(ObservableValue ov, State oldState, State newState){
if(newState == javafx.concurrent.Worker.State.SUCCEEDED){
listener = new EventListener(){
public void handleEvent(org.w3c.dom.events.Event evt) {
eventListeners(evt);
}
};
setListenerByTagNames(listener, "a");
}
}
});
}
private void setListenerByTagNames(EventListener listener, String tagName){
Document doc = webEngine.getDocument();
NodeList elements = doc.getElementsByTagName(tagName);
for(int i=0; i < elements.getLength();i++){
((EventTarget) elements.item(i)).addEventListener("click", listener, false);
}
System.out.println("Listening on :"+tagName);
}
}
the first time i run it the output looks like this
ENTER TEXT
click
Listening on : a
second time
ENTER TEXT
click
Listening on : a
Listening on : a
third time
ENTER TEXT
click
Listening on : a
Listening on : a
Listening on : a
I don't see how the worker is increasing but it causes the page to reload/refresh somehow and therefore all the changes to the page DOM is reset.
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;
}
}