5

To expound a little more, I have a GUI that looks like:

enter image description here

Then I have an action listener on the OK button that starts like:

//OK Button Action Listener
private void okButtonActionPerformed(ActionEvent e) {   
    //Enable/Disable Buttons
    okButton.setEnabled(false);
    cancelButton.setEnabled(true);
    updateCheckbox.setEnabled(false);
    //Move on to a series of other methods here...

Which should, in theory, make this happen:

enter image description here

However, instead, I get the following until ALL methods and other things connected to the OK button are completed:

enter image description here

This obviously can't happen, because the idea is to make the cancel button available and the OK button and several other tick-boxes unavailable for the duration of the program (Image 2), where, instead, it freezes in a half-state (Image 3). Is there any way to combat this?

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
Jtaylorapps
  • 5,680
  • 8
  • 40
  • 56
  • everything inside actionListener is done (on the screen) in one moment, after all code lines are executed, please whats real goal – mKorbel Jul 31 '13 at 16:51
  • I'm having a hard time understanding exactly what you mean, but essentially I have a long-running process that needs to happen while also keeping the GUI available. Based on other answers, I should be using another thread via `SwingWorker`. – Jtaylorapps Jul 31 '13 at 16:57
  • I have a long-running process ---> then follows and accept one of answers here, bunch of code examples, some of them with excelent descriptions – mKorbel Jul 31 '13 at 16:59
  • :-) this question and answers here are like as wild shorts based on guessing and images sticked, nice, very nice, but you get an aswers – mKorbel Jul 31 '13 at 17:03

4 Answers4

7

Every time you execute logic from the GUI you should be using the SwingWorker in the following way:

SwingWorker myWorker= new SwingWorker<String, Void>() {
    @Override
    protected String doInBackground() throws Exception {
        //Execute your logic
        return null;
    }
};
myWorker.execute();

If you want to update the GUI from inside this logic use InvokeLater:

SwingUtilities.invokeLater(new Runnable() {
    @Override
    public void run() {
        //To update your GUI
    }
});

With this you can be sure that both your logic and your GUI stay responsive.

Edit:

You could also use invokeAndWait if this suits your needs more. Link to related answer

Community
  • 1
  • 1
bjedrzejewski
  • 2,378
  • 2
  • 25
  • 46
  • Er, not so much. There is a perfectly good framework within the `SwingWorker` for passing back intermediate updates using `publish` and `process`; why on earth would you use `invokeLater`?! – Boris the Spider Jul 31 '13 at 16:08
  • The logic that I follow is here: http://stackoverflow.com/a/7196920/1611957 Could you explain why do you think it does not apply? I mean- it does make it easy. – bjedrzejewski Jul 31 '13 at 16:15
  • Because the `SwingWorker` already has an asynchronous task queue whereby you can pass items into `process` and then the EDT will call `publish`. You override `publish` and use the item to update the GUI. Your method requires creation of another anonymous class (namely the `Runnable`) and if you want to pass any data into it then it has to be `final` etc. Also you need to consider thread safety as the `Runnable` will be invoked by the EDT. – Boris the Spider Jul 31 '13 at 16:23
  • Sounds reasonable, you could add it to your solution so that there are two ways of achieving that here. – bjedrzejewski Jul 31 '13 at 16:26
  • I've never actually used SwingWorker before. In its most basic form, am I basically wanting to wrap all of my ActionListener code that isn't directly related to the GUI inside of SwingWorker within the ActionEvent method? – Jtaylorapps Jul 31 '13 at 16:46
  • Yes JTApps- if you do it, it will keep the GUI responsive. How to update GUI from inside this (if you need to) is another story- discussed here in the comments :) – bjedrzejewski Jul 31 '13 at 16:47
  • I think I understand now, and I'll be giving it a shot here soon. I will need to be updating the GUI due to the fact that there is a progress bar and an on-screen percent completed counter that will need to be kept updated as the thread progresses. That'll be a hurdle for after I get the GUI responsive in the first place, though. – Jtaylorapps Jul 31 '13 at 16:59
  • Just put anything that is slow inside the worker and all gui updates (from the worker) into invokeLater if you want simple and correct solution :) – bjedrzejewski Jul 31 '13 at 17:01
  • I'll give it a shot and see if I can get it working :) I'll accept your answer too seeing as it addresses the concern with updating the GUI most completely. – Jtaylorapps Jul 31 '13 at 17:18
  • this answer doesn't explian proper of ways, process, setProcess, publish and done notified EDT, without using invokeLater as notifier, doInBackground() is bridge betweens Swing and Workers Thread, this method isn't designated to notify EDT – mKorbel Jul 31 '13 at 17:50
3

//Move on to a series of other methods here...

Make sure you don't block the GUI thread (EDT) with long-running operations. Instead use SwingWorker.

Community
  • 1
  • 1
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
3

Are you doing some processing on the same thread that's handling the GUI? You might want to look into a SwingWorker thread to do the heavy stuffin the background so your UI remains responsive if so.

user1111284
  • 886
  • 1
  • 6
  • 23
  • That's exactly what I'm doing and I see what I'm doing wrong. I've just got to try and understand implementing another thread now. – Jtaylorapps Jul 31 '13 at 16:43
3

Yup, that's because you have blocked with EDT with your other methods.

You need to use another Thread to do the work in the background otherwise the GUI will be blocked.

In Swing applications it is recommended that any long running tasks are carried out on a SwingWorker.

Take a look at the documentation for an introduction.

A SwingWorker will carry out a task that it is given and it can report back to the GUI when it is done. This will be done in a non-blocking way so that you can still use the GUI while the task it being carried out.

If you want to be able to cancel the background task you need to keep a reference to the SwingWorker so that you can call the cancel method. In this case the work method needs to be interruptable, otherwise the task cannot be cancelled.

Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • Cancel will, in this case, simply exit the program out entirely rather than interrupting it. The way the various answers are making it sound, it seems like it might require a major code restructure which I do NOT want to do. I could be interpreting it wrong, though. Are you suggesting essentially wrapping all of the action listener code that isn't related to the GUI using SwingWorker? – Jtaylorapps Jul 31 '13 at 16:41
  • Yes, that is what you should do. If you want to provide GUI updates as processing is happening then you publish updates using the `publish` method and override the `process` method. The `process` method will run on the EDT, thereby not breaking Swing's single threaded model. – Boris the Spider Jul 31 '13 at 16:49
  • I'm doing my research now, but I'm having a tiny bit of trouble with the logic. For example, if I wanted to do `cancelButton.setEnabled(true)` while running the `doSomething()` method, what would that look like? A visual representation of the logic here would help me to best apply it. – Jtaylorapps Jul 31 '13 at 16:55