I have a Java swing UI where I need to disable a button on the UI if I could not detect an active internet connection.
The code function properly so far, but when I get disconnected from the internet, it does not retrigger the method to update the boolean flag.
How can I add an event on that flag to let my button consume it during the lifetime of the application?
public class Main {
private static JButton button;
private static boolean testButtonEnabled;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
/*
* function that returns true/false if connected to the internet
*/
if(Utils.isConnectedToInternet()) {
logger.debug("System is connected to the internet");
testButtonEnabled=true;
} else {
logger.debug("System is not connected to the internet");
testButtonEnabled=false;
}
Main window = new Main();
window.frame.setVisible(true);
button = new JButton("my button");
/*
* set the internet status
*/
button.setVisible(testButtonEnabled);
}
}
}
}
Again, this code:
SwingWorker<Void, String> worker = new SwingWorker<Void, String>()
{
public Void doInBackground()
{
while(true)
{
try {
isConnectedToInternet = Utils.isConnectedToInternet();
if (isConnectedToInternet) {
btn_online2.setEnabled(isConnectedToInternet);
} else {
btn_online2.setEnabled(isConnectedToInternet);
}
logger.debug("Internet connection status: " + isConnectedToInternet);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.execute();
is dangerous since it makes mutational changes to a Swing component from a background thread. While this code may work 95% of the time, it can fail in unpredicatable ways and at unpredicatable times. Better to only mutate Swing components on the event thread. For instance, even this would be better:
SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
#Override
public Void doInBackground() {
while(true) {
try {
isConnectedToInternet = Utils.isConnectedToInternet();
// note that there is no need for the if/else block
SwingUtilities.invokeLater(() -> {
btn_online2.setEnabled(isConnectedToInternet);
});
logger.debug("Internet connection status: " + isConnectedToInternet);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.execute();
or better still, using SwingWorker's publish/process:
SwingWorker<Void, Boolean> worker = new SwingWorker<Void, Boolean>() {
#Override
public Void doInBackground() {
while(true) {
try {
isConnectedToInternet = Utils.isConnectedToInternet();
// note that there is no need for the if/else block
publish(Utils.isConnectedToInternet());
logger.debug("Internet connection status: " + isConnectedToInternet);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
protected void process(List<Boolean> chunks) {
for (Boolean chunk : chunks) {
btn_online2.setEnabled(chunk);
}
}
};
worker.execute();
I was able to resolve using the following method in the initialize() of the frame.
Adding that change part of the swingworker allowed the button to check the flag and assign it to itself whenever it changes.
Now whenever the internet is disconnected the button is disabled and whenever connected the button is enabled.
SwingWorker<Void, String> worker = new SwingWorker<Void, String>()
{
public Void doInBackground()
{
while(true)
{
try {
isConnectedToInternet = Utils.isConnectedToInternet();
if (isConnectedToInternet) {
btn_online2.setEnabled(isConnectedToInternet);
} else {
btn_online2.setEnabled(isConnectedToInternet);
}
logger.debug("Internet connection status: " + isConnectedToInternet);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.execute();
Related
I am writing a turn-based game on the internet. I try to pop up a window that should be in front until the input stream is ready. I created smth like this, but it seems that it does not work.
class CustomBlockerDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = 1L;
public CustomBlockerDialog(Frame owner, String text) {
super(owner, true);
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setSize(300, 100); // Adjust if needed
setTitle("");
add(new JLabel(text, SwingConstants.CENTER));
}
}
final CustomBlockerDialog block = new CustomBlockerDialog(null, "Not your turn");
SwingUtilities.invokeLater(new Runnable() {//A
#Override
public void run() {
System.out.println("show");
block.setVisible(true);
}
});
boolean one_write_only = true;
while(in.ready()){ /* C*/
if(one_write_only){
System.out.println("waiting server");
one_write_only = false;
}
};
System.out.println("suppose to hide");
SwingUtilities.invokeLater(new Runnable() {//B
#Override
public void run() {
System.out.println("hide");
block.setVisible(false);
}
});
It looks like "A" and "B" are executed after "C" and I have no idea why.
Your problem must be due to "C" being called on the Swing event thread and not in a background thread, since it sounds like "C" is blocking the event thread from running "A". Solution: be sure that "C" is not called on the Swing event thread. Also if this is the case, and this can be tested by running the SwingUtilities.isEventDispatchThread() method, then you don't need all those other runnables.
// note that this all must be called on the Swing event thread:
final CustomBlockerDialog block = new CustomBlockerDialog(null, "Not your turn");
System.out.println("show");
// block.setVisible(true); // !! no this will freeze!
final SwingWorker<Void, Void> worker = new SwingWorker<>() {
public void doInBackground() throws Exception {
boolean one_write_only = true;
while(in.ready()){ /* C*/
if(one_write_only){
System.out.println("waiting server");
one_write_only = false;
}
}
}
}
worker.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChanged(PropertyChangeEvent pcEvt) {
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
System.out.println("hide");
block.setVisible(false);
// call worker's get() method here and catch exceptions
}
}
});
worker.execute();
// moved to down here since the dialog is modal!!!
block.setVisible(true);
Caveat: code not compiled nor tested. There may be errors present as it was typed off the cuff.
Thanks to Hovercraft Full Of Eels, I created a little different solution which works in my case:
final SwingWorker<Object,Object> worker2 = new SwingWorker<Object, Object>() {
public Object doInBackground() throws Exception {
boolean one_write_only = true;
while(!in.ready()){ /* C*/
if(one_write_only){
System.out.println("waiting server");
one_write_only = false;
}
}
return one_write_only;
}
protected void done() {
try {
block.setVisible(false);
} catch (Exception ignore) {}
}
};
worker2.execute();
block.setVisible(true);
I'm trying to figure out why the text field isn't updating. I'm aware that using SwingWorker will probably fix this problem, but I can't understand why it doesn't work in the first place.
public class waitExample {
private JFrame frame;
private JTextField txtLeadingText;
private String one = "update string 1";
private String two = "update string 2";
private String three = "update string 3";
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
waitExample window = new waitExample();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public waitExample() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
txtLeadingText = new JTextField();
txtLeadingText.setHorizontalAlignment(SwingConstants.CENTER);
txtLeadingText.setText("leading text");
frame.getContentPane().add(txtLeadingText, BorderLayout.SOUTH);
txtLeadingText.setColumns(10);
JButton btnClickMeTo = new JButton("CLICK ME TO UPDATE TEXT");
btnClickMeTo.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
try {
updateOne();
Thread.sleep(1000);
updateTwo();
Thread.sleep(1000);
updateThree();
Thread.sleep(1000);
updateLast();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
frame.getContentPane().add(btnClickMeTo, BorderLayout.CENTER);
}
private void updateOne() {
txtLeadingText.setText(one);
}
private void updateTwo() {
txtLeadingText.setText(two);
}
private void updateThree() {
txtLeadingText.setText(three);
}
private void updateLast() {
txtLeadingText.setText("default text");
}
}
From what I understand, the default Thread will prevent any GUI updates. That shouldn't matter because I am setting the textField BEFORE the Thread.sleep.
Why doesn't the text field update? Shouldn't the text be set, then the Thread wait?
EDIT: As per the answers, the above code has been updated.
You are invoking Thread.sleep(1000); on EDT. This means that when your method will end - only then the repaint() will fire (at some point in time later).
Until then your GUI is freezed.
Consider that this is going on one thread (so processing is straightforward):
txtLeadingText.setText(one);
Thread.sleep(1000);
txtLeadingText.setText(two);
Thread.sleep(1000);
txtLeadingText.setText(three);
Thread.sleep(1000);
...
<returning from updateText()>
<processing other events on button click>
...
// some time later
<Swing finds out that GUI needs repaint: calls rapaint()>
This is what you should do (I didn't compile or test it):
public class MyRunnable implements Runnable {
private List<String> strsToSet;
public MyRunnable(List<String> strsToSet) {
this.strsToSet = strsToSet;
}
#Override
public void run() {
try {
if(strsToSet.size() > 0) {
final String str = strsToSet.get(0);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
txtLeadingText.setText(str);
}
});
Thread.sleep(1000);
List<String> newList = new LinkedList<String>(strsToSet);
newList.remove(0);
new Thread(new MyRunnable(newList)).start();
}
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
new Thread(new MyRunnable(Arrays.asList(one, two, three))).start();
It is hard to do in Swing but in contrast in dynamically languages (like Groovy) it would go as simple as that (you'll get a better grasp of what is going on):
edt {
textField.setText(one)
doOutside {
Thread.sleep(1000);
edt {
textField.setText(two)
doOutside {
Thread.sleep(1000);
edt {
textField.setText(three)
}
}
}
}
}
The GUI event loop updates the screen, but it can't update the screen until you return.
I suggest you avoid doing any blocking operations in the GUI event thread.
I'm new with JavaFX and I've a little problem with a thread: I can execute it twice and I can't find why.
Here is a sum-upt of my code:
Task<Void> task = new Task<Void>() {
#Override public Void call() throws ImageLoadedException, HomographyException, IOException {
try{
System.out.println("GO !");
return null;
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
#Override
protected void succeeded() {
super.succeeded();
System.out.println("SUCCEEDED");
}
};
#FXML protected void launch(ActionEvent event){
new Thread(task).start();
}
When I click a first time the button who start my thread, my task run without any problem (my console display "GO !" and "SUCCEEDED").
But if I click a second time, nothing append. Am I doing something wrong ? Can't we use a thread more than once ?
From the Thread.start() documentation : No
It is never legal to start a thread more than once. In particular, a
thread may not be restarted once it has completed execution.
From the Concurrency in JavaFX tutorial :
The Task class defines a one-time object that cannot be reused. If you
need a reusable Worker object, use the Service class.
So, you have to consider the Service class rather than Task.
Edit: this should work for you:
Service service = new Service<>(task);
//Updated use this to create a new Service object instead
Service service = new Service() {
#Override
protected Task createTask() {
return new Task() {
#Override
protected Void call() throws Exception {
//Your codes here
return null;
}
};
}
};
#FXML protected void launch(ActionEvent event){
if (!service.isRunning()) {
service.reset();
service.start();
}
}
With a button can fire new tasks
Button btn = new Button();
btn.setText("New task");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Executor ex=new Executor("Task"+count);
ex.start();
count++;
System.out.println("Task Starting...");
}
});
Do it with a wraper class
import java.io.IOException;
import javafx.concurrent.Task;
public class Executor {
private String name;
private Task<Void> task;
public Executor(final String name) {
this.name=name;
task = new Task<Void>() {
#Override
public Void call() throws IOException, InterruptedException {
try {
int i=0;
while(i<20){
System.out.println(name);
Thread.sleep(2000);
i++;
}
return null;
} catch (IllegalThreadStateException e) {
System.out.println(e);
}
return null;
}
#Override
protected void succeeded() {
super.succeeded();
try {
System.out.println(name+" finish");
} catch (Exception ex) {
System.out.println(ex);
}
}
};
}
public void start() {
try {
Thread th = new Thread(task);
th.start();
} catch (Exception ex) {
System.out.println(ex);
}
}
}
I'm designing a simple JavaFX form.
First, I load the JavaFX environment (and wait for it to finish), with something like this :
final CountDownLatch latch_l = new CountDownLatch(1);
try {
// init the JavaFX environment
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JFXPanel(); // init JavaFX
latch_l.countDown();
}
});
latch_l.await();
}
This works fine. (the reason why I need to first load the JavaFX this way, is because it's mainly a Swing application, with some JavaFX components inside, but they are loaded later)
Now, I'd like to add a splash-screen on launch, and displays it while the JavaFX environment loads (and in fact put in on-screen for like 5 seconds, because there are logo, trademark etc.. of the application I need to show)
So I came up with a SplashScreen class, which just displays a JWindow on-screen, like that :
public class SplashScreen {
protected JWindow splashScreen_m = new JWindow();
protected Integer splashScreenDuration_m = 5000;
public void show() {
// fill the splash-screen with informations
...
// display the splash-screen
splashScreen_m.validate();
splashScreen_m.pack();
splashScreen_m.setLocationRelativeTo(null);
splashScreen_m.setVisible(true);
}
public void unload() {
// unload the splash-screen
splashScreen_m.setVisible(false);
splashScreen_m.dispose();
}
}
Now, I want for the splash-screen to load and display itself 5 seconds.
Meanwhile, I want the JavaFX environment to load, too.
So I updated the CountDownLatch like this :
final CountDownLatch latch_l = new CountDownLatch(2); // now countdown is set to 2
final SplashScreen splash_l = new SplashScreen();
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// show splash-screen
splash_l.show();
latch_l.countDown();
// init the JavaFX environment
new JFXPanel(); // init JavaFX
latch_l.countDown();
}
});
latch_l.await();
splash_l.unload();
}
So, it's working, but the splash only stays for the JavaFX environment to load, so basically it unloads very quickly (which is normal, given the code I wrote).
How to display the splash-screen for 5 seconds minimum (if the JavaFX loads faster) without freezing the EDT ?
Thanks.
The most significant issue is you're blocking the Event Dispatching Thread, meaning that it can't display/update anything while it's blocked. The same problem applies to JavaFX.
You should, also, never update either from anything other then they respective event queues.
Now, there are any number of ways you might be able to go about this, but SwingWorker is probably the simplest for the time been.
I apologise, this is the entire exposure to JavaFX I've had...
public class TestJavaFXLoader extends JApplet {
public static void main(String[] args) {
new TestJavaFXLoader();
}
public TestJavaFXLoader() throws HeadlessException {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Loader loader = new Loader();
loader.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("state") && evt.getNewValue().equals(SwingWorker.StateValue.DONE)) {
System.out.println("Load main app here :D");
}
}
});
loader.load();
}
});
}
public class Loader extends SwingWorker<Object, String> {
private JWindow splash;
private JLabel subMessage;
public Loader() {
}
protected void loadSplashScreen() {
try {
splash = new JWindow();
JLabel content = new JLabel(new ImageIcon(ImageIO.read(...))));
content.setLayout(new GridBagLayout());
splash.setContentPane(content);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
subMessage = createLabel("");
splash.add(createLabel("Loading, please wait"), gbc);
splash.add(subMessage, gbc);
splash.pack();
splash.setLocationRelativeTo(null);
splash.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
protected JLabel createLabel(String msg) {
JLabel message = new JLabel("Loading, please wait");
message.setForeground(Color.CYAN);
Font font = message.getFont();
message.setFont(font.deriveFont(Font.BOLD, 24));
return message;
}
public void load() {
if (!EventQueue.isDispatchThread()) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
loadSplashScreen();
}
});
} catch (Exception exp) {
exp.printStackTrace();
}
} else {
loadSplashScreen();
}
execute();
}
#Override
protected void done() {
splash.dispose();
}
#Override
protected void process(List<String> chunks) {
subMessage.setText(chunks.get(chunks.size() - 1));
}
#Override
protected Object doInBackground() throws Exception {
publish("Preparing to load application");
try {
Thread.sleep(2500);
} catch (InterruptedException interruptedException) {
}
publish("Loading JavaFX...");
runAndWait(new Runnable() {
#Override
public void run() {
new JFXPanel();
}
});
try {
Thread.sleep(2500);
} catch (InterruptedException interruptedException) {
}
return null;
}
public void runAndWait(final Runnable run)
throws InterruptedException, ExecutionException {
if (Platform.isFxApplicationThread()) {
try {
run.run();
} catch (Exception e) {
throw new ExecutionException(e);
}
} else {
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
lock.lock();
try {
Platform.runLater(new Runnable() {
#Override
public void run() {
lock.lock();
try {
run.run();
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
condition.signal();
} finally {
lock.unlock();
}
}
}
});
condition.await();
// if (throwableWrapper.t != null) {
// throw new ExecutionException(throwableWrapper.t);
// }
} finally {
lock.unlock();
}
}
}
}
}
I found the runAndWait code here
In my GUI I have a PDF file creation operation. The operation can take up to 10-15 seconds to complete. When I start the operation, I attach a listener to it. The listener changes the cursor and disables the GUI, until the operation completes.
I would also like to add a progressbar, so the users will have a idea when it is going to complete.
Created a method startProgressBar() and called it from the start of the operation method.
See Below:
private void startSavePdfOperation() {
startProgressBar();
saveOp = new AplotSaveOperation(appReg.getString("aplot.message.SAVETOPDF"), "PDF", session);
saveOp.addOperationListener(new MyOperationListener(this) {
startProgressBar Method - See Below:
public void startProgressBar() {
Shell shell = new Shell(getShell());
shell.setSize(260, 120);
final ProgressBar bar = new ProgressBar(shell, SWT.SMOOTH);
bar.setBounds (20, 20, 200, 20);
shell.open();
final int maximum = bar.getMaximum();
new Thread(new Runnable() {
public void run() {
for (final int[] i = new int[1]; i[0] <= maximum; i[0]++) {
try {Thread.sleep (100);} catch (Throwable th) {}
if (Display.getDefault().isDisposed()) return;
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (bar.isDisposed ()) return;
bar.setSelection(i[0]);
}
});
}
}
}).start();
The code above created the ProgressBar. The issue is that the operation would end well before the progressbar indicator was close to ending.
Question: Is this because in the method I am creating a new thread and the indicator is updating according to the new thread and not the operation thread?
Question: Is it possible to create a new thread that watches the GUI thread and updates the progressbar accordingly?
Read a article suggesting using ProgressMonitorDialog with IRunnableWithProgress.
Method startProgressBar using ProgressMonitorDialog - see below:
public void startProgressBar() {
ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell());
try {
dialog.run(true, true, new IRunnableWithProgress(){
public void run(IProgressMonitor monitor) {
monitor.beginTask("Some nice progress message here ...", 100);
** getThread(); **
monitor.done();
}
});
}
catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void getThread() {
new Thread etc.. etc...
}
It seems that it will have the same issues with threading and updating as the code above.
Question: So now I am thinking can I just add or update the ProgressBar to my existing Listener
OperationListener Code - see below:
public abstract class MyOperationListener implements InterfaceAIFOperationListener {
AplotCreatePDFDialog w = null;
public MyOperationListener(AplotCreatePDFDialog win) {
w = win;
}
public void startOperation(String startMessage) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
w.getShell().setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_WAIT));
w.recursiveSetEnabled(getShell(), getShell().getEnabled());
w.getShell().setEnabled(!getShell().getEnabled());
}
});
}
public void endOperation() {
try {
endOperationImpl();
}
finally {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
w.getShell().setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_ARROW));
w.recursiveSetEnabled(getShell(), true);
w.getShell().setEnabled(!getShell().getEnabled());
w.close();
}
});
}
}
abstract protected void endOperationImpl();
} // end class MyOperationListener
Thanks for any help you can give me with this.
EDIT
Baz, your answer below is exactly what the question asked, so thank you for answering.
But I am starting to think that what I am trying to do is not possible.
When my operation starts, I wanted the progress bar indicator to start and when my operation ended I wanted the indicator be at the end and the monitor would close.
I thought there might bee a way to use my listener to add the progressbar. Something like the following.
public void startOperation(String startMessage) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
->monitor.beginTask("Creating PDF File(s)", IProgressMonitor.UNKNOWN);<-
w.getShell().setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_WAIT));
w.recursiveSetEnabled(getShell(), getShell().getEnabled());
w.getShell().setEnabled(!getShell().getEnabled());
}
});
}
public void endOperation() {
try {
->monitor.worked(1);<-
endOperationImpl();
}
finally {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
w.getShell().setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_ARROW));
w.recursiveSetEnabled(getShell(), true);
w.getShell().setEnabled(!getShell().getEnabled());
->monitor.done();<-
w.close();
}
});
}
}
abstract protected void endOperationImpl();
} // end class MyOperationListener
But I am starting to see that the ProgressBar has to have some sort of measurement to display the indicator correctly.
I would be happy if the indicator just went back and forth and the monitor would close at the end of the operation.
Why not use ProgressMonitorDialog?
Here is a related answer from me showing a simple example.
This is what it looks like:
If you are not sure about the workload, use this code:
monitor.beginTask("Copying files", IProgressMonitor.UNKNOWN);
It will show the idle bar while running.