While using SwingWorker to sequentially update my front end, I think I may have found a bug.
I have create a server which listen for connections. A connection might only initiate in a few minutes, so I created a little connecting animation that plays while the server is waiting for a new incoming connection.
A SwingWorker<T,T> is used to update a JLabel every 0.5 seconds with a character, which in the end, creates a character that bounces from one end of the panel to next, upon connection, the animation is removed and the JLabel is updated with different text.
This has been working all fine and dandy, but I have recently realised that the animation sometimes freezes, but yet, every time I ran the animation, it stopped and the JLabel updated correctly.
I then realised that the JLabel text does not update when I am on a different workspace.
(Edit: Where workspace refers to the ability to switch between desktops (workspaces) in Linux (I'm on Linux Mint 15). See: http://linux.about.com/library/gnome/blgnome2n4a.htm
I tested and retested:
Test A:
Start listening for connections
Application Animation starts (powered by SwingWorker)
Keep app open on current workspace
Client connects and animation correctly disappears
Jlabel correctly updates to new text.
Test B:
Start listening for connections
Animation starts (powered by SwingWorker)
Move to a different workspace by pressing Ctrl+Alt, (arrow key) and leave app on previous workspace.
Wait until client connects and move back to workspace containing app.
Jlabel froze and DID NOT update to new text.
This is where it gets weird: If I select a JMenuItem, which will in turn overlap with the animation, the overlapping part of the text will then UPDATE partially to the correct text.
I have not tested this on another Linux distribution, but will do so after I get feedback that my code is not the cause of this problem.
CODE:
ConnectingAnimation will be running all the time, but only updating the animation when the server is not connected.
public class ConnectingAnimation extends SwingWorker<Void, String> {
#Override
protected Void doInBackground() throws Exception {
StringBuilder sb = new StringBuilder();
while (true) {
while (!connected) {
try {
...
//Append text to StringBuilder
...
//publish updated text to be displayed next
publish(sb.toString());
//Sleep
Thread.sleep(100);
} catch (InterruptedException e) {
return null;
}
}
//Waits until clientConnected() is called
try {
synchronized (clientConnection) {
clientConnected.wait();
}
} catch (InterruptedException e) {
logger.logException(e);
}
}
}
#Override
protected void process(final List<String> chunks) {
for (final String update : chunks) {
if (connected) {
connectionStatusLabel.setText("Client Connected");
break;
}
connectionStatusLabel.setText(update);
}
}
}
clientConnected() will be called by an external module notifying the animation to stop
public static void clientConnected() {
...
synchronized (clientConnection) {
connected = true;
clientConnection.notify();
}
}
Related
So this is in reference to my previous questions here and here. Basically the problem I am having is the program goes into the Server class and stays there because there is a while look in the server code that essentially is continuously listening to the port for any response from the client; this makes the Main window completely unclickable since the program is not in the MainWindow class.
But really what I want it to do is setup the connection and then go back to MainWindow so the on-screen buttons are clickable. Or, as suggested by one poster, I could pass the MainWindow object by reference to the Server (which I have already done) and then use SwingUtilities.invokeLater() but I'm not really looking to update the GUI. I'm looking to give control back to the GUI so the user can click buttons and such.
When it goes back to MainWindow, I still need the connection to stay alive so the client can send things and I can receive it and display the received content on the screen for the user to control.
Basically, you could use a SwingWorker to perform actions in the background, off the Event Dispatching Thread, which will allow you application to continue working...
SwingWorker<Server , Object> worker = new SwingWorker<Server , Object>() {
#Override
protected ServerdoInBackground() throws Exception {
Server server = new Server();
//...
// Use publish(...) to send information
// back the EDT via the process method...
return server;
}
#Override
protected void done() {
try {
Server server = get();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
}
#Override
protected void process(List<Object> chunks) {
// You can safly update the UI from here, maybe
// with some messages??
}
};
worker.execute();
Basically, this creates a new Server object in the doInBackground method, does what ever else it needs to do that might otherwise block the EDT and exits by returning the server instance.
You can use done if you want to be notified when the doInBackground method completes and either get the result of the method, or if it threw an exception, handle the exeption.
The publish/process methods can be used to send information back to the EDT, which could be used to update the state of the UI...or not...
Now, if you already have a reference to the server, may consider creating a custom SwingWorker to which you pass the reference of the Server to...
Server server = ...;
ConnectionWorker worker = new ConnectionWorker(server);
worker.execute();
//...
public class ConnectionWorker extends SwingWorker<Object, Object> {
private Server server;
public ConnectionWorker(Server server) {
this.server;
}
#Override
protected ServerdoInBackground() throws Exception {
server.doStuff(); //...
return ...;
}
}
Take a look at SwingWorker more details
So I have been trying to implement a progress indicator with no luck. I am not sure I understand managing threads with JavaFx very well, despite having read a bit about the Platform.RunLater and Tasks. So here is my use case.
My program allows users to connect to a database and look at some of the schemas and other objects in the database. Sometimes connecting to a large database and pulling up all its tables and info takes a while, so I would like to show a progress indicator. I am not trying to update the progress at all I would just like to make the progress indicator visible at a value of -1 while the process is running to pull everything from the database. Ideally I will have a progress indicator loaded in from an FXML file invisible. When I start the process of pulling info from the database I would like to make it visible.
When trying to make my progress visible it never showed up, so I decide to start out having it visible and making it invisible, just to see what happens. The progress indicator rotated nicely when I opened the program up, but as soon as I try to connect to the database it stopped rotating and just froze. I assume this is what happens when I try to make it visible too which is why it was never showing up.
The following is my current code, I would appreciate any detailed help with explanations so I can understand what is going on. Thanks
from the method that is doing most of the work.
//make progress indicator visible
pi.setVisible(true);
// separate non-FX thread
ExtractorThread t = new ExtractorThread();
t.setCp(cp);
t.start();
//Wait until the thread is done
try{
t.join();
}
catch(Exception e){
e.printStackTrace();
}
//Retrieve the dbextractor from the thread
DbExtractor dbe = t.getDbe();
//move on to the next page in the application
this.caster.goToDataSource(c, cp, dbe);
The ExtractorThread which does the work.
private class ExtractorThread extends Thread{
private ConnectionProperties cp;
private DbExtractor dbe;
public void run() {
dbe = new DbExtractor(cp);
try {
dbe.extract();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public DbExtractor getDbe() {
return dbe;
}
public void setCp(ConnectionProperties cp) {
this.cp = cp;
}
}
If I am supposed to use the Platform.RunLater I am not sure where to use it or why. Any help would be appreciated, thanks!
Use the javafx.concurrent API. Extend Task instead of Thread:
private class ExtractorThread extends Task<DbExtractor>{
private ConnectionProperties cp;
public DbExtractor call() throws Exception {
dbe = new DbExtractor(cp);
dbe.extract();
return dbe;
}
public void setCp(ConnectionProperties cp) {
this.cp = cp;
}
}
Then do:
//make progress indicator visible
pi.setVisible(true);
// separate non-FX thread
final ExtractorThread t = new ExtractorThread();
t.setCp(cp);
t.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
public void handle(WorkerStateEvent event) {
DbExtractor dbExtractor = t.getValue();
this.caster.goToDataSource(c, cp, dbe);
}
});
t.setOnFailed(...); // similarly, to handle exceptions
new Thread(t).start();
I don't code JavaFX, and so I can't give you chapter and verse, but this line:
t.join();
will block the calling code until the background thread is through. Don't do this. Instead use some type of listener to get notified when the background thread finishes. If this were Swing, I'd use a PropertyChangeListener added to a SwingWorker to notify me when the background thread was done. I think that you can still use a PropertyChangeListener to do a similar thing with with JavaFX, but I cannot tell you if this would represent the canonical solution.
Also, don't extend Thread but instead implement Runnable. This won't fix your problem but is basic Java common sense.
I'm trying to write a Swing application in Java that also runs the Google AppEngine Dev-Server (see Developing a Java Application that uses an AppEngine database) and am running into a strange problem with the Swing Eventloop.
I have the following two classes:
A debug-window, which will eventually receive log messages, etc:
public class DebugWindow {
private static JFrame debugWindow = null;
private static JTextArea debugContent = null;
public static void show() {
debugWindow = new JFrame("Debug");
debugWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
debugContent = new JTextArea("Debug messages go here!");
debugWindow.add(debugContent, BorderLayout.CENTER);
debugWindow.pack();
debugWindow.setVisible(true);
}
}
A helper-class that loads the Google AppEngine Dev-Server:
// other imports
import com.google.appengine.tools.development.DevAppServerMain;
public class DevServer {
public static void launch(final String[] args, boolean waitFor) {
Logger logger = Logger.getLogger("");
logger.info("Launching AppEngine server...");
Thread server = new Thread() {
#Override
public void run() {
try {
DevAppServerMain.main(args); // run DevAppServer
} catch (Exception e) { e.printStackTrace(); }
}
};
server.setDaemon(true); // shut down server when rest of app completes
server.start(); // run server in separate thread
if (!waitFor) return; // done if we don't want to wait for server
URLConnection cxn;
try {
cxn = new URL("http://localhost:8888").openConnection();
} catch (IOException e) { return; } // should never happen
boolean running = false;
while (!running) {
try {
cxn.connect(); // try to connect to server
running = true;
} catch (Exception e) {}
}
logger.info("Server running.");
}
}
My main(...) method looks like this:
public static void main(final String[] args) throws Exception {
DevServer.launch(args, true); // launch and wait for AppEngine dev server
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
DebugWindow.show(); // create and show debug window
}
});
}
With this I'm getting some very strange behavior regarding the Swing Eventloop:
First, the way Swing should work: If I comment out the line DevServer.launch(...) in main(...), the application launches, shows the debug window, keeps running, and when I close the debug window, it shuts down.
If I add DevServer.launch(...) back in, it launches the server as expected, and then quits immediately (it probably also showed the debug window briefly, but it's too quick to see).
If I move DevServer.launch(...) line after SwingUtilities.invokeLater(...), it shows the debug window, then launches the server, and when the server is up, it quits immediately.
Now it get's really weird: If I change the line to DevServer.launch(args, false), i.e. I don't wait for the server to actually launch, but simply let my main(...) method complete immediately, the debug window shows, the server loads correctly, the application keeps running, but doesn't quit if I close the debug window?!
If I then also change JFrame.DISPOSE_ON_CLOSE to JFrame.EXIT_ON_CLOSE, the debug window shows, the server loads correctly, the application keeps running, and it quits correctly if I close the debug window.
Any idea what is going on with the Swing event loop here? I'm stumped... Are there things that will cause the Swing event loop to terminate early (scenario 2 and 3)? Do multi-threaded applications prevent Swing from detecting the last disposed window (scenario 4)?
For reference, here is the source of the Google AppEngine Dev Server.
Items #4 and #5 are actually expected behavior. A Java/Swing application doesn't stop when the last Swing window is disposed, but when the last thread stops executing. These two conditions are equivalent for single-threaded applications, but not for multi-threaded ones.
As for #1, #2 and #3: looking through the AppEngine Dev Server code, I noticed a fair amount of System.exit(int) calls in there. One of these is probably the culprit. If the code you're showing is all that's relevant, then the offending System.exit is likely called in response to the connection established after if (!waitFor) return; (due to #4)
I have an app that connects reads file on remote server. File dynamically updates that's why I use Timer class to reread this file periodically.
Workflow is the following:
Open window where text will be displayed.
Start reading file (reread once per 15 sec using Timer)
In 15 seconds window is filled with data or I receive exceptions in log. Exceptions are suppressed and I continue trying to read data.
Exceptions are my problem, because user doesn't know what is happening now with an app.
There are at least two Exceptions I ran at:
- If file is absent, I receive FileNotFoundException.
- If server is on maintenance I receive other Exception (I catch it, so its name doesn't matter).
Here is how above looks like in code:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final RemoteReader reader = new RemoteReader();
Timer timer = new Timer(15000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
reader.getMainPanel().readData();
} catch (IOException e) {
//Here is a counter that increases after each exception throw
if(counter >5) {
JOptionPane.showOptionDialog(chat,
e.getMessage(),
e.getClass().getName(),
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null,
new String[]{"Retry", "Cancel"}, //on Retry - make another 5 tries to read file, on cancel - close the window
null);
counter = 0;
}
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
});
timer.start();
}
});
}
public String readData() throws IOException {
// read and process data before returning it
// but for test purpose:
//BufferedReader br = new BufferedReader(new InputStreamReader(this.url.openStream()));
throw new IOException("cannot read file");
}
What I want to do is to add JProgressBar. On openning the main window progress bar appears and then data is read. If IOException throws 5 times in a row, show option dialog. Otherwise hide progress bar and show data. If remote file becomes unavailable, show option dialog. And pressing the retry button, show progress bar... then workflow starts from the very beginning.
Some code examples would help me, but I don't expect solution for the whole issue - advice, how it should be done in right way from design point of view will be enough. Samples of Oracle a little bit vague for me.
Even if WatchService, seen here, is not available, I'd still use SwingWorker, seen here and here.
You have considerable latitude in pacing the doInBackground() thread, e.g. Thread.sleep(15 * 1000).
You can setProgress() in the background and listen for any PropertyChangeEvent in the GUI.
You can update any GUI components in process(), which runs on the EDT.
I have this Timer that is supposed to initiate various actions at 1 second intervals. It's a pretty simple idea that simulates a 5 second countdown (literally). At the start, a JLabel is updated to set its text to "5". Simultaneously, a little mp3 sound file plays that voices the number that the user sees on the screen. Then, one second later, the text is changed to "4" and a different mp3 plays that voices the number 4. And so on until we get to zero.
This all works, but I can't get the visual updating to synchronize precisely with the audio part. The mp3 always seems to play just slightly before the screen is updated. At first, I thought that I just needed to put a little extra silence at the beginning of each mp3 on a trial and error basis until things synched up. But no matter how much silence I prepend to each mp3, I still hear the audio before the screen updates. All that changes is the lag between each "one second" update.
Anyway, here is the code that I am working with. Can anyone help me get this to synchronize? Maybe I need a second timer? I'm not sure how that would work. Thanks in advance!
class Countdown extends JFrame implements ActionListener {
private Timer countdownTimer = new Timer(1000, this);
int countdownSeconds;
MyJFrame myFrame;
public Countdown(MyJFrame thisFrame) {
int countdownSeconds = 5;
countdownTimer.start();
myFrame = thisFrame;
}
#Override
public void actionPerformed(ActionEvent e) {
if (countdownSeconds == 0) {
myFrame.updateCountdown(myFrame, "Go");
SoundEffect.play("launch.mp3");
countdownTimer.stop();
} else {
myFrame.updateCountdown(myFrame, Integer.toString(countdownSeconds));
if (countdownSeconds == 5) {SoundEffect.play("five.mp3");}
if (countdownSeconds == 4) {SoundEffect.play("four.mp3");}
if (countdownSeconds == 3) {SoundEffect.play("three.mp3");}
if (countdownSeconds == 2) {SoundEffect.play("two.mp3");}
if (countdownSeconds == 1) {SoundEffect.play("one.mp3");}
countdownSeconds--;
}
}
}
public void updateCountdown(MyJFrame thisFrame, String numSec) {
lblCountdown.setText(numSec);
}
import java.io.FileInputStream;
import javazoom.jl.player.Player;
public class SoundEffect {
public static void play(String mp3File) {
try {
FileInputStream mp3_file = new FileInputStream(mp3File);
Player mp3 = new Player(mp3_file);
mp3.play();
}
catch(Exception e) {
System.out.println(e);
}
}
}
I highly doubt you will ever be able to sync those perfectly, but I can explain why the current approach will not work.
Swing components must be updated on the Event Dispatch Thread, as you do with the Timer. When you update the text of the label, it will schedule a repaint on the Event Dispatch Thread. Note the word schedule, and not perform.
However, the Event Dispatch Thread is currently busy playing your sound, so the actual repaint operation will only occur after you called mp3.play().
Now you could (if allowed, not sure about the threading rules for playing MP3's) try to play the mp3 on a different Thread (e.g. by using a secondary non-Swing timer). But since you never have full control over when the actual repaint is going to happen and only can control when the repaint is scheduled, the visual and auditive updates can still be out-of-sync.
The major part of the problem comes down to:
if (countdownSeconds == 5) {SoundEffect.play("five.mp3");}
..leading to..
public class SoundEffect {
public static void play(String mp3File) {
try {
FileInputStream mp3_file = new FileInputStream(mp3File);
Player mp3 = new Player(mp3_file);
mp3.play();
}
catch(Exception e) {
System.out.println(e);
}
}
}
Whoa! This is not the time to be loading the clips!
Instead they should be loaded before the timer ever starts. I think the file I/O is the real cause of the (perceptible) lag or delay.